Kodewick
Lesson 22 of 30Contents

Lambda expressions

Lesson 22 of 30 · Part 5 · Memory and lambdas · about 15 minutes

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:

main.cpp
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}
Output
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:

main.cpp
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}
Output
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:

main.cpp
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}
Output
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:

main.cpp
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}
Output
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:

main.cpp
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}
Output
$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:

main.cpp
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}
Output
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:

Guess first

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}

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:

main.cpp
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}
Output
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.

WriteMeaning
[]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)":

main.cpp
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}
Output
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.

Exercise 1

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.

It should print
Eggs
Milk
Coffee beans
exercise1.cpp
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}
Output
Press Run or Ctrl+Enter
Exercise 2

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.

It should print
Hot days: 3
exercise2.cpp
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}
Output
Press Run or Ctrl+Enter
Exercise 3

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.

It should print
Total: 86
exercise3.cpp
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}
Output
Press Run or Ctrl+Enter

What you learned

  • A lambda is a small function written in place: [capture](parameters) { body }, with the return type worked out; store one with auto.
  • Give std::sort a lambda to set the order, like [](int a, int b) { return a > b; }.
  • std::count_if and std::find_if want a lambda answering true / false; std::for_each calls 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.
← Previous lessonSmart pointersNext lesson · Coming soonInheritance