Lambdas and Move Semantics
Lambdas and Move Semantics
Use local callable objects and efficient value transfer in modern C++.
Lambdas and Move Semantics
Use local callable objects and efficient value transfer in modern C++.
auto square = [](int x) {
return x * x;
};
int factor = 2;
auto scale = [factor](int x) { return x * factor; };
Capture by value when the lambda should keep its own copy. Capture by reference only when the lambda really needs to observe or modify the surrounding object.
std::string name = "Ada";
std::vector<std::string> names;
names.push_back(std::move(name));
After moving, the source object is valid but its exact value is unspecified.
That means you may destroy it, assign to it, or call operations whose post-move behavior is documented, but you should not rely on the old content still being there.
int total = 0;
auto add = [&total](int value) { total += value; };
[&] and [=] for nontrivial lambdas unless the scope is very small and obvious.auto fn = [name = std::string{"Ada"}] {
std::cout << name << '\n';
};
Init captures are useful when a lambda should own moved data.
auto task = [buffer = std::vector<int>{1, 2, 3}] {
return buffer.size();
};
This is often the cleanest way to transfer ownership into deferred work.
auto logger = std::make_unique<Logger>();
auto flush = [owner = std::move(logger)] {
owner->flush();
};
This pattern matters because lambdas are often used to package work for later execution. Init-capture makes the ownership transfer explicit.
A moved-from object is valid but only safe to destroy, assign, or otherwise use according to its documented post-move guarantees.
std::function<int()> make_bad_lambda() {
int value = 42;
return [&value] { return value; };
}
This returns a lambda holding a dangling reference. If the lambda may outlive the scope, capture by value instead.
std::string name = "Ada";
auto fn = [text = std::move(name)] {
std::cout << text << '\n';
};
After this move, name still exists but should not be treated as if it still certainly contains "Ada".
std::function<void()> bad_task() {
std::string message = "ready";
return [&message] {
std::cout << message << '\n';
};
}
This compiles, but the returned lambda keeps a dangling reference. If the lambda survives longer than the local scope, capture by value.
std::ranges::for_each(values, [](int& value) {
value *= 2;
});
auto file = std::make_unique<Logger>();
auto flush = [owner = std::move(file)] {
owner->flush();
};
auto print_twice = [](const auto& value) {
std::cout << value << ' ' << value << '\n';
};
Generic lambdas are often the fastest way to express a tiny template-like callback without writing a named function template.
std::movestd::move#include <algorithm>
#include <vector>
int main() {
std::vector<int> values{1, 2, 3};
std::for_each(values.begin(), values.end(), [](int& value) { value *= 2; });
return values[0];
}
Capture a move-only resource into the lambda. That shows why lambdas are often the cleanest bridge between ownership and deferred work.
#include <memory>
int main() {
auto value = std::make_unique<int>(7);
auto task = [ptr = std::move(value)] { return *ptr; };
return task();
}