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 lambdaSpecifiers
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-overheadImmediately 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