Lambda expressions
In lesson 12, sorting in descending order took std::greater<int>(), and in lesson 14, sorting by a member took a separate comparison function written above. You can instead write a small function right where you call sort. That's a lambda expression:
1#include <algorithm>2#include <iostream>3#include <vector>4 5int main() {6 std::vector<int> scores{72, 95, 88, 60};7 std::sort(scores.begin(), scores.end(), [](int a, int b) { return a > b; });8 for (int s : scores) {9 std::cout << s << " ";10 }11 std::cout << '\n';12 return 0;13}
95 88 72 60
[](int a, int b) { return a > b; } is a function with no name: two parameters, returning true if a is bigger than b, meaning "bigger ones first". sort calls it every time it compares two elements.
What a lambda looks like
A lambda has three parts: square brackets [] (the capture, covered below), parameters in parentheses, and a body in curly braces. No return type needed; the compiler works it out from the return.
A lambda can also be stored in a variable and called like a function. Only the compiler knows its type, so use auto:
1#include <iostream>2 3int main() {4 auto with_tax = [](double price) { return price * 1.1; };5 auto is_even = [](int n) { return n % 2 == 0; };6 std::cout << with_tax(50) << '\n';7 std::cout << with_tax(200) << '\n';8 std::cout << is_even(7) << '\n';9 return 0;10}
55 220 0
Press Step: each call to with_tax adds a layer, just like an ordinary function, with price holding this call's value.
Lambdas for sort
Sorting by a member used to need a separate function. Now the rule sits right next to sort, and readers don't have to scroll up:
1#include <algorithm>2#include <iostream>3#include <string>4#include <vector>5 6struct Student {7 std::string name;8 int score;9};10 11int main() {12 std::vector<Student> list{{"Lin", 82}, {"Zhou", 95}, {"Chen", 76}};13 std::sort(list.begin(), list.end(), [](const Student& a, const Student& b) {14 return a.score > b.score;15 });16 for (const auto& s : list) {17 std::cout << s.name << " " << s.score << '\n';18 }19 return 0;20}
Zhou 95 Lin 82 Chen 76
The parameters are const Student&: no copy, no changes (lesson 10). A lambda body can span several lines, just like an ordinary function's.
count_if and find_if
Many <algorithm> functions ending in _if want a function that "asks about one element and answers true or false". std::count_if counts how many answer true; std::find_if finds the first that does, returning an iterator to it, or end() if there's none:
1#include <algorithm>2#include <iostream>3#include <vector>4 5int main() {6 std::vector<int> scores{72, 58, 91, 45, 66};7 auto failed = std::count_if(scores.begin(), scores.end(), [](int s) { return s < 60; });8 std::cout << "Failed: " << failed << '\n';9 auto it = std::find_if(scores.begin(), scores.end(), [](int s) { return s > 90; });10 if (it != scores.end()) {11 std::cout << "First over 90: " << *it << '\n';12 }13 return 0;14}
Failed: 2 First over 90: 91
Compare lesson 12's std::count and std::find: those only count or find elements "equal to some value". With _if, the condition is up to you. (More on iterators in lesson 27; for now, *it gets the value.)
for_each
std::for_each calls your function once for each element. With a reference parameter, it can change the elements:
1#include <algorithm>2#include <iostream>3#include <vector>4 5int main() {6 std::vector<double> prices{20, 35, 12.5};7 std::for_each(prices.begin(), prices.end(), [](double& p) { p = p * 0.8; });8 std::for_each(prices.begin(), prices.end(), [](double p) {9 std::cout << "$" << p << '\n';10 });11 return 0;12}
$16 $28 $10
Honestly, a range for is just as clear for this. The upside of for_each is that when you already have a lambda stored in a variable, you can just hand it over.
Capturing outside variables
If a lambda wants a variable from outside, say a pass mark, using it directly is a compile error:
1#include <algorithm>2#include <iostream>3#include <vector>4 5int main() {6 std::vector<int> scores{72, 58, 91, 45, 66};7 int pass_line = 70;8 auto passed = std::count_if(scores.begin(), scores.end(), [](int s) { return s >= pass_line; });9 std::cout << passed << '\n';10 return 0;11}
main.cpp: In lambda function:
main.cpp:8:87: error: 'pass_line' is not captured
8 | auto passed = std::count_if(scores.begin(), scores.end(), [](int s) { return s >= pass_line; });
| ^~~~~~~~~'pass_line' is not captured. By default a lambda can't see local variables outside it; you list them in the square brackets, which is called capturing. Write [pass_line] and it works: the result is 2.
[pass_line] captures by value: the moment the lambda is created, it takes a copy of pass_line's value. Changes outside after that don't reach the lambda:
What does this program show?
1#include <iostream>2 3int main() {4 int bonus = 5;5 auto add_bonus = [bonus](int s) { return s + bonus; };6 bonus = 100;7 std::cout << add_bonus(80) << '\n';8 return 0;9}
When the lambda is created on line 5, bonus is 5, and that's copied in. Line 6 changes the bonus outside; the lambda's copy is still 5, so it's 85.
Capturing by reference
To let a lambda change an outside variable, or see its latest value, capture by reference: [&total]. The total in the lambda is a reference to the one outside:
1#include <algorithm>2#include <iostream>3#include <vector>4 5int main() {6 std::vector<int> steps{4200, 8100, 6500, 9900};7 int total = 0;8 int good_days = 0;9 int goal = 6000;10 std::for_each(steps.begin(), steps.end(), [&total, &good_days, goal](int s) {11 total += s;12 if (s >= goal) {13 good_days++;14 }15 });16 std::cout << total << " steps, goal met on " << good_days << " days\n";17 return 0;18}
28700 steps, goal met on 3 days
Press Step: each time the lambda runs, total and good_days in main change. goal is only read, so capturing it by value is fine.
| Write | Meaning |
|---|---|
[] | capture nothing |
[x] | copy x |
[&x] | reference x |
[=] | copy every outside variable used |
[&] | reference every outside variable used |
Note: A variable captured by value is read-only inside the lambda: [total](int s) { total += s; } is a compile error. Take care when capturing by reference: when the lambda runs, the referenced variable must still be alive.
std::function
You can't write a lambda's type, so auto stores it. But to have one variable hold this lambda now and that one later, or to write a function whose parameter is a function, use std::function from <functional>. In the angle brackets, write "return type(parameter types)":
1#include <functional>2#include <iostream>3#include <vector>4 5void show_prices(const std::vector<double>& prices, std::function<double(double)> rule) {6 for (double p : prices) {7 std::cout << rule(p) << " ";8 }9 std::cout << '\n';10}11 12int main() {13 std::vector<double> prices{20, 50, 120};14 std::function<double(double)> rule = [](double p) { return p; };15 show_prices(prices, rule);16 rule = [](double p) { return p * 0.9; };17 show_prices(prices, rule);18 double cut = 15;19 show_prices(prices, [cut](double p) { return p >= 100 ? p - cut : p; });20 return 0;21}
20 50 120 18 45 108 20 50 105
A std::function<double(double)> can hold anything that "takes a double and gives back a double": different lambdas, ones with captures, plain functions too. show_prices doesn't care what the rule is; it just calls it. (In the last lambda, condition ? a : b gives a if the condition holds, otherwise b.)
Practice
Write the code, then press Check. A hint costs nothing; the answer is there when you need it.
Cheapest first
✓Use std::sort with a lambda to put the products in order of price, lowest first, then show the names in order. The program should show three lines: Eggs, Milk, Coffee beans.
Eggs Milk Coffee beans
1#include <algorithm>2#include <iostream>3#include <string>4#include <vector>5 6struct Product {7 std::string name;8 double price;9};10 11int main() {12 std::vector<Product> cart{{"Coffee beans", 68}, {"Eggs", 15.5}, {"Milk", 22}};13 // sort here14 for (const auto& p : cart) {15 std::cout << p.name << '\n';16 }17 return 0;18}
How many hot days
✓Use std::count_if to count the days in temps above hot (capture it). The program should show Hot days: 3.
Hot days: 3
1#include <algorithm>2#include <iostream>3#include <vector>4 5int main() {6 std::vector<int> temps{29, 33, 35, 31, 36, 28};7 int hot = 32;8 // count here9 return 0;10}
Fix it: a total that can't change
✓This code tries to add up the bills with for_each, but it doesn't compile. Add one character so the program shows Total: 86.
Total: 86
1#include <algorithm>2#include <iostream>3#include <vector>4 5int main() {6 std::vector<int> bills{30, 12, 44};7 int total = 0;8 std::for_each(bills.begin(), bills.end(), [total](int b) { total += b; });9 std::cout << "Total: " << total << '\n';10 return 0;11}
What you learned
- A lambda is a small function written in place:
[capture](parameters) { body }, with the return type worked out; store one withauto. - Give
std::sorta lambda to set the order, like[](int a, int b) { return a > b; }. std::count_ifandstd::find_ifwant a lambda answeringtrue/false;std::for_eachcalls one for each element.- To use local variables from outside, a lambda captures them:
[x]copies (the value at creation, read-only),[&x]is a reference (can change it, sees the latest value). [=]copies everything used,[&]references everything used.std::function<return type(parameter types)>holds all kinds of functions and lambdas, to store, swap, or pass as a parameter.