A lambda expression is a shorthand notation for creating an unnamed callable object (also called a closure, or an anonymous function). A lambda can "capture" variables from its surrounding scope by value or by reference, allowing the body of the lambda to access or modify those variables without having to pass them as parameters. Unlike regular functions, lambdas are typically written in-line, combining the reusability of functions with direct access to local context. The lambda retains captured variables' state (for captures by value) or dynamically references them (for captures by reference), making lambdas ideal for short, context-dependent operations like custom comparisons, filters, or event handlers.
Example:
bool is_even(int x) {
return x % 2 == 0;
}
int main() {
std::vector<int> data = { 1, 3, 5, 10, 73 };
bool has_even = std::ranges::any_of(data, is_even);
}The above code can be rewritten as:
int main() {
std::vector<int> data = { 1, 3, 5, 10, 73 };
bool has_even = std::ranges::any_of(data, [](int x) { return x % 2 == 0; });
}The basic syntax of a lambda expression looks like this:
[captures](parameters) -> return_type {
statements;
}A more detailed explanation of the syntax can be found on cppreference.
All lambda expressions begin with a capture list. The capture list specifies which variables to capture from the surrounding scope:
int x = 10;
auto lambda = [x](int y) { return x + y; };
lambda(5) // 15[]- Empty capture list.[=]- Automatically capture variables that are used in the lambda body by value.- Mutually exclusive with
[&]. - Does not implicitly capture
this.
- Mutually exclusive with
[&]- Automatically capture variables that are used in the lambda body by reference.- Mutually exclusive with
[=]. - Implicitly captures
this.
- Mutually exclusive with
[x]- Capture onlyxby value.[&x]- Capture onlyxby reference.[this]- Capture*thisby reference.- Capturing
[&this]is invalid.
- Capturing
[x = y]- Define a local variablexand initialize it toy.[x...]- Capture a packxby value.[&x...]- Capture a packxby reference.
Different captures can be mixed together:
int a = 1;
int b = 2;
int c = 3;
[=, &b, d = c]() {
// `a` is implicitly captured by value,
// `b` is explicitly captured by reference, and
// `d` is initialized to `c`.
return a + b + d;
}However, [=] may not be followed by other explicit captures by copy and [&] may not be followed by other explicit captures by reference.
- Lambdas with implicit captures only capture what they actually use, so
[&]and[=]can be used without fear of bloat or additional overhead.
Implicit captures can be nested multiple times:
int x = 0;
[&]() {
[&]() {
x = 5;
}();
}();
x // 5If a lambda takes no parameters, the parameter list may be omitted entirely:
auto very_important_number = [] { return 4; };Lambda parameters work the same way as normal function parameters, and auto parameters make a lambda's operator() implicitly templated.
The explicit this parameter can also be used with lambdas since C++23, allowing easy recursion:
auto fibonacci = [](this auto self, int n) {
if (n < 2) return n;
return self(n - 1) + self(n - 2);
};A lambda's return type may be specified with an arrow:
auto add = [](int x, int y) -> int { return x + y; };Omitting the return type is the same as writing -> auto.
constexpr- Explicitly specifies that a lambda'soperator()is a constexpr function.- Mutually exclusive with
consteval. - Lambdas are implicitly marked
constexpr, if suitable.
- Mutually exclusive with
consteval- Makes a lambda'soperator()an immediate function.- Mutually exclusive with
constexpr.
- Mutually exclusive with
static- Makes a lambda'soperator()a static member function.- Mutually exclusive with
mutable. - Cannot be used if the captures list is not empty, or an explicit
thisparameter is present.
- Mutually exclusive with
mutable- Allows the body of the lambda to modify the variables captured by value.- Mutually exclusive with
static. - Cannot be used if an explicit
thisparameter is present.
- Mutually exclusive with
auto next = [i = 0] mutable { return i++; };
next() // 0
next() // 1
next() // 2These may be followed by a noexcept specifier, to determine whether calling the lambda may throw an exception.
A lambda's operator() may be templated to accept template parameters since C++20:
auto lambda = []<typename T>(const T& x) {
return x;
};
// Deduce template argument from function argument
lambda(5);
// Pass template argument explicitly
lambda.template operator()<double>(5);Lambdas may be given attributes that apply to their operator()s since C++23:
auto very_important_number = [] [[nodiscard]] { return 4; };For details, see cppreference.
Lambdas without captures can be converted to function pointers:
auto* ptr = +[] { return 4; };This can be done with or without a static specifier on the lambda.
Each lambda expression has its own unique, unnameable type:
auto add = [](int x, int y) { return x + y; };It is similar to defining a new unnamed class for every lambda:
struct {
auto operator()(int x, int y) const {
return x + y;
}
} add;This also means that identical lambda expressions always have different types:
auto foo = [](int x) { return x + 1; };
auto bar = [](int x) { return x + 1; };
static_assert(not std::same_as<decltype(foo), decltype(bar)>);Note that lambdas can not be converted to other lambda types.
Lambdas can be derived from:
auto base = [] { std::puts("Hello, world!"); };
struct : decltype(base) {} derived;
derived(); // prints "Hello, world!"This, in combination with multiple inheritance, allows for a very neat "visitor" class:
template<typename... Fs>
struct visitor : Fs... {
using Fs::operator()...;
};
visitor {
[](int) { std::puts("int"); },
[](double) { std::puts("double"); },
[](...) { std::puts("other"); }
}(0); // prints "int"While you can use a function's parameters in its noexcept specifier and trailing requires clause, using a lambda there which captures the function's parameters is invalid:
// Fine
void f(int x) noexcept(noexcept(x)) {}
// Invalid
void f(int x) noexcept(noexcept([x] { x; })) {}This is because the lambda is technically not in function or class scope, and thus cannot have captures (see expr.prim.lambda.capture#3).
Making a type alias containing a lambda expression in a header file or module interface violates the One-Definition Rule because aliases are not a "definable item", so lambdas in them are not allowed to match with other lambda declarations in other translation units:
// Bad
using T = decltype([] {});
// Bad
template<auto> struct A {};
using T = A<[] {}>;
// Ok
auto lambda = [] {};
using T = decltype(lambda);See CWG2988 about lambda expressions in concept definitions.
Usually, partial specialization requires declaring multiple structs in namespace scope:
using Function = int(char);
template<typename>
struct return_type;
template<typename Return, typename... Args>
struct return_type<Return(Args...)> {
using type = Return;
};
static_assert(std::same_as<int, return_type<Function>::type>);However, the same work can be done completely in-line with the help of an immediately-invoked lambda expression (IILE):
using Function = int(char);
static_assert(std::same_as<
int,
decltype([]<typename Return, typename... Args>(std::type_identity<Return(Args...)>) {
return std::type_identity<Return>();
}(std::type_identity<Function>()))::type
>);Here, types are wrapped in std::type_identity objects to pass them around without actually constructing Function, Return(Args...), or Return.
IILEs are also useful in concepts:
template<typename>
struct type {};
template<typename T, template<typename...> typename Template>
concept specialization_of = requires {
[]<typename... Args>(type<Template<Args...>>) {}(type<T>());
};
static_assert(specialization_of<std::vector<int>, std::vector>);and in combination with std::integer_sequence:
[]<std::size_t... i>(std::index_sequence<i...>) {
(..., std::print("{} ", i));
}(std::make_index_sequence<5>());
// prints "0 1 2 3 4"