Skip to content

Expert · Modern C++: lambdas, C++17 and C++20 essentials · topic 30 of 42 · 30 min

Lambdas: small functions written in place

Explanation

A lambda is an unnamed function you write right where you need it, often as the comparison for std::sort or the condition for std::count_if. The capture list [ ] says which outside variables it can use: [=] copies, [&] refers, or name them one by one. Be careful with [&] when the lambda outlives the variables.

Why lambdas?

Algorithms like std::sort, std::count_if and std::find_if need a small piece of logic: how to compare, what to count. Before C++11 you had to write a separate named function or a class for each one, far away from where it was used. A lambda expression lets you write that small function inline, exactly where it is needed.

cpp
#include <algorithm>
#include <iostream>
#include <vector>

int main() {
    std::vector<int> marks = {35, 82, 47, 91, 28};
    int passMark = 40;
    auto passed = std::count_if(marks.begin(), marks.end(),
                                [passMark](int m) { return m >= passMark; });
    std::cout << passed << '\n';   // 3
}
[passMark] captures the local variable; (int m) is the parameter; the body returns a bool.

The parts of a lambda

output
[capture](parameters) -> returnType { body }

auto add = [](int a, int b) { return a + b; };        // return type deduced
auto half = [](double x) -> double { return x / 2; };  // explicit return type
std::cout << add(2, 3) << ' ' << half(5) << '\n';      // 5 2.5
A lambda creates a callable object (a closure). You can store it in an auto variable and call it like a function.

Captures

CaptureMeaning
[]captures nothing
[x]copies x into the lambda when the lambda is created
[&x]refers to x; changes and later updates are shared
[=]copies every outside variable the body uses
[&]refers to every outside variable the body uses
[this]lets a lambda inside a member function use the object's members
[total = 0]init-capture: makes a new variable inside the lambda (C++14)
cpp
int count = 0;
auto byRef = [&count]() { ++count; };
byRef(); byRef();
std::cout << count << '\n';            // 2

int base = 10;
auto byValue = [base]() { return base * 2; };
base = 100;
std::cout << byValue() << '\n';        // 20: the copy was taken when the lambda was made

auto counter = [n = 0]() mutable { return ++n; };
std::cout << counter() << counter() << '\n';   // 12
Copies are const inside the lambda unless you write mutable.
Think of it like this: A lambda with a capture is like a delivery rider leaving the shop. Capture by value is taking a printed copy of the address list: later changes at the shop are not on it. Capture by reference is taking a phone number to call the shop each time: always current, but useless if the shop has closed (the variable no longer exists).
Careful: Dangling captures: a lambda that captures local variables by reference and is stored or run later (in a thread, a callback, a returned std::function) may run after those locals are destroyed. That is undefined behaviour. When a lambda outlives the current scope, capture by value (or move the data in with an init-capture).

Generic lambdas and std::function

cpp
auto show = [](const auto& x) { std::cout << x << '\n'; };   // C++14 generic lambda
show(42);
show("Namaste");

#include <functional>
std::function<double(double)> taxFor = [](double p) { return p * 0.18; };
taxFor = [](double p) { return p * 0.05; };   // can hold any callable with that signature
auto parameters make the lambda a template. std::function can store any callable but has some overhead; prefer auto or templates when you can.

Where lambdas appear

  • Comparators: std::sort(v.begin(), v.end(), [](const auto& a, const auto& b) { return a.price < b.price; });
  • Predicates: std::find_if, std::any_of, std::erase_if, std::remove_if.
  • Callbacks: run this when a button is clicked or a download finishes.
  • Threads: std::thread t([] { doWork(); });
  • Immediately invoked initialisation: const int limit = [&] { /* complex logic */ return 5; }();
Tip: Keep lambdas short. If a lambda grows beyond a few lines or is used in several places, turn it into a named function: names are documentation.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Lambdas: small functions written in place · 1/6

