Here’s a C++ lambda syntax cheat sheet from basics → advanced, with the pieces building up progressively.
1. Basic lambda
cpp
[]() {
std::cout << "Hello";
};
General syntax:
cpp
[capture](parameters) -> return_type {
// body
};
The -> return_type is usually optional because C++ can deduce it.
cpp
[]() {
return 10;
};
───
2. Store a lambda in a variable
cpp
auto add = [](int a, int b) {
return a + b;
};
std::cout << add(2, 3); // 5
Think of a lambda as an unnamed function object.
───
3. Parameters
cpp
auto multiply = [](int a, int b) {
return a * b;
};
multiply(3, 4);
Explicit return type:
cpp
auto divide = [](int a, int b) -> double {
return static_cast<double>(a) / b;
};
───
4. Capture list []
The capture list controls which outside variables the lambda can access.
cpp
int x = 10;
auto f = []() {
// std::cout << x; // ❌ x not captured
};
Capture x by value:
cpp
int x = 10;
auto f = [x]() {
std::cout << x;
};
Capture x by reference:
cpp
int x = 10;
auto f = [&x]() {
x = 20;
};
───
5. Capture everything
Capture everything by value
cpp
int x = 10;
int y = 20;
auto f = [=]() {
std::cout << x << y;
};
Capture everything by reference
cpp
int x = 10;
int y = 20;
auto f = [&]() {
x++;
y++;
};
Mix value and reference
cpp
int x = 10;
int y = 20;
auto f = [x, &y]() {
// x → copied
// y → referenced
};
You can also write:
cpp
[x, &y]
───
6. Mutable lambda
This is an important concept.
By default, variables captured by value cannot be modified inside the lambda:
cpp
int x = 10;
auto f = [x]() {
// x++; // ❌
};
Use mutable:
cpp
int x = 10;
auto f = [x]() mutable {
x++;
std::cout << x;
};
f(); // 11
f(); // 12
std::cout << x; // 10
Notice:
text
Original x = 10
Lambda's copy = 10 → 11 → 12
mutable changes the lambda's captured copy, not the original variable.
───
7. Lambda with no parameters
cpp
auto hello = [] {
std::cout << "Hello";
};
The () can be omitted when there are no parameters.
Equivalent:
cpp
[]() {
std::cout << "Hello";
};
───
8. Lambda with return type
Usually:
cpp
auto f = [](int x) {
return x * 2;
};
Compiler deduces int.
Explicit:
cpp
auto f = [](int x) -> int {
return x * 2;
};
Useful when deduction is problematic or when you want to be explicit.
───
9. Lambda passed to an algorithm
This is where lambdas become extremely useful.
cpp
std::vector<int> v = {1, 2, 3, 4, 5};
std::for_each(v.begin(), v.end(), [](int x) {
std::cout << x << " ";
});
Sorting:
cpp
std::sort(v.begin(), v.end(), [](int a, int b) {
return a > b;
});
Result:
text
5 4 3 2 1
───
10. Lambda with std::find_if
cpp
auto it = std::find_if(
v.begin(),
v.end(),
[](int x) {
return x > 10;
}
);
The lambda acts as a predicate.
───
11. Generic lambda — C++14
Instead of specifying parameter types:
cpp
auto print = [](auto x) {
std::cout << x;
};
print(10);
print(3.14);
print("Hello");
The lambda effectively behaves like a function template.
Conceptually:
text
print(int)
print(double)
print(const char*)
───
12. Generic lambda with multiple parameters
cpp
auto add = [](auto a, auto b) {
return a + b;
};
add(10, 20);
add(2.5, 3.5);
───
13. Generic lambda with forwarding references
More advanced:
cpp
auto f = [](auto&& x) {
// x can bind to lvalue or rvalue
};
This is commonly used when writing generic code.
For perfect forwarding:
cpp
auto f = [](auto&& x) {
some_function(std::forward<decltype(x)>(x));
};
───
14. Lambda returning a lambda
Yes, lambdas can return lambdas.
cpp
auto createAdder = [](int x) {
return [x](int y) {
return x + y;
};
};
auto add10 = createAdder(10);
std::cout << add10(5); // 15
Here:
text
createAdder(10)
↓
lambda capturing x = 10
↓
add10(5)
↓
15
───
15. Immediately Invoked Lambda — IIFE
You can create and immediately execute a lambda:
cpp
int result = [](int a, int b) {
return a + b;
}(10, 20);
result becomes 30.
This:
cpp
[](int a, int b) {
return a + b;
}(10, 20);
means:
Create lambda → immediately call it with 10, 20.
───
16. Capture this
Inside a member function:
cpp
class A {
int value = 10;
public:
void foo() {
auto f = [this]() {
std::cout << value;
};
f();
}
};
[this] captures the this pointer.
You can therefore access:
cpp
value
which is essentially:
cpp
this->value
───
17. Capture *this — C++17
This is different.
cpp
auto f = [*this]() {
std::cout << value;
};
[*this] captures a copy of the object.
Compare:
cpp
[this]
with:
cpp
[*this]
Capture
Captures
[this]
this pointer
[*this]
copy of the object
This distinction becomes important when the lambda outlives the object.
───
18. Init capture — C++14
You can create a new variable inside the capture list:
cpp
int x = 10;
auto f = [y = x + 5]() {
std::cout << y;
};
y is a new variable belonging to the lambda.
Very useful for moving objects:
cpp
auto ptr = std::make_unique<int>(10);
auto f = [p = std::move(ptr)]() {
std::cout << *p;
};
Now ownership of the unique_ptr has been moved into the lambda.
───
19. Generalized lambda capture
You can have multiple init captures:
cpp
int x = 10;
auto f = [
a = x + 1,
b = x * 2
]() {
std::cout << a << b;
};
───
20. constexpr lambda
Modern C++ allows constexpr lambdas:
cpp
constexpr auto square = [](int x) {
return x * x;
};
constexpr int result = square(5);
result can be evaluated at compile time.
───
21. Lambda with constraints — C++20
Generic lambda:
cpp
auto add = [](auto a, auto b) {
return a + b;
};
You can constrain it:
cpp
auto add = []<typename T>(T a, T b) {
return a + b;
};
With concepts:
cpp
auto add = []<std::integral T>(T a, T b) {
return a + b;
};
Now the lambda only accepts integral types.
This <typename T> syntax is called a template parameter list for the lambda and was introduced in C++20.
───
22. Lambda conversion to function pointer
A lambda with no captures can convert to a function pointer:
cpp
auto f = [](int x) {
return x * 2;
};
int (*ptr)(int) = f;
std::cout << ptr(5);
But this doesn't work for a capturing lambda:
cpp
int x = 10;
auto f = [x](int y) {
return x + y;
};
// int (*ptr)(int) = f; // ❌
Why?
Because the lambda needs stored state (x).
───
23. std::function
You can store lambdas in std::function:
cpp
std::function<int(int, int)> add =
[](int a, int b) {
return a + b;
};
std::cout << add(2, 3);
This is useful when you need a common callable type.
But std::function has some overhead, so don't automatically use it everywhere.
───
24. Lambda as a comparator
Very common in interviews:
cpp
std::sort(v.begin(), v.end(),
[](int a, int b) {
return a < b;
});
For objects:
cpp
std::sort(students.begin(), students.end(),
[](const Student& a, const Student& b) {
return a.age < b.age;
});
───
25. Recursive lambda
A lambda cannot simply refer to itself by its own variable during its initialization:
cpp
// ❌
auto factorial = [](int n) {
return n * factorial(n - 1);
};
One common solution is std::function:
cpp
std::function<int(int)> factorial =
[&](int n) {
if (n <= 1)
return 1;
return n * factorial(n - 1);
};
Modern C++ also allows more efficient patterns using an explicit self parameter:
cpp
auto factorial = [](this auto&& self, int n) {
if (n <= 1)
return 1;
return n * self(n - 1);
};
The explicit object parameter form above is C++23.
───
26. The full syntax to remember
The most useful mental model is:
cpp
[captures] <template_params> (parameters)
mutable
constexpr
noexcept
-> return_type
{
body
}
Not every part is required.
For example:
cpp
auto f =
[x, &y] // capture
<typename T> // template parameters (C++20)
(T value) // parameters
mutable // mutable
noexcept // noexcept
-> T // return type
{
// body
};
───
The progression I'd memorize
text
[]() { }
↓
[](int x) { }
↓
[x](int y) { }
↓
[&x](int y) { }
↓
[=](int y) { }
↓
[&](int y) { }
↓
[x]() mutable { }
↓
[x = std::move(obj)]() { }
↓
[](auto x) { } // C++14
↓
[*this]() { } // C++17
↓
[]<typename T>(T x) { } // C++20
↓
[](this auto&& self) { } // C++23
If you're preparing for a C++ interview, the most important lambda topics are: capture by value/reference, mutable, this vs *this, init-capture, generic lambdas, lambda-to-function-pointer conversion, std::function, and using lambdas with STL algorithms.
No comments:
Post a Comment