Appearance
C++ Lambdas and Callable Design
A lambda creates a closure object. Its capture list determines which surrounding state becomes part of that object and therefore which lifetimes and mutations the callback can observe.
Captures are stored state
cpp
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> values{2, 5, 8, 11};
int threshold = 6;
auto count = std::ranges::count_if(values, [threshold](int value) {
return value >= threshold;
});
std::cout << count << '\n';
}[threshold] copies the current value into the closure. [&threshold] stores a reference-like capture and requires threshold to outlive every invocation. [=] and [&] broad defaults are concise but can hide accidental dependencies, especially in asynchronous callbacks.
[this] captures the object pointer, not an owning copy of the object. A callback that outlives the instance dangles. [*this] captures a copy in supported standards, with its own semantic and cost implications.
Lambdas have unique types
Each lambda expression has an unnamed closure type. Templates can accept it without type erasure:
cpp
template <typename Predicate>
void visit_matching(Predicate predicate);std::function<R(Args...)> stores many callable types behind one copyable type-erased interface. It may allocate and adds indirection. Use it when a stable runtime-polymorphic callable value is needed, not automatically for every callback parameter.
Function pointers remain useful for C APIs and captureless callbacks. A capturing lambda cannot convert to a plain function pointer because that pointer has nowhere to store closure state.
Generic lambdas are local templates
cpp
auto identity = [](auto&& value) -> decltype(auto) {
return std::forward<decltype(value)>(value);
};Generic parameters are useful, but forwarding and reference-preserving return types carry lifetime hazards. Prefer ordinary value returns unless the abstraction intentionally preserves identity.
Callback ownership is an API decision
Does a function invoke the callable synchronously, store a copy, move it into a worker, or retain a reference? State that contract. A lambda safe for immediate sort may be unsafe when saved for later.
Cancellation, thread affinity, error propagation, and reentrancy are also part of callback design. The lambda syntax does not solve them.
Optional prompts
Debug: An asynchronous callback captures a local variable with [&] and later crashes. What is the likely defect?
Answer: The closure retained a reference after the local's lifetime ended. Capture needed values by value or arrange shared lifetime deliberately.
Explain: Why use a template parameter instead of std::function for a synchronous algorithm callback?
Answer: It preserves the concrete callable type, enabling inlining and avoiding type-erasure overhead while accepting lambdas and function objects naturally.