c++lambdasmart-pointers

std::move captured unique_ptr inside lambda


I wanted to capture std::unique_ptr into lambda and then move it into another function inside lambda, but my two minimal examples fail to compile and I'm not sure why.

Can somebody explain to me, why it tries to make a copy even though I'm trying to move it in in first example and where does const qualifier comes from in second example?

First example (function accepts std::unique_ptr by value):

#include <memory>

void sink(std::unique_ptr<int> p) {
    // Do something with `p`
}

int main() {
    auto p = std::make_unique<int>(42);
    auto f = [p = std::move(p)](){
        sink(std::move(p));
    };
    f();
    return 0;
}

The error is:

<source>:10:13: error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]'
   10 |         sink(std::move(p));
      |         ~~~~^~~~~~~~~~~~~~
In file included from /opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/memory:78,
                 from <source>:1:
/opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/bits/unique_ptr.h:516:7: note: declared here
  516 |       unique_ptr(const unique_ptr&) = delete;
      |       ^~~~~~~~~~
<source>:10:13: note: use '-fdiagnostics-all-candidates' to display considered candidates
   10 |         sink(std::move(p));
      |         ~~~~^~~~~~~~~~~~~~
<source>:3:32: note:   initializing argument 1 of 'void sink(std::unique_ptr<int>)'
    3 | void sink(std::unique_ptr<int> p) {
      |           ~~~~~~~~~~~~~~~~~~~~~^
Compiler returned: 1

Second example (function accepts std::unique_ptr by rvalue ref):

#include <memory>

void sink(std::unique_ptr<int> &&p) {
    // Do something with `p`
}

int main() {
    auto p = std::make_unique<int>(42);
    auto f = [p = std::move(p)](){
        sink(std::move(p));
    };
    f();
    return 0;
}

The error is:

<source>: In lambda function:
<source>:10:23: error: binding reference of type 'std::unique_ptr<int>&&' to 'std::remove_reference<const std::unique_ptr<int>&>::type' {aka 'const std::unique_ptr<int>'} discards qualifiers
   10 |         sink(std::move(p));
      |              ~~~~~~~~~^~~
<source>:3:34: note:   initializing argument 1 of 'void sink(std::unique_ptr<int>&&)'
    3 | void sink(std::unique_ptr<int> &&p) {
      |           ~~~~~~~~~~~~~~~~~~~~~~~^
Compiler returned: 1

Solution

  • Lambda members, the captures, are treated as const in the lambda body since the synthsized operatror() is const. One way to get around that is to make the lambda mutable like

    #include <memory>
    
    void sink(std::unique_ptr<int> p) {
        // Do something with `p`
    }
    
    int main() {
        auto p = std::make_unique<int>(42);
        auto f = [p = std::move(p)]() mutable {
            sink(std::move(p));
        };
        f();
        return 0;
    }
    

    and then it will compile.