1
votes

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)?

2
"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.Galik
By the way, if you take the vector by reference it won't be cleaned up at the end of the thread.Galik
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.Galik
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?underscore_d
@Galik 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?Alien_AV

2 Answers

4
votes

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.
3
votes

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.