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

Alien_AV
  • 345
  • 1
  • 9
  • 3
    ***"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 Sep 30 '18 at 15:53
  • By the way, if you take the vector *by reference* it **won't** be cleaned up at the end of the thread. – Galik Sep 30 '18 at 15:56
  • 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 Sep 30 '18 at 15:58
  • 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?](https://stackoverflow.com/questions/10770181/should-a-move-constructor-take-a-const-or-non-const-rvalue-reference) – underscore_d Sep 30 '18 at 16:07
  • @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 Sep 30 '18 at 16:36
  • @Galik 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.) – Alien_AV Sep 30 '18 at 16:40

2 Answers2

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.
xtofl
  • 40,723
  • 12
  • 105
  • 192
  • 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. – Alien_AV Sep 30 '18 at 16:46
  • `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. – xtofl Oct 01 '18 at 12:01
  • I think this comment sums it up perfectly. Can you add it to your answer, and I'll accept it? – Alien_AV Oct 01 '18 at 17:01
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.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • Can you take a look at the follow up part in the question and address that as well? Thanks – Alien_AV Sep 30 '18 at 19:05
  • 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. – Sam Varshavchik Sep 30 '18 at 19:51