|
| 1 | +## Abstract |
| 2 | +A lambda expression is a shorthand notation for creating an unnamed callable object (also called a closure, or an anonymous function). A lambda can "capture" variables from its surrounding scope by value or by reference, allowing the body of the lambda to access or modify those variables without having to pass them as parameters. Unlike regular functions, lambdas are typically written in-line, combining the reusability of functions with direct access to local context. The lambda retains captured variables' state (for captures by value) or dynamically references them (for captures by reference), making lambdas ideal for short, context-dependent operations like custom comparisons, filters, or event handlers. |
| 3 | + |
| 4 | +For example: |
| 5 | +```cpp |
| 6 | +// Define a function in namespace scope |
| 7 | +bool is_even(int x) { |
| 8 | + return x % 2 == 0; |
| 9 | +} |
| 10 | + |
| 11 | +int main() { |
| 12 | + std::vector<int> data = { 1, 3, 5, 10, 73 }; |
| 13 | + |
| 14 | + // Actually use the function here |
| 15 | + bool has_even = std::ranges::any_of(data, is_even); |
| 16 | +} |
| 17 | +``` |
| 18 | +can instead be rewritten as: |
| 19 | +```cpp |
| 20 | +int main() { |
| 21 | + std::vector<int> data = { 1, 3, 5, 10, 73 }; |
| 22 | +
|
| 23 | + // Define a lambda in-line |
| 24 | + bool has_even = std::ranges::any_of(data, [](int x) { return x % 2 == 0; }); |
| 25 | +} |
| 26 | +``` |
| 27 | + |
| 28 | + |
| 29 | + |
| 30 | + |
| 31 | + |
| 32 | +## Syntax |
| 33 | +The basic syntax of a lambda expression looks like this: |
| 34 | +``` |
| 35 | +[captures](params) -> ReturnType { |
| 36 | + statements; |
| 37 | +} |
| 38 | +``` |
| 39 | +A more detailed explanation of the syntax can be found on [cppreference](https://en.cppreference.com/w/cpp/language/lambda#Syntax). |
| 40 | + |
| 41 | + |
| 42 | + |
| 43 | +Each lambda expression has its own unique, unnameable type: |
| 44 | +```cpp |
| 45 | +auto add = [](int x, int y) { return x + y; }; |
| 46 | +``` |
| 47 | +is similar to writing: |
| 48 | +```cpp |
| 49 | +struct { |
| 50 | + auto operator()(int x, int y) const { |
| 51 | + return x + y; |
| 52 | + } |
| 53 | +} add; |
| 54 | +``` |
| 55 | +This also means that even identical lambda expressions always have completely different types, and are not interconvertible: |
| 56 | +```cpp |
| 57 | +auto foo = [](int x) { return x + 1; }; |
| 58 | +auto bar = [](int x) { return x + 1; }; |
| 59 | + |
| 60 | +static_assert(not std::same_as<decltype(foo), decltype(bar)>); |
| 61 | +``` |
| 62 | +
|
| 63 | + |
| 64 | +
|
| 65 | +### Capture list |
| 66 | +All lambda expressions begin with a capture list. The capture list specifies which variables to capture from the surrounding scope: |
| 67 | +```cpp |
| 68 | +int x = 10; |
| 69 | +
|
| 70 | +auto lambda = [x](int y) { return x + y; }; |
| 71 | +
|
| 72 | +lambda(5) // 15 |
| 73 | +``` |
| 74 | +- `[]` - Empty capture list. |
| 75 | +- `[=]` - Automatically capture variables that are used in the lambda body by value. |
| 76 | + - Mutually exclusive with `[&]`. |
| 77 | + - Does not implicitly capture `this` if used in a class. |
| 78 | +- `[&]` - Automatically capture variables that are used in the lambda body by reference. |
| 79 | + - Mutually exclusive with `[=]`. |
| 80 | +- `[x]` - Capture only `x` by value. |
| 81 | +- `[&x]` - Capture only `x` by reference. |
| 82 | +- `[x...]` - Capture a pack `x` by value. |
| 83 | +- `[&x...]` - Capture a pack `x` by reference. |
| 84 | +- `[this]` - Capture `*this` by reference. |
| 85 | +- `[x = y]` - Define a local variable `x` and initialize it to `y`. |
| 86 | + |
| 87 | +Different captures can be mixed together: |
| 88 | +```cpp |
| 89 | +int a = 1; |
| 90 | +int b = 2; |
| 91 | +int c = 3; |
| 92 | + |
| 93 | +auto foo = [=, &b, d = c]() { |
| 94 | + // `a` is implicitly captured by value, |
| 95 | + // `b` is explicitly captured by reference, and |
| 96 | + // `d` is initialized to `c`. |
| 97 | + return a + b + d; |
| 98 | +}; |
| 99 | +``` |
| 100 | +However, `[=]` may only be followed by captures by reference and `[&]` may only be followed by captures by value. |
| 101 | + |
| 102 | +Implicit captures can be nested multiple times: |
| 103 | +```cpp |
| 104 | +int x = 0; |
| 105 | + |
| 106 | +[&]() { |
| 107 | + [&]() { |
| 108 | + x = 5; |
| 109 | + }(); |
| 110 | +}(); |
| 111 | +``` |
| 112 | + |
| 113 | + |
| 114 | + |
| 115 | +### Parameters |
| 116 | +Lambda parameters work the same way as normal function parameters, and `auto` parameters make a lambda's `operator()` implicitly templated. |
| 117 | + |
| 118 | +If a lambda takes no parameters, the parameter list may be omitted entirely: |
| 119 | +```cpp |
| 120 | +auto very_important_number = [] { return 4; }; |
| 121 | +``` |
| 122 | +
|
| 123 | +Finally, the explicit `this` parameter can also be in lambdas since C++23: |
| 124 | +```cpp |
| 125 | +auto fibonacci = [](this auto self, int n) { |
| 126 | + if (n < 2) return n; |
| 127 | + return self(n - 1) + self(n - 2); |
| 128 | +}; |
| 129 | +``` |
| 130 | + |
| 131 | + |
| 132 | + |
| 133 | +### Return type |
| 134 | +A lambda's return type may be specified with an arrow: |
| 135 | +```cpp |
| 136 | +auto add = [](int x, int y) -> int { return x + y; }; |
| 137 | +``` |
| 138 | +Omitting the return type is the same as writing `-> auto`. |
| 139 | +
|
| 140 | + |
| 141 | +
|
| 142 | +### Specifiers |
| 143 | +- `constexpr` - Explicitly specifies that a lambda's `operator()` is a [constexpr function](https://en.cppreference.com/w/cpp/language/constexpr#constexpr_function). |
| 144 | + - Mutually exclusive with `consteval`. |
| 145 | + - Lambdas are implicitly marked `constexpr`, if possible. |
| 146 | +- `consteval` - Makes a lambda's `operator()` an [immediate function](https://en.cppreference.com/w/cpp/language/consteval). |
| 147 | + - Mutually exclusive with `constexpr`. |
| 148 | +- `static` - Makes a lambda's `operator()` a [static member function](https://en.cppreference.com/w/cpp/language/static#Static_member_functions). |
| 149 | + - Mutually exclusive with `mutable`. |
| 150 | + - Cannot be used if the captures list is not empty, or an explicit `this` parameter is present. |
| 151 | +- `mutable` - Allows the body of the lambda to modify the variables captured by value. |
| 152 | + - Mutually exclusive with `static`. |
| 153 | + - Cannot be used if an explicit `this` parameter is present. |
| 154 | +```cpp |
| 155 | +auto next = [i = 0] mutable { return i++; }; |
| 156 | +
|
| 157 | +next() // 0 |
| 158 | +next() // 1 |
| 159 | +next() // 2 |
| 160 | +``` |
| 161 | +These may be followed by a `noexcept` specifier, to determine whether calling the lambda may throw an exception. |
| 162 | + |
| 163 | + |
| 164 | + |
| 165 | +### Template parameters |
| 166 | +A lambda's `operator()` may be templated to accept template parameters since C++20: |
| 167 | +```cpp |
| 168 | +auto lambda = []<typename T>(const T& x) { |
| 169 | + return x; |
| 170 | +}; |
| 171 | + |
| 172 | +// Deduce template argument from function argument |
| 173 | +lambda(5); |
| 174 | + |
| 175 | +// Pass template argument explicitly |
| 176 | +lambda.template operator()<double>(5); |
| 177 | +``` |
| 178 | +
|
| 179 | + |
| 180 | +
|
| 181 | +### Attributes |
| 182 | +Lambdas may be given attributes that apply to their `operator()`s since C++23: |
| 183 | +```cpp |
| 184 | +auto very_important_number = [][[nodiscard]] { return 4; }; |
| 185 | +``` |
| 186 | +These attributes are placed right after (optional) template parameters. |
| 187 | + |
| 188 | +For details, see [cppreference](https://en.cppreference.com/w/cpp/language/lambda). |
| 189 | + |
| 190 | + |
| 191 | + |
| 192 | + |
| 193 | + |
| 194 | +## Notes |
| 195 | +### Inheritance |
| 196 | +Lambdas can be derived from: |
| 197 | +```cpp |
| 198 | +auto base = [] { std::puts("Hello, world!"); }; |
| 199 | + |
| 200 | +struct : decltype(base) {} derived; |
| 201 | + |
| 202 | +derived(); // prints "Hello, world!" |
| 203 | +``` |
| 204 | +This, in combination with multiple inheritance, allows for a very neat "visitor" class: |
| 205 | +```cpp |
| 206 | +template<typename... Fs> |
| 207 | +struct visitor : Fs... { |
| 208 | + using Fs::operator()...; |
| 209 | +}; |
| 210 | +
|
| 211 | +visitor { |
| 212 | + [](int) { std::puts("int"); }, |
| 213 | + [](double) { std::puts("double"); }, |
| 214 | + [](...) { std::puts("unknown"); } |
| 215 | +}(5); // prints "int" |
| 216 | +``` |
| 217 | + |
| 218 | + |
| 219 | + |
| 220 | +### Capturing function parameters |
| 221 | +While you can use a function's parameters in its `noexcept` specifier and trailing `requires` clause, using a lambda there which captures the function's parameters is invalid: |
| 222 | +```cpp |
| 223 | +// Fine |
| 224 | +void f(int x) noexcept(noexcept(x)) {} |
| 225 | + |
| 226 | +// Invalid |
| 227 | +void f(int x) noexcept(noexcept([x] { x; })) {} |
| 228 | +``` |
| 229 | +This is because the lambda is technically not in function or class scope, and thus cannot have captures. See [expr.prim.lambda.capture#3](https://timsong-cpp.github.io/cppwp/n4950/expr.prim.lambda.capture#3). This can sometimes be worked around using a `requires` expression: |
| 230 | +```cpp |
| 231 | +void f(int x) noexcept(requires(decltype(x) x) { |
| 232 | + requires noexcept([x] { x; }); |
| 233 | +}) {} |
| 234 | +``` |
| 235 | + |
| 236 | + |
| 237 | + |
| 238 | +### Type aliases |
| 239 | +Making a type alias with an in-line lambda in a header file or module interface violates the [One-Definition Rule](https://en.cppreference.com/w/cpp/language/definition) because aliases are not a "definable item", so lambdas in them are not allowed to match with other lambda declarations in other [translation units](https://en.cppreference.com/w/cpp/language/translation_phases#Translation_process): |
| 240 | +```cpp |
| 241 | +// Bad |
| 242 | +using T = decltype([] {}); |
| 243 | + |
| 244 | +// Bad |
| 245 | +template<auto> struct A {}; |
| 246 | +using T = A<[] {}>; |
| 247 | + |
| 248 | +// Ok |
| 249 | +auto lambda = [] {}; |
| 250 | +using T = decltype(lambda); |
| 251 | +``` |
| 252 | +
|
| 253 | + |
| 254 | +
|
| 255 | +### In-line partial specialization |
| 256 | +Partial specialization usually requires writing out a struct in namespace scope: |
| 257 | +```cpp |
| 258 | +template<typename> |
| 259 | +struct return_type; |
| 260 | +
|
| 261 | +template<typename Return, typename... Args> |
| 262 | +struct return_type<Return(Args...)> { |
| 263 | + using type = Return; |
| 264 | +}; |
| 265 | +
|
| 266 | +return_type<int(char)>::type // int |
| 267 | +``` |
| 268 | +However, the fact that lambdas can be invoked immediately when they are defined allows doing the same work completely in-line: |
| 269 | +```cpp |
| 270 | +decltype( |
| 271 | + []<typename Return, typename... Args>(std::type_identity<Return(Args...)>) { |
| 272 | + return std::type_identity<Return>(); |
| 273 | + }(std::type_identity<int(char)>()) |
| 274 | +)::type // int |
| 275 | +``` |
| 276 | +Here, `T` and `Return` are wrapped in `std::type_identity` objects to pass them around without actually constructing the types they hold. |
| 277 | + |
| 278 | +A similar trick is useful in concepts: |
| 279 | +```cpp |
| 280 | +template<typename T, template<typename...> typename Template> |
| 281 | +concept specialization_of = requires { |
| 282 | + []<typename... Args>(std::type_identity<Template<Args...>>) {}(std::type_identity<T>()); |
| 283 | +}; |
| 284 | + |
| 285 | +static_assert(specialization_of<std::tuple<int>, std::tuple>); |
| 286 | +``` |
| 287 | +and with `std::integer_sequence`: |
| 288 | +```cpp |
| 289 | +[]<std::size_t... i>(std::index_sequence<i...>) { |
| 290 | + (..., std::print("{} ", i)); |
| 291 | +}(std::make_index_sequence<5>()); |
| 292 | +// prints "0 1 2 3 4" |
| 293 | +``` |
0 commit comments