Lambda Expressions¶
Anonymous function objects with capture semantics. Core tool for callbacks, algorithms, and functional patterns.
Key Facts¶
- Syntax:
[capture](params) -> return_type { body } [=]captures all locals by value,[&]captures all by reference[x]capture by value,[&x]capture by reference,[x = std::move(y)]init capture (C++14)- Lambdas are syntactic sugar for compiler-generated closure types (unnamed
structwithoperator()) - Each lambda has a unique type - use
std::function<>orautoto store autolambda parameters (C++14) make generic/polymorphic lambdasmutablekeyword allows modifying captured-by-value variablesconstexprlambdas (C++17) - usable in compile-time contexts- Template lambdas (C++20):
[]<typename T>(T x) { ... } - Lambdas with no capture can convert to function pointer
- Prefer lambda over
std::bind- clearer, more efficient, better optimized
Patterns¶
Capture Modes¶
int x = 10;
std::string name = "test";
auto by_val = [x]() { return x; }; // copy of x
auto by_ref = [&x]() { return x; }; // reference to x
auto all_val = [=]() { return x + name.size(); }; // all by value
auto all_ref = [&]() { x++; }; // all by reference
auto mixed = [&x, name]() { x += name.size(); }; // mix
// Init capture (C++14) - move into lambda
auto moved = [s = std::move(name)]() { return s.size(); };
// Capture this
struct Widget {
int value_ = 42;
auto get_printer() {
return [this]() { std::cout << value_; }; // captures this ptr
// return [*this]() { std::cout << value_; }; // C++17: copies *this
}
};
Generic Lambdas¶
// C++14: auto parameters
auto add = [](auto a, auto b) { return a + b; };
add(1, 2); // int
add(1.5, 2.5); // double
add("a"s, "b"s); // string
// C++20: template syntax for constraints
auto print = []<typename T>(const std::vector<T>& v) {
for (const auto& x : v) std::cout << x << ' ';
};
With STL Algorithms¶
std::vector<int> v = {5, 3, 1, 4, 2};
// Sort with custom comparator
std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });
// Find first matching
auto it = std::find_if(v.begin(), v.end(), [](int x) { return x > 3; });
// Transform
std::vector<std::string> result;
std::transform(v.begin(), v.end(), std::back_inserter(result),
[](int x) { return std::to_string(x); });
// Accumulate with lambda
auto product = std::accumulate(v.begin(), v.end(), 1,
[](int acc, int x) { return acc * x; });
Immediately Invoked Lambda (IIFE)¶
// Complex initialization
const auto config = [&] {
Config c;
c.width = parse_int(args[1]);
c.height = parse_int(args[2]);
c.title = args[3];
return c;
}(); // note: immediately invoked
// Conditional const initialization
const int value = [&] {
if (mode == Mode::Fast) return compute_fast();
else return compute_accurate();
}();
Recursive Lambda¶
// C++23: deducing this for recursion
auto fib = [](this auto self, int n) -> int {
if (n <= 1) return n;
return self(n-1) + self(n-2);
};
// Pre-C++23: use std::function
std::function<int(int)> fib = [&fib](int n) -> int {
if (n <= 1) return n;
return fib(n-1) + fib(n-2);
};
Gotchas¶
- Issue: Capturing local by reference, lambda outlives the local -> dangling reference, UB -> Fix: Capture by value, or use init capture to move ownership into lambda
- Issue:
[=]capturesthispointer by value (the pointer, not the object) -> still dangling if object destroyed -> Fix: Use[*this](C++17) to capture object copy, or ensure lifetime - Issue:
std::function<>has overhead (type erasure, heap allocation) -> Fix: Useautowhen possible;std::functiononly when type-erased storage is needed - Issue: Lambda modifying captured-by-value variable won't compile -> Fix: Add
mutable:[x]() mutable { x++; }