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.
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.
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.
-> 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; });
-std=c++17+).Source: cppreference — Lambda expressions.
The capture list controls how the lambda gets at variables from the enclosing scope:
| Capture | Meaning |
|---|---|
[] | 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) |
[&]) 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.
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; });
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.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
| Need | Use |
|---|---|
| Pass a callable to an algorithm right now | a lambda directly (template deduces it) |
| Hold a lambda in a local variable | auto (zero overhead) |
| Store / return / collect callables of one signature | std::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.
[factor](int x){ return x * factor; }, what is [factor]?[&]) and is returned/stored to run later. The risk?auto rather than naming its type?std::function the right tool?[capture](params) -> ret { body }. The capture list is the only new part; the return type is usually deduced and omitted.[x] vs [&x]?[x] captures a copy frozen at creation; [&x] captures a reference that sees later changes but must outlive the lambda.auto?auto stores it with zero overhead; you can't write its type by hand.std::function<int(int)>?auto can't name the type.std::function?auto for parameters; reserve function for storage.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.