2

Why does this code fail to compile?

#include <memory>
#include <utility>

int foo() {
    auto num = std::make_unique<int>(1);
    auto func = [s = std::move(num)] {
        auto u = std::move(s); <-- ERROR!
        return *u;
    };
    return func();
}

The error is:

<source>:8:14: error: call to deleted constructor of 'std::unique_ptr<int, std::default_delete<int>>'
        auto u = std::move(s);
             ^   ~~~~~~~~~~~~
/opt/compiler-explorer/gcc-10.2.0/lib/gcc/x86_64-linux-gnu/10.2.0/../../../../include/c++/10.2.0/bits/unique_ptr.h:468:7: note: 'unique_ptr' has been explicitly marked deleted here
      unique_ptr(const unique_ptr&) = delete;

I can't figure out why is the copy constructor being called, which is obviously deleted for a unique_ptr by design. I can understand what the error is, but not why it's there in the first place.

Here's what I think happens but I'm not sure. If I unpack this lambda into a sort of struct with the operator() as

template <typename T>
struct Lambda{
  :
  :
  operator() const{
    auto u = std::move(s); // <-- error
  }
  private:
  std::unique_ptr<T> s;
};

I think this would fail to compile because the move(s) would change the value of s which isn't allowed in a const function. So compilations should've failed citing the immutability of the lamda. Even the fix to this error is by changing the lambda to be mutable. But another one appears to be to make s a shared_ptr (which as per me should have failed since the lambda would still be immutable) and this is where I'm confused.

I've tried this on both clang and gcc with similar results. So, can please someone help with clearing the gap in my understanding?

O'Neil
  • 3,790
  • 4
  • 16
  • 30
Zoso
  • 3,273
  • 1
  • 16
  • 27
  • 4
    Lambda captures, like `s` are const by default. Try adding `mutable` before `{`. (Also your lambda is missing an argument list?) – user253751 Feb 18 '21 at 15:16
  • No argument list [works](https://godbolt.org/z/vYK7bG) as parameter list is optional as per the 4th avatar [here](https://en.cppreference.com/w/cpp/language/lambda) and I've tried adding `mutable` to the lambda which does fix it, as already mentioned in my question. I was more intent on deciphering the compiler error. Thanks anyway! – Zoso Feb 18 '21 at 15:31
  • Ok, if the answer below is insufficient, let us know and we'll clarify further. – rustyx Feb 18 '21 at 15:51
  • @rustyx I have accepted the answer. The alternative of using a `shared_ptr` came with its own behavior. I've updated my comments to the answer below. Please do add if you have something else. – Zoso Feb 18 '21 at 16:59

1 Answers1

0

It is because you are trying to move a data member in a const member function.

The type and category of std::move(s) is const unique_ptr&&. There is no overload unique_ptr(const unique_ptr&&), the only one that is viable is unique_ptr(const unique_ptr&).

I think this would fail to compile because the move(s) would change the value of s which isn't allowed in a const function.

No, move is simply a static_cast.

Caleth
  • 52,200
  • 2
  • 44
  • 75
  • I understand the first part of your answer quite well. Regarding the `move`, what I meant was something on the lines of [this](https://godbolt.org/z/MxnTMK). As you see, `num` is changed during lambda construction and becomes null. Similarly `s` too should've become `null`, isn't it, akin to `num` as per constructor #10 [here](https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr)? – Zoso Feb 18 '21 at 16:54
  • I think this is probably a different question from the original one and is related to [this](https://stackoverflow.com/questions/43319352/stdmove-with-stdshared-ptr-in-lambda) so in my case, `s` isn't actually modified, and hence the code compiles without a `mutable` keyword. – Zoso Feb 18 '21 at 16:55
  • It's the move constructor that set's `s` null, not the `move` call – Caleth Feb 18 '21 at 18:08
  • Ok. So just to clarify that I do understand what you are saying, `std::move` is just a simple cast that gives an rvalue reference to that object. The actual nullifying is done in the move constructor of the object that's being created using this rvalue reference which just moves the incoming object's memory. – Zoso Feb 19 '21 at 07:03
  • Yes, that's correct – Caleth Feb 19 '21 at 08:38