Lambda Functions in c++11
An introductory course — C++11 and beyond
Covers: syntax capture STL algorithms closures best practices
1. What is a Lambda Function?
A lambda function (also called a lambda expression or anonymous function) is a function defined inline, without a name, exactly where it is needed. Introduced in C++11, lambdas are one of the most important modern features of the language.
Before lambdas, passing custom behaviour to algorithms required writing a separate named function or a functor (a class with operator()). Lambdas eliminate that boilerplate entirely.
Without lambda (C++03)
// A separate comparator function was required
bool byLength(const std::string& a, const std::string& b) {
return a.size() < b.size();
}
std::sort(words.begin(), words.end(), byLength);
With lambda (C++11)
// The comparator lives right at the call site
std::sort(words.begin(), words.end(),
[](const std::string& a, const std::string& b) {
return a.size() < b.size();
});
The second version is shorter, easier to read, and keeps the logic exactly where it belongs.
2. Syntax Breakdown
Every lambda expression has four parts, three of which are optional:
[ capture ] ( parameters ) -> return_type { body }
| Part | Description |
|---|---|
[ capture ] |
Variables from the outer scope accessible inside the lambda. Required, but can be empty []. |
( parameters ) |
Input parameters, exactly like a normal function. Can be omitted if there are none. |
-> return_type |
The return type. Optional — the compiler deduces it automatically in almost all cases. |
{ body } |
The function body. Any valid C++ code. |
Minimal examples
// Absolutely minimal — no capture, no params, deduced return
auto greet = [] { std::cout << "Hello!\n"; };
// With parameters
auto add = [](int a, int b) { return a + b; };
// With explicit return type
auto divide = [](double a, double b) -> double {
if (b == 0.0) return 0.0;
return a / b;
};
3. The Capture Clause
The capture clause [ ] determines which variables from the surrounding scope are available inside the lambda, and how they are passed in.
| Syntax | Meaning |
|---|---|
[] |
Nothing captured. The lambda cannot access any outer variables. |
[=] |
Capture everything by value (copy). The lambda gets its own copy of each variable. |
[&] |
Capture everything by reference. The lambda reads and writes the originals. |
[x] |
Capture only x, by value. |
[&x] |
Capture only x, by reference. |
[x, &y] |
x by value, y by reference. |
[=, &x] |
Everything by value, except x which is by reference. |
[this] |
Capture the this pointer (used inside class methods). |
Capture by value vs. by reference
int x = 10;
int y = 20;
// By value — lambda gets copies; originals are unchanged
auto byVal = [x, y]() { return x + y; };
std::cout << byVal() << "\n"; // 30
// By reference — lambda modifies the original variable
auto byRef = [&x]() { x += 5; };
byRef();
std::cout << x << "\n"; // 15
Rule of thumb: capture by value when the lambda outlives the variable (e.g. stored in a callback). Capture by reference for short-lived lambdas used immediately.
4. The mutable Keyword
By default, variables captured by value are const inside the lambda — you cannot modify them. The mutable keyword lifts this restriction, allowing the lambda to modify its local copy (not the original).
int counter = 0;
// Without mutable — compile error: counter is const
// auto f = [counter]() { counter++; };
// With mutable — modifies the local copy only
auto f = [counter]() mutable {
counter++;
return counter;
};
std::cout << f() << "\n"; // 1
std::cout << f() << "\n"; // 2
std::cout << counter << "\n"; // 0 — original unchanged
5. Storing and Passing Lambdas
Lambdas can be stored in variables and passed around like any other value. There are two common approaches.
auto — for local storage
auto multiply = [](int a, int b) { return a * b; };
std::cout << multiply(4, 5) << "\n"; // 20
std::function — for flexible storage and passing
#include <functional>
// Store as a typed function object
std::function<int(int, int)> op;
op = [](int a, int b) { return a + b; };
std::cout << op(3, 4) << "\n"; // 7
op = [](int a, int b) { return a * b; };
std::cout << op(3, 4) << "\n"; // 12
// Pass a lambda to a function
void apply(std::function<int(int)> f, int val) {
std::cout << f(val) << "\n";
}
apply([](int x) { return x * x; }, 6); // 36
Note:
autois faster (no overhead);std::functionis necessary when the lambda must be stored in a class member, a container, or passed across translation units.
6. Lambdas with STL Algorithms
This is where lambdas truly shine. Every STL algorithm that accepts a callable becomes dramatically more readable with a lambda.
std::sort — custom comparator
std::vector<int> v = {5, 2, 8, 1, 9};
// Descending order
std::sort(v.begin(), v.end(),
[](int a, int b) { return a > b; });
// v = {9, 8, 5, 2, 1}
std::find_if — find first match
std::vector<int> nums = {3, 7, 2, 9, 4};
auto it = std::find_if(nums.begin(), nums.end(),
[](int n) { return n > 5; });
if (it != nums.end())
std::cout << "First > 5: " << *it << "\n"; // 7
std::transform — map over a vector
std::vector<int> nums = {1, 2, 3, 4, 5};
std::vector<int> squares;
std::transform(nums.begin(), nums.end(),
std::back_inserter(squares),
[](int n) { return n * n; });
// squares = {1, 4, 9, 16, 25}
std::copy_if — filter elements
std::vector<int> nums = {1, 2, 3, 4, 5, 6};
std::vector<int> evens;
std::copy_if(nums.begin(), nums.end(),
std::back_inserter(evens),
[](int n) { return n % 2 == 0; });
// evens = {2, 4, 6}
std::accumulate — reduce / fold
#include <numeric>
std::vector<int> nums = {1, 2, 3, 4, 5};
int sum = std::accumulate(nums.begin(), nums.end(), 0,
[](int acc, int n) { return acc + n; });
// sum = 15
7. Closures — Lambda Returning Lambda
A closure is a lambda that captures its environment and returns another lambda. This is a powerful pattern for creating configurable, reusable function objects.
Factory function example
// makeMultiplier returns a NEW lambda each time
auto makeMultiplier(int factor) {
return [factor](int x) {
return x * factor; // factor is captured
};
}
int main() {
auto triple = makeMultiplier(3);
auto byTen = makeMultiplier(10);
std::cout << triple(5) << "\n"; // 15
std::cout << byTen(5) << "\n"; // 50
std::cout << triple(byTen(2)) << "\n"; // 60
return 0;
}
Each call to makeMultiplier creates an independent lambda with its own captured copy of factor. This pattern is the C++ equivalent of partial application in functional programming.
8. Recursive Lambda
A lambda cannot refer to itself by its own name inside its body — it does not have one. The solution is to use std::function, which allows the lambda to refer to a named variable.
#include <functional>
std::function<int(int)> factorial = [](int n) -> int {
return (n <= 1) ? 1 : n * factorial(n - 1);
};
std::cout << factorial(6) << "\n"; // 720
Important: the variable
factorialmust be declared withstd::function, notauto, because at the time the lambda is defined, its own type is not yet known.
9. Complete Example with main()
The following program demonstrates all major concepts in a single, self-contained example.
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <functional>
auto makeAdder(int base) {
return [base](int x) { return base + x; };
}
std::function<int(int)> factorial = [](int n) -> int {
return (n <= 1) ? 1 : n * factorial(n - 1);
};
int main() {
// 1. Basic lambda
auto greet = []() { std::cout << "Hello, Lambda!\n"; };
greet();
// 2. Lambda with parameters
auto add = [](int a, int b) { return a + b; };
std::cout << "3+4=" << add(3, 4) << "\n";
// 3. Capture by reference
int counter = 0;
auto inc = [&counter]() { counter++; };
inc(); inc(); inc();
std::cout << "counter=" << counter << "\n"; // 3
// 4. std::sort with lambda comparator
std::vector<int> v = {5, 2, 8, 1, 9, 3};
std::sort(v.begin(), v.end(), [](int a, int b){ return a > b; });
for (int x : v) std::cout << x << " ";
std::cout << "\n"; // 9 8 5 3 2 1
// 5. std::transform — squares
std::vector<int> nums = {1, 2, 3, 4, 5};
std::vector<int> sq;
std::transform(nums.begin(), nums.end(), std::back_inserter(sq),
[](int n){ return n * n; });
for (int x : sq) std::cout << x << " ";
std::cout << "\n"; // 1 4 9 16 25
// 6. std::accumulate — sum
int sum = std::accumulate(nums.begin(), nums.end(), 0,
[](int acc, int n){ return acc + n; });
std::cout << "sum=" << sum << "\n"; // 15
// 7. Closure — lambda returning lambda
auto add10 = makeAdder(10);
std::cout << "add10(7)=" << add10(7) << "\n"; // 17
// 8. Recursive lambda
std::cout << "5!=" << factorial(5) << "\n"; // 120
return 0;
}
Expected output
Hello, Lambda!
3+4=7
counter=3
9 8 5 3 2 1
1 4 9 16 25
sum=15
add10(7)=17
5!=120
10. Best Practices
- Use
autoto store lambdas locally. Reservestd::functiononly when you need to store or pass them across boundaries. - Prefer specific captures (
[x, &y]) over blanket captures ([=],[&]). Explicit captures make dependencies obvious. - Avoid capturing
[&]in lambdas that outlive the current scope — dangling references cause undefined behaviour. - Keep lambda bodies short. If the body exceeds ~5 lines, consider extracting it into a named function.
- Use
mutablesparingly. If you need to mutate a lot of state, a regular function or a functor is likely clearer. - For recursive lambdas, always use
std::function—autocannot refer to itself.
11. Quick Reference
| Feature | Syntax / Example |
|---|---|
| Basic lambda | auto f = []() { ... }; |
| With parameters | auto f = [](int a, int b) { return a+b; }; |
| Explicit return type | auto f = [](double x) -> double { return x; }; |
| Capture by value | [x, y] — copies of x and y |
| Capture by reference | [&x, &y] — references to x and y |
| Capture all by value | [=] |
| Capture all by reference | [&] |
| mutable | [x]() mutable { x++; } — modifies local copy |
| Store in std::function | std::function<int(int)> f = [](int x){ return x; }; |
| Sort comparator | std::sort(v.begin(), v.end(), [](int a, int b){ return a>b; }); |
| Transform | std::transform(... [](int n){ return n*n; }); |
| Filter | std::copy_if(... [](int n){ return n%2==0; }); |
| Reduce | std::accumulate(... 0, [](int acc, int n){ return acc+n; }); |
| Closure factory | auto make = [](int x){ return [x](int y){ return x+y; }; }; |
| Recursive lambda | std::function<int(int)> f = [](int n){ return n<=1?1:n*f(n-1); }; |