Lambda Expressions
Many standard library functions need a small piece of behaviour from you: "sort these marks, but highest first", "count the students who passed", "find the first order above Rs. 1,000". A lambda expression (C++11) lets you write that small function right where you need it, without giving it a name or defining it somewhere else.
In this lesson you will write lambdas, pass them to algorithms, capture local variables in different ways, write generic lambdas, and store lambdas in std::function.
Lambda syntax
A lambda has up to four parts: the capture list in square brackets, the parameters in round brackets, an optional return type after ->, and the body in braces. You can store a lambda in an auto variable and call it like a function.
#include <print>
#include <string>
int main() {
auto greet = [](const std::string& name) {
std::println("Namaste, {}!", name);
};
auto square = [](int x) { return x * x; };
auto with_gst = [](double amount) -> double { return amount * 1.18; };
greet("Ananya");
std::println("9 squared is {}", square(9));
std::println("Rs. 500 with GST: Rs. {:.2f}", with_gst(500));
}Namaste, Ananya! 9 squared is 81 Rs. 500 with GST: Rs. 590.00
The compiler usually deduces the return type from the return statement, so -> double is only needed when you want to force a type. Each lambda actually creates an unnamed class with an operator(), exactly like the function objects in Operator Overloading, but with far less typing.
Lambdas with algorithms
The most common place for a lambda is as an argument to a standard algorithm. The algorithm calls your lambda for each item.
#include <algorithm>
#include <print>
#include <vector>
int main() {
std::vector<int> marks = {45, 78, 92, 33, 60, 88};
auto passed = std::ranges::count_if(marks, [](int m) { return m >= 40; });
std::println("Passed: {}", passed);
std::ranges::sort(marks, [](int a, int b) { return a > b; }); // highest first
for (int m : marks) {
std::print("{} ", m);
}
std::println("");
}Passed: 5 92 88 78 60 45 33
count_if asks the lambda "does this item count?" for each mark. sort asks "should a come before b?"; returning a > b gives descending order. You will meet many more algorithms in Iterators & Algorithms.
Captures: using local variables
A lambda's body cannot see the local variables around it unless you capture them in the square brackets.
[pass_mark]captures by value: the lambda keeps its own copy, made when the lambda is created.[&total]captures by reference: the lambda uses the original variable.[=]captures every used variable by value;[&]captures every used variable by reference.
#include <print>
int main() {
int pass_mark = 60;
auto is_pass = [pass_mark](int m) { return m >= pass_mark; };
pass_mark = 90; // the lambda still has 60
std::println("75 passes? {}", is_pass(75));
int total = 0;
auto add = [&total](int amount) { total += amount; };
add(10);
add(25);
std::println("Total: {}", total);
}75 passes? true Total: 35
Changing pass_mark after the lambda was created has no effect, because the lambda copied the value 60. add changes the real total, because it holds a reference. Prefer listing captures explicitly; it makes clear what the lambda depends on.
mutable and init captures
Values captured by copy are read-only inside the lambda. Add mutable to let the lambda change its own copies. An init capture (C++14) creates a new variable that lives inside the lambda:
#include <print>
int main() {
auto next_token = [number = 100]() mutable { return ++number; };
int first = next_token();
int second = next_token();
int third = next_token();
std::println("Tokens: {} {} {}", first, second, third);
}Tokens: 101 102 103
number belongs to the lambda object and keeps its value between calls, which makes next_token a tiny counter. The three calls are in separate statements on purpose: in std::println("{} {}", next_token(), next_token()) C++ does not specify which argument is evaluated first, so the numbers could appear in either order.
Generic lambdas and std::function
A lambda with auto parameters (C++14) is generic: it works with any type, like a template. When you need to store different lambdas in the same variable or container, use std::function from <functional>, which can hold any callable with a matching signature.
#include <functional>
#include <map>
#include <print>
int main() {
auto show = [](const auto& value) { std::println("[{}]", value); };
show(42);
show("Pune");
show(3.5);
std::map<char, std::function<int(int, int)>> calculator = {
{'+', [](int a, int b) { return a + b; }},
{'-', [](int a, int b) { return a - b; }},
{'*', [](int a, int b) { return a * b; }},
};
for (const auto& [symbol, operation] : calculator) {
std::println("12 {} 4 = {}", symbol, operation(12, 4));
}
}[42] [Pune] [3.5] 12 * 4 = 48 12 + 4 = 16 12 - 4 = 8
std::function<int(int, int)> means "anything callable that takes two int values and returns an int". The map is sorted by its char keys, and in character codes * comes before +, which comes before -; that explains the order of the output. std::function is flexible but has a small cost, so use plain auto when you do not need to store different lambdas in one place.
Common mistakes
- Capturing by reference and using the lambda later. If a lambda captures a local by reference and outlives that local (for example, it is returned from the function), it dangles. Capture by value in that case.
- Expecting a value capture to see later changes.
[x]copiesxwhen the lambda is created. - Forgetting
mutable. Changing a by-value capture withoutmutableis a compile error. - Calling a stateful lambda twice in one expression. Argument evaluation order is unspecified; call it in separate statements.
Key points
- A lambda is
[captures](parameters) { body }; store it inautoand call it like a function. - Lambdas are most often passed to algorithms such as
std::ranges::sortandstd::ranges::count_if. [x]captures by value,[&x]by reference;[=]and[&]capture everything used.mutableallows changing by-value captures; init captures like[n = 0]create lambda-owned state.autoparameters make generic lambdas;std::functionstores any callable with a given signature.
Exercise
A cinema in Kochi has ticket prices {180, 250, 120, 300, 220}. Write a lambda affordable that captures a budget of 200 by value and returns whether a price is within the budget. Use it with std::ranges::count_if to count affordable tickets. Then write a lambda that captures a spent variable by reference and adds a price to it, and call it for the first three prices. Print both results.
Show solution
#include <algorithm>
#include <print>
#include <vector>
int main() {
std::vector<int> prices = {180, 250, 120, 300, 220};
int budget = 200;
auto affordable = [budget](int price) { return price <= budget; };
auto count = std::ranges::count_if(prices, affordable);
std::println("Affordable tickets: {}", count);
int spent = 0;
auto buy = [&spent](int price) { spent += price; };
for (int i = 0; i < 3; ++i) {
buy(prices[i]);
}
std::println("Spent on first three: Rs. {}", spent);
}Affordable tickets: 2 Spent on first three: Rs. 550
Only 180 and 120 are within Rs. 200. The first three prices add up to 180 + 250 + 120 = 550, and spent changes because it is captured by reference.
Quick Quiz
1. What is the capture list in [&total](int x) { total += x; }?
The square brackets hold the captures; &total means the lambda uses the original variable.
2. int n = 5; auto f = [n] { return n; }; n = 9; What does f() return?
A by-value capture copies n when the lambda is created.
3. What does mutable allow?
By-value captures are read-only unless the lambda is marked mutable.
4. When do you need std::function instead of auto?
Each lambda has its own unique type; std::function can hold any of them that match the signature.
Learn C++ with a mentor
Free lessons get you started. Our Java, C & C++ Programming training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
