C++ Standard Library · Lesson 9

Lambdas & std::function

The inline anonymous functions you hand to algorithms — how captures work and the one that bites — plus std::function, the box that stores any callable.

🎯 Pass behavior, not just data, into the library
The one idea

A lambda is a function you write inline, right where it's used — the natural argument to sort, find_if, transform. std::function is a general-purpose box that can store any callable with a given signature when you can't (or don't want to) name its exact type.

01 Lambda anatomy

You've used lambdas in the last two lessons. Here's the full shape. The only unusual part versus a normal function is the capture list [...] at the front — it says which surrounding variables the lambda can see.

[factor] (int x) -> int { return x * factor; } capture outer vars params like any fn return type optional body the code
The return type is usually deduced — you can omit -> int and let the compiler figure it out. Most lambdas are just [capture](params){ body }.
int factor = 3;
auto triple = [factor](int x){ return x * factor; };   // store a lambda in 'auto'
triple(10);   // 30

// or write it inline as an algorithm argument (the common case):
std::count_if(v.begin(), v.end(), [](int x){ return x > 0; });
Run on godbolt.org (-std=c++17+).

Source: cppreference — Lambda expressions.

02 Captures — and the one that bites

The capture list controls how the lambda gets at variables from the enclosing scope:

CaptureMeaning
[]capture nothing — only uses its parameters
[x]x by value (a copy, frozen at creation)
[&x]x by reference (sees later changes; must outlive the lambda)
[=]everything used, by value
[&]everything used, by reference
[this]access the enclosing object's members (in a method)
⚠ Reference capture + escaping lambda = dangling If a lambda captures by reference ([&]) and then outlives what it captured — stored in a std::function, returned, or run later on another thread — those references dangle. Rule of thumb: capture by value for anything that escapes the current scope; by reference only for here-and-now use (like an algorithm call on the next line).
// BUG: returns a lambda holding a reference to a dead local
std::function<int()> make_counter() {
    int n = 0;
    return [&n]{ return ++n; };   // ✗ n dies when make_counter returns
}
// FIX: capture by value (or 'mutable' to keep state) → [n]() mutable { return ++n; }

Source: Core Guidelines — capture by reference only for local use.

03 Lambdas are why algorithms are flexible

The algorithm provides the loop; your lambda provides the decision. That separation is the whole reason <algorithm> reads so cleanly:

int threshold = 18;

// "sort by age"
std::sort(people.begin(), people.end(),
          [](const Person& a, const Person& b){ return a.age < b.age; });

// "count adults" — capture the threshold by value
auto adults = std::count_if(people.begin(), people.end(),
    [threshold](const Person& p){ return p.age >= threshold; });

// "names of everyone" — map objects to a field
std::transform(people.begin(), people.end(), std::back_inserter(names),
    [](const Person& p){ return p.name; });
💡 Store lambdas in auto Every lambda has a unique, unnameable compiler-generated type, so you hold one in auto. You only need std::function when auto won't do — see next.

04 std::function — store any callable

Sometimes you need a variable, member, or container element that holds "some callable with signature int(int)" — and it might be a lambda today and a function pointer tomorrow. auto can't express that (it pins one exact type). std::function<Sig> is a type-erased box that holds any callable matching the signature.

#include <functional>

std::function<int(int)> op;        // "holds something callable as int(int)"

op = [](int x){ return x + 1; };     // a lambda
op = std::negate<int>{};            // a function object — same box
op(41);                            // calls whatever's inside

// the payoff: a registry / callback table of heterogeneous callables
std::unordered_map<std::string, std::function<void()>> commands;
commands["save"]  = []{ save(); };
commands["quit"]  = []{ quit(); };
commands[cmd]();                   // dispatch by name
NeedUse
Pass a callable to an algorithm right nowa lambda directly (template deduces it)
Hold a lambda in a local variableauto (zero overhead)
Store / return / collect callables of one signaturestd::function<Sig>
⚠ std::function isn't free It type-erases, which can mean a heap allocation and an indirect call — measurably slower than a raw lambda. Use it when you genuinely need to store mixed callables; for a function parameter, prefer a template or auto when you can.

Source: cppreference — std::function.

05 Check yourself

In [factor](int x){ return x * factor; }, what is [factor]?
A lambda captures a local by reference ([&]) and is returned/stored to run later. The risk?
Why store a lambda in auto rather than naming its type?
When is std::function the right tool?

06 Flashcards

Q: What are the parts of a lambda?
A: [capture](params) -> ret { body }. The capture list is the only new part; the return type is usually deduced and omitted.
Q: [x] vs [&x]?
A: [x] captures a copy frozen at creation; [&x] captures a reference that sees later changes but must outlive the lambda.
Q: The capture rule of thumb?
A: Capture by value if the lambda escapes the current scope (stored/returned/threaded); by reference only for immediate, here-and-now use.
Q: Why hold a lambda in auto?
A: Each lambda has a unique, unnameable compiler-generated type. auto stores it with zero overhead; you can't write its type by hand.
Q: What is std::function<int(int)>?
A: A type-erased box holding any callable with that signature (lambda, function pointer, functor). Use it to store/pass/collect callables when auto can't name the type.
Q: What's the cost of std::function?
A: Type erasure can mean a heap allocation and an indirect call — slower than a bare lambda. Prefer templates/auto for parameters; reserve function for storage.
👩‍🏫 I'm your teacher — ask me anything

Try: "is this capture safe?" with a snippet, or "build a command dispatch table with std::function". Next we go back to memory: smart pointers and ownership.