Lambda Expressions
From C++11 basics to C++23 enhancements
Lambda Basics
Lambdas are unnamed function objects that can capture variables from their enclosing scope.
// Basic lambda syntax
[capture](parameters) -> return_type { body }
// Examples:
auto add = [](int a, int b) { return a + b; };
auto square = [](double x) -> double { return x * x; };
// With capture
int offset = 10;
auto add_offset = [offset](int x) { return x + offset; };Capture Modes
int x = 1, y = 2;
// [] - No capture
auto f1 = []() { return 42; };
// [=] - Capture all by value
auto f2 = [=]() { return x + y; }; // x and y copied
// [&] - Capture all by reference
auto f3 = [&]() { x++; y++; }; // Modifies originals
// [x] - Capture specific by value
auto f4 = [x]() { return x; }; // x copied
// [&x] - Capture specific by reference
auto f5 = [&x]() { x++; }; // Modifies x
// Mixed: [x, &y] - x by value, y by reference
auto f6 = [x, &y]() { y = x; };
// [this] - Capture current object by pointer
class MyClass {
int value_;
void method() {
auto lambda = [this]() { return value_; };
}
};
// C++17: [*this] - Capture current object by value
void MyClass::safe_method() {
auto lambda = [*this]() { return value_; }; // Copy of *this
}Generic Lambdas (C++14)
// C++14: Auto parameters
auto add = [](auto a, auto b) { return a + b; };
add(1, 2); // int
add(1.5, 2.5); // double
add(std::string("a"), "b"); // string
// Multiple auto parameters (can be different types)
auto multiply = [](auto x, auto y) { return x * y; };
multiply(2, 3.14); // int * double = double
// With trailing return type
auto divide = [](auto a, auto b) -> double {
return static_cast<double>(a) / b;
};Mutable Lambdas
// Without mutable: capture by value is const
int counter = 0;
auto bad_increment = [counter]() {
// counter++; // ERROR: counter is const
return counter;
};
// With mutable: can modify captured values
auto good_increment = [counter]() mutable {
return counter++; // OK: modifies copy
};
good_increment(); // Returns 0, internal counter is 1
good_increment(); // Returns 1, internal counter is 2
// Original 'counter' variable is still 0!Stateful Lambdas
// Lambdas can maintain state between calls
auto make_counter = [](int start = 0) {
int count = start;
return [count]() mutable {
return count++;
};
};
auto counter1 = make_counter(10);
auto counter2 = make_counter(100);
counter1(); // 10
counter1(); // 11
counter2(); // 100 (independent state)
// Custom comparator with state
auto make_case_insensitive_comparator = []() {
return [](const std::string& a, const std::string& b) {
return std::lexicographical_compare(
a.begin(), a.end(), b.begin(), b.end(),
[](char c1, char c2) {
return std::tolower(c1) < std::tolower(c2);
}
);
};
};Template Lambdas (C++20)
// C++20: Explicit template parameters
auto lambda = []<typename T>(T value) {
return value * 2;
};
// With concepts
auto process = []<std::floating_point T>(T value) {
return std::sqrt(value);
};
// Template parameter packs
auto print_tuple = []<typename... Args>(const std::tuple<Args...>& t) {
std::apply([](auto&&... args) {
((std::cout << args << " "), ...);
}, t);
};
// Constrained lambdas
auto sort = []<typename T>(std::vector<T>& v)
requires std::totally_ordered<T>
{
std::sort(v.begin(), v.end());
};Recursive Lambdas
// C++23: Deducing this for recursion
auto factorial = [](this auto&& self, int n) -> int {
if (n <= 1) return 1;
return n * self(n - 1);
};
// Pre-C++23 workaround with std::function (has overhead)
std::function<int(int)> fib = [&](int n) -> int {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
};
// Pre-C++23 workaround with Y-combinator pattern
auto make_recursive = [](auto f) {
return [f](auto&&... args) {
return f(f, std::forward<decltype(args)>(args)...);
};
};
auto factorial_old = make_recursive([](auto self, int n) -> int {
if (n <= 1) return 1;
return n * self(self, n - 1);
});Best Practices
- ✓Prefer [=] or [&] for simple captures, explicit list for complex
- ✓Use [*this] in C++17 when object may outlive lambda
- ✓Keep lambdas small and focused
- ✓Consider named functions for complex logic
- ✗Don't capture by reference if lambda outlives the captured variables
- ✗Avoid default capture [&] in long/complex lambdas (unclear what's captured)