Monday, October 5, 2026

 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