Why is a lambda not-movable if it captures a not-copiable object using std::move()?
Your lambda does not become non-movable by having a move capture. But it does become non-copyable, which is a problem.
std::function
does not support moving the supplied functor into itself, it always does a copy. Non-copyable lambdas (and other callables) therefore cannot be used with std::function
. The reason for this limitation is that the standard requires std::function
to be copyable, which could not be achieved if it was initialised with a non-copyable callable.
The problem is not with your lambda, but with your object being non-copyable, since std::function
demands its objects to be copyable the compiler complains. You should almost always follow the rule-of-zero.
In general:
- A lambda can be both copyable and moveable.
- If the lambda has non-copyable captures, it makes the the lambda itself not copyable. Those objects can be wrapped in a smart_pointer which can be moved (or copied -
shared_ptr
) in the lambda capture though. - If there is no capture by value, the closure type (the lambda) is typically trivially copyable and trivially moveable.
- The closure type would be trivially copyable and trivially moveable if-and-only-if all captured by value objects are of trivially copyable and trivially moveable non-const types (eg C-like types).
- Otherwise if there is capture by value, the move constructors of the closure type would copy the captured-by-value objects.
- If there is capture by value of a const object, any moves in the capture list would result in a copy.
- If the lambda itself is const it is never moved, only copied, even to other const lambdas.
example:
#include <iostream>
#include <type_traits>
struct S
{
S() {
std::cout << "ctor" << '\n';
}
~S() noexcept {
std::cout << "dtor" << '\n';
}
S(const S&) {
std::cout << "copy ctor\n";
}
S(S&&) noexcept noexcept {
std::cout << "move ctor\n";
}
S& operator= (const S&) {
std::cout << "copy aop\n";
}
S& operator= (S&&) noexcept {
std::cout << "move aop\n";
}
};
template <typename T>
void getTraits()
{
std::cout << std::boolalpha
<< "trivially_copy_constructible? "
<< std::is_trivially_copy_constructible_v<T>
<< "\ntrivially_move_constructible? "
<< std::is_trivially_move_constructible_v<T> << '\n' ;
}
int main()
{
S s ;
const S cs;
{
std::cout << "capture by value\n" ;
//auto closure = [s = std::move(s)] {} ; // S::move construct // 1.
//auto closure = [cs = std::move(cs)] {} ; // S::copy construct // 2.
//const auto closure = [s = std::move(s)] {} ; // S::move construct // 3.
const auto closure = [cs = std::move(cs)] {} ; // S::copy construct // 4.
getTraits<decltype(closure)>();
const auto copy_constructed = std::move(closure);
const auto move_constructed = std::move(closure);
}
{
std::cout << "\ncapture by reference\n";
const auto closure = [&s] {};
getTraits<decltype(closure)>();
}
}
Uncomment 1, 2, 3, 4 one at a time and check the outputs. Remember std::move
simply turns an object into an rvalue reference.