Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Capture and move a unique_ptr in a c++14 lambda expression

I am capturing a unique_ptr in a lambda expression this way:

auto str = make_unique<string>("my string");
auto lambda = [ capturedStr = std::move(str) ] {
   cout << *capturedStr.get() << endl;
};
lambda();

It works great until I try to move capturedStr to another unique_ptr. For instance, the following is not working:

auto str = make_unique<string>("my string");
auto lambda = [ capturedStr = std::move(str) ] {
    cout << *capturedStr.get() << endl;
    auto str2 = std::move(capturedStr); // <--- Not working, why?
};
lambda();

Here is the output from the compiler:

.../test/main.cpp:11:14: error: call to implicitly-deleted copy
constructor of 'std::__1::unique_ptr<std::__1::basic_string<char>,
std::__1::default_delete<std::__1::basic_string<char> > >'
        auto str2 = std::move(capturedStr);
             ^      ~~~~~~~~~~~~~~~~~~~~~~ ../include/c++/v1/memory:2510:31: note: copy constructor is implicitly
deleted because 'unique_ptr<std::__1::basic_string<char>,
std::__1::default_delete<std::__1::basic_string<char> > >' has a
user-declared move constructor
    _LIBCPP_INLINE_VISIBILITY unique_ptr(unique_ptr&& __u) _NOEXCEPT
                              ^ 1 error generated.

Why isn't it possible to move capturedStr?

like image 786
MartinMoizard Avatar asked Jan 06 '15 14:01

MartinMoizard


People also ask

What does it mean to Lambda capture this?

The lambda is capturing an outside variable. A lambda is a syntax for creating a class. Capturing a variable means that variable is passed to the constructor for that class. A lambda can specify whether it's passed by reference or by value.

What is auto lambda expression?

Immediately invoked lambda expression is a lambda expression which is immediately invoked as soon as it is defined. For example, #include<iostream> using namespace std; int main(){ int num1 = 1; int num2 = 2; // invoked as soon as it is defined auto sum = [] (int a, int b) { return a + b; } (num1, num2);


2 Answers

The operator () of a lambda is const by default, and you can't move from a const object.

Declare it mutable if you want to modify the captured variables.

auto lambda = [ capturedStr = std::move(str) ] () mutable {
//                                             ^^^^^^^^^^
    cout << *capturedStr.get() << endl;
    auto str2 = std::move(capturedStr);
};
like image 82
T.C. Avatar answered Oct 08 '22 02:10

T.C.


auto lambda = [ capturedStr = std::move(str) ] {
   cout << *capturedStr.get() << endl;
   auto str2 = std::move(capturedStr); // <--- Not working, why?
};

To give more detail, the compiler is effectively making this transformation:

class NameUpToCompiler
{
    unique_ptr<string> capturedStr;  // initialized from move assignment in lambda capture expression

    void operator()() const
    {
        cout << *capturedStr.get() << endl;
        auto str2 = std::move(capturedStr);  // move will alter member 'captureStr' but can't because of const member function.
    }
}

The use of mutable on the lambda will remove the const from the operator() member function, therefore allowing the members to be altered.

like image 11
rparolin Avatar answered Oct 08 '22 02:10

rparolin