Lambdas let a C++ program express behaviour inline: an unnamed function with a parameter list, a body and a capture list that says which surrounding variables it can see. They replace most function objects and almost every function pointer, and they are the reason the standard algorithms can take a custom comparison as an argument.
The shape of a lambda
A lambda has three parts: the capture list in square brackets, the parameter list in parentheses, and the body.
#include
#include
#include
int main() {
std::vector weights = {4, 30, 12, 8};
std::sort(weights.begin(), weights.end(),
[](int left, int right) { return left > right; });
for (int weight : weights) {
std::cout << weight << ' ';
}
std::cout << '\n';
return 0;
}The output shows the vector sorted in descending order:
30 12 8 4With an empty capture list the lambda sees nothing from the surrounding scope. That is the safest default, and it is why the lambda above compiles without qualification: the parameter types are written out.
Capturing by value and by reference
The capture list says which outside variables the lambda may use, and how.
- [[]] — capture nothing.
=— capture everything used, by value.&— capture everything used, by reference.name— capture this one by value.&name— capture this one by reference.this— capture the enclosing object's pointer.
#include
#include
int main() {
int threshold = 15;
std::vector weights = {4, 30, 12, 8};
auto heavy_by_value = [threshold](int weight) {
return weight > threshold;
};
auto heavy_by_reference = [&threshold](int weight) {
return weight > threshold;
};
threshold = 5;
std::cout << "by value: " << heavy_by_value(12) << '\n';
std::cout << "by reference: " << heavy_by_reference(12) << '\n';
return 0;
}The output shows the difference: the by-value lambda kept the old threshold of 15 and returned false, while the by-reference lambda saw the new value of 5 and returned true.
by value: 0
by reference: 1The general rule is simple: capture by reference when the lambda should see later changes, and by value when it should not.
Returning from a lambda
A lambda returns whatever its body returns, and the compiler deduces the type. When the branches return different types, you need an explicit return type.
#include
#include
#include
int main() {
auto describe = [](int weight) -> std::string {
return weight > 20 ? "heavy" : "light";
};
std::cout << describe(30) << '\n';
std::cout << describe(4) << '\n';
// With an explicit return type, different branches are allowed.
auto mixed = [](int weight) -> double {
if (weight > 20) {
return 1; // converts to double
}
return 0.5;
};
std::cout << mixed(30) << '\n';
return 0;
}The output shows the lambda returning a string and then a double:
heavy
light
1The -> std::string and -> double are optional when the types agree, and necessary when they do not.
Capturing this in a member function
Inside a member function, this captures the enclosing object, so the lambda can call its member functions.
#include
#include
#include
class Warehouse {
public:
explicit Warehouse(std::vector parcels)
: parcels_(std::move(parcels)) {}
void report() const {
auto describe = [this]() {
std::cout << "holds " << parcels_.size() << " parcels\n";
};
describe();
}
private:
std::vector parcels_;
};
int main() {
Warehouse warehouse({"p-001", "p-002"});
warehouse.report();
return 0;
}The output shows the lambda reaching the member variable:
holds 2 parcelsThe explicit [this] capture is the default in C++20, but writing it in C++17 makes the intent obvious and silences a warning that some compilers emit.
Lambdas and function pointers
A lambda with no capture can be converted to a function pointer, which is what lets you pass one to a C-style API.
#include
#include
#include
int apply_discount(int price) {
return price * 9 / 10;
}
int main() {
std::vector prices = {100, 200};
auto lambda = [](int price) { return price * 9 / 10; };
int (*function_pointer)(int) = lambda;
std::cout << "lambda: " << lambda(100) << '\n';
std::cout << "pointer: " << function_pointer(100) << '\n';
std::cout << "function: " << apply_discount(100) << '\n';
return 0;
}The output shows all three producing the same result:
lambda: 90
pointer: 90
function: 90A lambda that captures something cannot be converted to a function pointer, because a function pointer has nowhere to put the captured state.
When to use a lambda
Lambdas are the right tool when the behaviour is short, local, and used in one place — sorting with a custom comparator, checking a predicate, transforming a range. When the behaviour is long, reused across many places, or needs a state of its own, a named class is better.
Common lambda mistakes
- Capturing a local by reference and outliving it. The lambda then refers to a destroyed object. Capture by value instead.
- Forgetting that a by-value capture copies. Modifying the captured copy inside the lambda does not change the original.
- Implicit capture of the enclosing object in a lambda that outlives it. The same dangling problem, one level up.
- Expecting a lambda to be mutable. By default the captured objects are const inside the lambda; add
mutableto change that.
Quick reference
- A lambda is [[capture list](parameters) { body }]].
- Capture nothing with [[]], everything by value with
=, everything by reference with&. - Capture by reference to see later changes; capture by value to insulate the lambda.
- The return type is deduced, but can be written with
-> Type. - A captureless lambda converts to a function pointer; one with captures does not.
- Use a named class when the behaviour is long or reused.
Try it yourself: write a lambda that takes a weight and a threshold and returns whether the weight exceeds it, capturing the threshold by value and then by reference, and call both after changing the threshold. Next, use a lambda with [[this]] inside a class method to report a member. Finally, pass a captureless lambda to a function that expects a function pointer.