Stateless vs Stateful Lambdas in C++
In modern C++, lambda expressions provide a concise way to define anonymous function objects directly at the call site. The fundamental distinction between stateless and stateful lambdas lies in whether they capture variables from their enclosing scope. This article explores the core differences between stateless and stateful lambdas, examining how they are represented under the hood by the compiler, their memory footprints, their compatibility with legacy C-style function pointers, and common use cases for each.
Understanding the Capture Clause
The behavior of a C++ lambda depends entirely on its capture clause,
denoted by the square brackets [...] at the start of the
lambda definition.
- Stateless Lambdas: Have an empty capture list
[]. They do not capture or store any surrounding local variables. - Stateful Lambdas: Have a non-empty capture list
(such as
[=],[&],[x], or[&y]). They capture one or more variables by value or reference, binding contextual state to the lambda instance.
What is a Stateless Lambda?
A stateless lambda does not rely on any data outside of the arguments passed to it or global/static variables. Because it does not hold state, the compiler generates a closure class with no non-static data members.
auto add = [](int a, int b) {
return a + b;
};Key Properties of Stateless Lambdas:
Conversion to Function Pointers: A unique feature of stateless lambdas is that the C++ standard requires the compiler to generate an implicit conversion operator to a function pointer. This makes stateless lambdas directly compatible with legacy C APIs that expect raw function pointers:
int (*funcPtr)(int, int) = [](int a, int b) { return a + b; };Minimal Memory Footprint: Because they store no member variables, instances of stateless lambdas take up practically zero storage space (subject to the standard C++ rule that complete object types have a minimum size of 1 byte, which is often optimized away entirely).
Default Constructible and Assignable (C++20): Starting in C++20, stateless lambdas are default constructible and copy/move assignable.
What is a Stateful Lambda?
A stateful lambda captures variables from its enclosing scope. When compiling a stateful lambda, the compiler translates the capture list into private member variables inside an underlying, uniquely named closure class (functor).
int factor = 3;
// 'factor' is captured by value
auto multiplier = [factor](int value) {
return value * factor;
};Key Properties of Stateful Lambdas:
No Conversion to Function Pointers: Because a stateful lambda requires internal storage to preserve its captured state, it cannot be implicitly converted to a raw C-style function pointer (
R(*)(Args...)). To store or pass a stateful lambda dynamically, you must use templates,auto, or wrappers likestd::function.Variable Size: The memory size of a stateful lambda corresponds directly to the sizes and alignment of the variables it captures.
Internal State Management: By default, variables captured by value are treated as
constwithin the lambda body. To modify a by-value captured variable inside the lambda, you must mark the lambda asmutable:int counter = 0; auto increment = [counter]() mutable { return ++counter; };
Comparison: Stateless vs Stateful
| Feature | Stateless Lambda | Stateful Lambda |
|---|---|---|
| Capture List | Empty ([]) |
Non-empty ([x],
[&], [=], etc.) |
| Internal Data Members | None | Contains captured data as members |
| Function Pointer Conversion | Yes (implicit conversion) | No |
| Storage Overhead | None (effectively 0 to 1 byte) | Depends on captured variables |
| Use with C APIs | Direct interop | Requires custom context pointers
(void*) |
| Primary Use Cases | Generic algorithms, pure transformations | Callbacks needing context, stateful accumulation |
Stateless lambdas act as pure functions and provide a modern, type-safe replacement for raw function pointers. Stateful lambdas act as full-featured closures, encapsulating both data and execution logic into a single callable object.