Lambdas

A lambda is a small function written right where you need it.

Prefer a video by a teacher? Find free videos on “c++ lambda expressions capture by value reference tutorial” on YouTube ↗

In real life: Filtering a product catalogue

A shopping app filters products by the user's choices: price range and minimum rating. The filter values come from the screen, so the lambda captures them, and std::copy_if builds the filtered list. Sorting by price uses another lambda.

cpp
#include <algorithm>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>

struct Product { std::string name; int price; double rating; };

int main() {
    std::vector<Product> all = {{"Earbuds", 1299, 4.1}, {"Speaker", 2499, 4.5},
                                {"Cable", 199, 3.9}, {"Charger", 899, 4.4}};
    int maxPrice = 2000;
    double minRating = 4.0;
    std::vector<Product> shown;
    std::copy_if(all.begin(), all.end(), std::back_inserter(shown),
                 [maxPrice, minRating](const Product& p) {
                     return p.price <= maxPrice && p.rating >= minRating;
                 });
    std::sort(shown.begin(), shown.end(), [](const Product& a, const Product& b) { return a.price < b.price; });
    for (const auto& p : shown) std::cout << p.name << ' ' << p.price << '\n';
}
// Charger 899
// Earbuds 1299
Captured filter values plus two short lambdas replace two hand-written loops.

Practice (0/4 done)

Task 1 · Question

What does this print? auto sq = [](int x) { return x * x; }; std::cout << sq(6);

Task 2 · Question

What does this print? int k = 1; auto f = [k]() { return k + 1; }; auto g = [&k]() { return k + 1; }; k = 10; std::cout << f() << " " << g();

Task 3 · Question

Which line has the bug? 1: std::function<int()> make() { 2: int total = 5; 3: return [&total]() { return total; }; 4: }

Task 4 · Practice

Given std::vector<std::string> names, use lambdas with algorithms to: (1) count names longer than 5 letters, (2) sort names by length, shortest first, ties alphabetically, (3) remove names starting with 'A' using std::erase_if. Print the results.

Quick quiz

Question 1

1.What does [=] capture?

Question 2

2.Why is mutable needed in [n = 0]() mutable { return ++n; }?

Question 3

3.What makes a lambda "generic"?

Question 4

4.Which is a downside of std::function compared with auto?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What does [=] capture?

Answer: Every outside variable used in the body, by copy

[=] is a default capture by value.

Why is mutable needed in [n = 0]() mutable { return ++n; }?

Answer: Captured copies are const by default; mutable allows changing them

Without mutable, ++n would not compile.

What makes a lambda "generic"?

Answer: It uses auto in its parameter list

auto parameters turn the call operator into a template.

Which is a downside of std::function compared with auto?

Answer: It has some run-time overhead (type erasure, possible allocation)

std::function is flexible but costs more than calling the lambda directly.

What is the result of a lambda?Related

Answer: The value of its last expression

The last expression is returned automatically.

From Lambdas and higher-order functions

In Func<string, int>, what is the return type?Related

Answer: int

The last type parameter of Func is the return type.

From Delegates, lambdas, Func, Action and Predicate

What is the time complexity of std::sort?Related

Answer: O(n log n)

The standard requires O(n log n) comparisons.

From STL algorithms: sort, find, count, accumulate and binary search

When did lambdas arrive in Java?Related

Answer: Java 8

Java 8 (2014) added lambdas and streams.

From Lambdas and functional interfaces

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 3 more topics in “Modern C++: lambdas, C++17 and C++20 essentials” before its exam: auto, structured bindings and other C++17 conveniences, std::optional, std::variant and std::string_view, C++20 ranges, views and other C++20 essentials.
  2. Module 8 exam: Modern C++ · 12 questions · 25 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, Exceptions, concurrency and building projects with CMake. 42 topics left in the course before the final exam.

← Previous: Move semantics: steal instead of copy