cs.thefarshad
medium

Lambdas & Functional Style

Write inline callables, capture state by value or reference, store them in std::function, and feed them to STL algorithms.

A lambda is an unnamed function you write right where you need it. It is the glue of modern C++: pass it to std::sort as a comparator, to std::for_each as an action, or to std::transform as a mapping. What makes lambdas more than plain functions is the capture — they can carry state from the surrounding scope. Toggle the capture mode below and watch what happens when that state changes mid-run.

> int factor = 2;
auto scale = [=](int v) { return v * factor; };
std::transform(data.begin(), data.end(), out.begin(), scale);
factor = 10; // changes mid-run
// out now holds the scaled values
closure (scale)
captures factor by value (copy)
factor = 2
enclosing scope
the real variable
factor = 2
data → out (via std::transform)
5
8
3
6
·
·
·
·
1/7
Capture by value [=]: the closure stores its OWN copy of factor (2), taken at the moment the lambda is created.

Anatomy of a lambda

auto add = [](int a, int b) { return a + b; };   // no capture
add(2, 3);                                        // 5

int factor = 10;
auto scale = [factor](int v) { return v * factor; };  // captures factor
scale(4);                                              // 40

The brackets [...] are the capture list, then the parameter list, then the body. A lambda is really an anonymous struct with an operator(); the captures become its member variables. With no captures it is even convertible to a plain function pointer.

Capture by value versus reference

This is the distinction the visualizer makes concrete:

int factor = 2;
auto by_value = [factor](int v) { return v * factor; };  // copies 2
auto by_ref   = [&factor](int v) { return v * factor; }; // aliases factor

factor = 10;
by_value(5);   // 10  -> used the frozen copy (2)
by_ref(5);     // 50  -> sees the live value (10)

By value ([factor] or [=]) copies the variable into the closure at the moment it is created — later changes outside do not affect it. By reference ([&factor] or [&]) stores an alias, so the lambda always reads the live value. Reference captures are powerful but dangerous: if the lambda outlives the captured variable (stored and called later), you get a dangling reference. Prefer value capture unless you specifically need to observe or mutate the original.

std::function and STL algorithms

Each lambda has its own unique, unnameable type. When you need to store a callable in a variable or container — losing the exact type — wrap it in std::function, a type-erased holder for “anything callable with this signature”:

#include <functional>
#include <vector>
#include <algorithm>

std::function<int(int)> op = [](int x) { return x * x; };  // store any callable

std::vector<int> v = {5, 2, 8, 1};
std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });  // descending
std::for_each(v.begin(), v.end(), [](int x) { /* use x */ });
std::transform(v.begin(), v.end(), v.begin(), [](int x) { return x + 1; });

For hot paths, pass the lambda directly (or via a template parameter): the compiler can inline it, whereas std::function adds an indirect call and possible heap allocation. Reach for std::function only when you genuinely need a single named type to hold differing callables.

Takeaways

  • A lambda [captures](params){ body } is an inline, unnamed callable — an anonymous struct with operator().
  • Value capture ([=], [x]) freezes a copy; reference capture ([&], [&x]) aliases the live variable.
  • Reference captures can dangle if the lambda outlives the variable — prefer value capture by default.
  • std::function<R(Args)> type-erases any callable so it can be stored, at the cost of an indirect call.
  • Lambdas are the natural arguments to sort, for_each, and transform; pass them directly in hot paths so they inline.

References