Skip to content

Commit a627f20

Browse files
authored
Create src/resources/cpp/lambdas.md
1 parent 872e5d8 commit a627f20

File tree

1 file changed

+293
-0
lines changed

1 file changed

+293
-0
lines changed

src/resources/cpp/lambdas.md

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
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+
&nbsp;
29+
30+
&nbsp;
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+
&nbsp;
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+
&nbsp;
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+
&nbsp;
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+
&nbsp;
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+
&nbsp;
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+
&nbsp;
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+
&nbsp;
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+
&nbsp;
191+
192+
&nbsp;
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+
&nbsp;
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+
&nbsp;
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+
&nbsp;
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

Comments
 (0)