Skip to content
C++

Lambdas Quick Reference

C++ lambda cheat sheet — capture lists, generic lambdas, immediately invoked lambdas, mutable, constexpr, recursive, and C++23 features.

Syntax

cpp
[ capture-list ] ( params ) specifiers -> return-type { body }

All parts except the body and capture list are optional.


Capture modes

cpp
int x = 1, y = 2;

// Capture nothing
auto f0 = []() { return 42; };

// Capture by value (copies at point of lambda definition)
auto f1 = [x] { return x + 1; };    // x captured by copy
auto f2 = [=] { return x + y; };    // all local vars by copy

// Capture by reference (live reference to original variable)
auto f3 = [&x] { return x + 1; };   // x by ref
auto f4 = [&]  { return x + y; };   // all locals by ref

// Mixed
auto f5 = [=, &y] { return x + y; };  // x by copy, y by ref
auto f6 = [&, x]  { return x + y; };  // x by copy, all others by ref

// Capture with initializer (C++14)
auto f7 = [z = x + y] { return z; };       // z initialized to x+y
auto f8 = [p = std::move(ptr)] { return *p; };  // move into lambda

Specifiers

cpp
int n = 0;

// mutable — allow modifying captured copies
auto f = [n]() mutable { ++n; return n; };  // n's copy modified; original unchanged

// constexpr — usable in constant expressions (C++17, implicit when possible)
auto square = [](int x) constexpr { return x * x; };
constexpr int s = square(5);  // 25 — evaluated at compile time

// noexcept — assert lambda won't throw
auto safe = [](int x) noexcept { return x * 2; };

// [[nodiscard]] attribute — warn if return value discarded
auto make_val = []() [[nodiscard]] { return 42; };

Return type

cpp
// Deduced (single-expression or unified return type)
auto f1 = [](int x) { return x * 2; };       // deduced: int
auto f2 = [](int x) -> double { return x; }; // explicit: double

// Required for complex bodies with multiple returns
auto f3 = [](int x) -> std::optional<int> {
    if (x < 0) return {};
    return x * 2;
};

Generic lambdas (C++14)

cpp
// auto parameters = template function operator()
auto print = [](const auto& x) { std::println("{}", x); };
print(42);
print("hello");
print(3.14);

// Multiple auto params
auto add = [](auto a, auto b) { return a + b; };
add(1, 2);      // int
add(1.0, 2.0);  // double

// C++20: explicit template parameters
auto max_fn = []<typename T>(T a, T b) { return a > b ? a : b; };

// C++20: constrained generic lambda
auto print_range = []<std::ranges::range R>(const R& r) {
    for (const auto& v : r) std::print("{} ", v);
};

Recursive lambda (C++23)

cpp
// C++23: deducing this — self is the lambda itself
auto fib = [](this auto self, int n) -> int {
    if (n <= 1) return n;
    return self(n-1) + self(n-2);
};
fib(10);  // 55

// C++14/17: explicit recursion via reference capture
std::function<int(int)> fib14 = [&fib14](int n) -> int {
    if (n <= 1) return n;
    return fib14(n-1) + fib14(n-2);
};
// Note: std::function has overhead; C++23 deducing this is zero-overhead

Immediately invoked lambda (IIFE)

cpp
// Initialize const from complex logic
const int value = []{
    int x = expensive_compute();
    x = transform(x);
    return x;
}();  // immediately invoked

// Useful for: switch-case in initializer, complex conditional init
const std::string mode = [&]{
    switch (config.mode) {
    case Mode::Fast:  return "fast";
    case Mode::Safe:  return "safe";
    default:          return "unknown";
    }
}();

Storing lambdas

cpp
// std::function — type-erased, may heap-allocate
std::function<int(int)> f = [](int x) { return x * 2; };

// auto — zero overhead, but must be in same scope
auto g = [](int x) { return x * 2; };

// Template parameter — zero overhead, works across scopes
template<typename F>
void apply(F fn, int x) { fn(x); }

// C++26: std::function_ref — non-owning, zero overhead
void apply26(std::function_ref<int(int)> fn, int x) { fn(x); }

// std::move_only_function (C++23) — for move-only captures
auto fn = [p = std::make_unique<int>(42)](){ return *p; };
std::move_only_function<int()> movable = std::move(fn);

Lambda in algorithms

cpp
std::vector<int> v = {3, 1, 4, 1, 5, 9};

// Sort descending
std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });

// Filter with copy_if
std::vector<int> evens;
std::copy_if(v.begin(), v.end(), std::back_inserter(evens),
    [](int n) { return n % 2 == 0; });

// Transform
std::transform(v.begin(), v.end(), v.begin(),
    [factor = 2](int x) { return x * factor; });

// Remove-erase
v.erase(std::remove_if(v.begin(), v.end(),
    [threshold = 3](int x) { return x < threshold; }),
    v.end());
// C++20:
std::erase_if(v, [](int x) { return x < 3; });

Common pitfalls

cpp
// 1. Dangling reference capture
auto bad = [&local_str]{ return local_str.size(); };  // local_str gone after return!

// 2. Capturing this
struct Obj {
    int data_ = 42;
    auto make_fn() {
        return [this]{ return data_; };  // captures raw this — dangerous if Obj destroyed
        // C++17: capture by value:
        return [*this]{ return data_; };  // copies the Obj into lambda — safe
    }
};

// 3. Modifying captured copy without mutable
int x = 0;
auto f = [x]{ ++x; };   // ERROR: x is const in lambda by default
auto g = [x]() mutable { ++x; };  // OK — modifies lambda's copy

// 4. std::function overhead for hot paths
// Use auto or templates for lambdas in tight loops
for (int i = 0; i < 1e6; ++i)
    std::function<int(int)>{[](int x){ return x*2; }}(i);  // heap alloc each iteration!
// Fix: hoist the lambda out or use auto/template
Edit on GitHub