std::move a const std::vector in a lambda capture
Asked Answered
H

2

1

Motivation:

I'm trying to transfer a std::vector<std::unique_ptr<some_type>> to a different thread, via a lambda capture.

Since I need the vector to not be cleaned up when the function goes out of scope, I need to take it by value (and not by reference).

Since it's a vector of unique_ptrs, I need to move (and not copy) it into the capture.

I'm using a generalized lambda capture to move the vector while capturing.

Minimal program to illustrate the concept:

auto create_vector(){
    std::vector<std::unique_ptr<int>> new_vector{};
    new_vector.push_back(std::make_unique<int>(5));
    return std::move(new_vector);
}

int main() {
    const auto vec_const = create_vector();

    [vec=std::move(vec_const)](){
        std::cout << "lambda, vec size: " << vec.size() << std::endl;
    }();
}

Issue:

If I'm using a const local vector, compilation fails due to attempting to copy the unique_ptrs. However if I remove the const qualifier, the code compiles and runs well.

auto vec_const = create_vector();

Questions:

What's the reason for this? Does being const disable the "movability" of the vector? Why?

How would I ensure the constness of a vector in such a scenario?

Follow-up:

The comments and answers mention that a const type can't be moved from. Sounds reasonable, however the the compiler errors fail to make it clear. In this case I would expect one of two things:

  • The std::move(vec_const) should throw an error regarding moving from const (casting it to rvalue) being impossible.
  • The vector move-constructor telling me that it refuses to accept const rvalues.

Why don't those happen? Why does instead the assignment seems to just try to copy the unique_ptrs inside the vector (which is what I'd expect from the vectors copy-constructor)?

Havens answered 30/9, 2018 at 15:51 Comment(6)
"Since I need the vector to not be cleaned up when the function goes out of scope, I need to take it by value (and not by reference)." and "Since it's a vector of unique_ptrs, I need to move (and not copy) it into the capture." - those sound mutually exclusive to me.Margrettmarguerie
By the way, if you take the vector by reference it won't be cleaned up at the end of the thread.Margrettmarguerie
It actually souns like you might want a std::shared_ptr to your vector. One of the very rare times you need a pointer to a container.Margrettmarguerie
I mean... how could you move anything out of it, if it was const?? I'm baffled. anyway: Possible duplicate of Should a move constructor take a const or non-const rvalue reference?Germaun
@Margrettmarguerie I feel that we're talking about different things here. If I pass the local vector (stack-allocated) by reference to a new thread, it will be cleaned up when the function which declared it (and passed it to the thread) goes out of scope. Correct?Havens
@Margrettmarguerie I mean move/copy in regard to construction of the new vector at the target location (move semantics). I need a new vector to be created inside the lambda (hence no taking reference), but the unique_ptrs don't like to be copied, only moved (hence by-value move.)Havens
J
4

Moving is a disruptive operation: you conceptually change the content of the thing you move from.

So yes: a const object can (and should) not be moved from. That would change the original object, which makes its constness void.

In this case, vector has no vector(const vector&&), only vector(vector &&) (move constructor) and vector(const vector &) (copy constructor).

Overload resolution will only bind a call with const vector argument to the latter (lest const-correctness would be violated), so this will result in copying the contents.

I agree: error reporting sucks. It's hard to engineer an error report about vector when you hit a problem with unique_ptr. That's why the whole tail of required from ...., required from ... obliterates the view.

From your question, and your code, I can tell that you don't fully grasp the move semantics stuff:

  • you shouldn't move into a return value; a return value is already an rvalue, so there's no point.
  • std::move does not really move anything, it only changes the qualifier of the variable you want to 'move from', so that the right receiver can be selected (using 'binding' rules). It is the receiving function that actually changes the contents of the original object.
Josejosee answered 30/9, 2018 at 15:54 Comment(3)
Does this mean that std::vector has an assignment operator which is receiving parameters of type const && vector, but tries to do a copy-assignment instead? Why the compilation error I get is copying the unique_ptrs inside the vectors? I would expect to receive an error that the vector can't be assigned a const rvalue.Havens
vector has no vector(const vector&&), only vector(vector &&) (move constructor) and vector(const vector &) (copy constructor). Overload resolution will only bind to the latter (lest const-correctness would be violated), so that will result in copying the contents. I agree: error reporting sucks.Josejosee
I think this comment sums it up perfectly. Can you add it to your answer, and I'll accept it?Havens
L
3

When you are moving something from A to B, then act of moving must necessarily mean that A gets modified, since after the move A may no longer have whatever was in A, originally. This is the whole purpose of move semantics: to provide an optimal implementation since the moved-from object is allowed to be modified: its contents getting transferred in some fast and mysterious way into B, leaving A in some valid, but unspecified, state.

Consequently, by definition, A cannot be const.

Leis answered 30/9, 2018 at 15:56 Comment(2)
Can you take a look at the follow up part in the question and address that as well? ThanksHavens
C++ compilers never had, and never will, have a reputation for clear compilation errors. C++ is the most complicated general purpose programming language in use today. What may seem obvious to you, is not so obvious to the compiler, who will often not see a problem until it's in the middle of unraveling the bowels of some nested template, when it will run into a roadblocked cause by a failure to meet some precondition at the very beginning of the template's invocation.Leis

© 2022 - 2024 — McMap. All rights reserved.