Lambda captures and predicates
Capture values and references safely, understand lambda lifetime, and build reusable predicates.
Learning objectives
- Explain a lambda as a small function object.
- Distinguish capture by value and capture by reference.
- Use init-capture to move or create captured state.
- Avoid dangling references in stored callbacks.
Prerequisites: functions, parameters, and basic STL algorithms. Review Functions and STL algorithms first.
Lambda anatomy
auto is_even = [](int number) {
return number % 2 == 0;
};The capture list is inside []; parameters follow it; the body contains the work. A lambda creates a closure object whose type is unique and compiler-generated.
Capture by value
int threshold = 10;
auto above = [threshold](int value) {
return value > threshold;
};
threshold = 100;
// above(20) is still trueThe lambda stores its own copy of threshold. Changing the original variable later does not change that captured copy.
Capture by reference
int total = 0;
std::for_each(values.begin(), values.end(),
[&total](int value) { total += value; });The lambda refers to the original variable. Changes are visible to the caller, but the referenced object must remain alive whenever the lambda is used.
Capture defaults
[=]captures used local variables by value.[&]captures used local variables by reference.[this]captures the current object pointer (C++11).[*this]captures a copy of the current object (C++17).
Prefer explicit captures in teaching examples and callbacks that may outlive the current scope. Broad defaults can hide dependencies.
Init-capture
Language version: init-capture was introduced in C++14. Capturing the current object by copy with [*this] requires C++17.
auto task = [message = std::string("ready") ] {
std::cout << message;
};Init-capture creates a closure member initialized by the expression. It can also move ownership into a lambda, for example [ptr = std::move(unique_ptr_value)].
mutable lambdas
A lambda that captures by value treats those captured copies as const inside its call operator by default. Add mutable if the lambda needs to modify its own stored copy.
int start = 0;
auto next = [start]() mutable {
return ++start;
};Calling next() repeatedly updates the closure's private copy, not the original start.
Lifetime and safety
A lambda that captures a local variable by reference must not be used after that variable's lifetime ends. Returning a callback that references a local variable creates a dangling reference. For callbacks stored for later, capture needed state by value or use an ownership model that guarantees the referenced object's lifetime.
Practice
- Write a lambda that captures a multiplier by value and multiplies its argument.
- Write a count_if predicate that captures a threshold.
- Why can returning a lambda with [&local] be unsafe?
- What does mutable change for a value-capturing lambda?
Show solutions
[factor](int x) { return x * factor; }[limit](int x) { return x > limit; }- The local variable is destroyed when the function returns, leaving a dangling reference.
- It allows the closure to modify its own captured-by-value copies.
Summary
Captures define what state a closure stores or refers to. Value capture owns a copy; reference capture depends on an external lifetime. Choose captures deliberately, especially for delayed callbacks.