Lambda expressions were introduced in C++11 to simplify the use of function objects, especially when passing custom logic to standard algorithms like std::sort.
Motivation for Lambda Expressions
In C++98, defining custom comparison logic required creating a separate functor class:
struct Product {
std::string name;
double price;
int rating;
};
struct CompareByPriceAsc {
bool operator()(const Product& a, const Product& b) const {
return a.price < b.price;
}
};
// Usage
std::vector<Product> items;
std::sort(items.begin(), items.end(), CompareByPriceAsc{});
This approach becomes cumbersome when multiple comparison strategies are needed, as each requires a new struct with repetitive boilerplate.
Introducing Lambda Expressions
C++11 allows inline anonymous functions through lambdda syntax:
std::sort(items.begin(), items.end(), [](const Product& x, const Product& y) {
return x.price < y.price;
});
This improves readability by placing the comparison logic directly at the call site.
Lambda Syntax Breakdown
The general form is:
[capture](parameters) mutable -> return_type { body }
- Capture list (
[...]): Specifies which variables from the enclosing scope are accessible and how (by value or reference) - Parameters (
(...)): Same as regular functions; can be omitted if empty - Mutable: Allows modification of captured values (by default, lambdas are const)
- Return type (
-> ...): Optional when compiler can deduce it - Body (
{...}): Function implementation
Capture List Variasions
[x]– capturexby value[&x]– capturexby reference[=]– capture all visible variables by value[&]– capture all visible variables by reference[=, &y]– capture all by value exceptyby reference
Note: Mixing explicit captures with = or & is allowed, but duplicate captures cause compilation errors.
Examples
// Simplest lambda
[] {};
// Capture by value, return inferred as int
int p = 5, q = 7;
auto sum = [=] { return p + q; };
// Capture by reference, modify captured variable
auto update = [&](int val) { q = p + val; };
update(10); // q becomes 15
// Full specification with mutable
int factor = 3;
auto multiplier = [factor](int input) mutable {
factor *= 2;
return input * factor;
};
// Returns input * 6 (factor modified inside)
Key Constraints
- Lambdas defined outside block scope must have empty capture lists
- Two lambdas—even with identical bodies—have distinct, incompatible types
- Assignment between lambdas is not allowed, though copy construction works
- Lambdas with no captures can be converted to function poitners
Lambdas vs Function Objects
Both provide callable behavior, but lambdas offer more concise syntax:
// Traditional functor
class InterestCalculator {
double rate;
public:
InterestCalculator(double r) : rate(r) {}
double operator()(double principal, int years) const {
return principal * rate * years;
}
};
// Equivalent lambda
double annual_rate = 0.05;
auto calc_interest = [=](double principal, int years) {
return principal * annual_rate * years;
};
Under the hood, the compiler generates a unique class with an operator() for each lambda, making them syntactic sugar for functors.