anatomy of a lambda

A lambda is an inline, unnamed function for a short snippet that's used once or twice and isn't worth naming separately — most commonly, a callback passed straight into an STL algorithm.

[ capture ] ( parameters ) -> return_type { body }

auto square = [](int x) { return x * x; };   // return type deduced automatically
square(5);   // 25
            
The minimal lambda, []{}, is equivalent to []() -> void {} — every part except the capture clause and body is optional. If a lambda has multiple return statements, they must all deduce the same type, or you write the return type explicitly.

capture modes

CaptureMeaning
[&]everything from the enclosing scope, by reference
[=]everything from the enclosing scope, by value (copied)
[a, &b]a by value, b by reference — mix explicitly per variable
[&, a]everything by reference, except a by value
[=] is convenient but worth using deliberately rather than as a default — it silently copies everything the body touches, which is easy to miss when that includes a large collection you didn't mean to duplicate. [&] avoids the copy but has the opposite risk: if the lambda outlives the variables it references (stored and called later, say, after the enclosing function has returned), those references dangle.

what a lambda actually is

A lambda expression isn't syntactic sugar over a function pointer — the compiler generates a unique, unnamed class (a "closure type") with a captured member per captured variable and an operator() holding the body. Two lambdas with byte-identical source text, written in the same scope, are still two different types — each []{...} expression mints its own class.

auto lb = [](int x) { return 5 * x; };
// roughly equivalent, from the compiler's perspective, to:
class __CompilerGeneratedClosure {
public:
  auto operator()(int x) const { return 5 * x; }
};

lb(5);   // really __CompilerGeneratedClosure_instance.operator()(5)

auto l1 = []{}; auto l2 = []{};
std::is_same<decltype(l1), decltype(l2)>::value;   // false — same source, different generated types
auto l3 = l1;                                          // fine — l3 is a copy, same type as l1
            
Because a closure is a class instance, lambdas live on the stack, not the heap, and by default variables captured by value are treated as members of a const operator() — the lambda can't mutate its own copies. The mutable keyword lifts that restriction for the lambda's internal copy specifically; it still doesn't affect the original variable back in the enclosing scope.

int counter = 0;
auto inc = [counter]() mutable { return ++counter; };  // mutates the LAMBDA's own copy of counter
inc(); inc();               // returns 1, then 2
std::cout << counter;        // still 0 — the outer counter was never touched
            

lambdas as STL callbacks: map / filter / reduce

C++ doesn't have first-class map/filter/reduce functions the way Python does — the same three operations are std::transform, std::copy_if, and std::accumulate, each taking a lambda as the operation to apply:

std::vector<int> nums{10, 11, 12, 13, 14, 15};
std::vector<int> tripled, odds;

std::transform(nums.begin(), nums.end(), std::back_inserter(tripled),
                [](int v) { return v * 3; });                        // map

std::copy_if(tripled.begin(), tripled.end(), std::back_inserter(odds),
             [](int v) { return v % 2 != 0; });                       // filter

int total = std::accumulate(odds.begin(), odds.end(), 0,
                            [](int acc, int v) { return acc + v; });   // reduce
            
See STL Algorithms & Iterators for the rest of this family — std::find_if, std::count_if, std::sort with a comparator lambda, and more all follow the same "algorithm plus a small predicate" shape.
The map/filter/reduce chain above, plus a mutable lambda's captured copy diverging from the original variable, run end to end: TopNotchNote/cpp/lambda_map_filter_reduce.cpp

std::function and std::bind

std::function<Signature> is a type-erased wrapper that can hold any callable matching that signature — a lambda, a function pointer, or a bind expression — which is what lets you store a callback in a member variable or a container, where a lambda's own unique, unnameable closure type wouldn't fit.

std::function<int(int, int)> op = [](int a, int b) { return a + b; };
op(2, 3);   // 5 — op can later be reassigned to a totally different callable with the same signature
            
std::bind partially applies a function — fixing some arguments and optionally reordering the rest — producing a new callable. It predates lambdas being fully general and is largely superseded by a lambda that does the same thing more readably, but it still shows up often enough in existing code to recognize:

bool atLeast(int x, int y) { return x >= y; }

auto over21 = std::bind(atLeast, std::placeholders::_1, 21);   // fixes the second argument to 21
std::count_if(ages.begin(), ages.end(), over21);

// the lambda equivalent, generally preferred in new code:
std::count_if(ages.begin(), ages.end(), [](int x) { return x >= 21; });
            

a note on compile-time metaprogramming

Template metaprogramming (computing a result at compile time via recursive template instantiation, e.g. a compile-time factorial) is a related but separate idea from lambdas and runtime functional programming — it belongs more to Templates & Generics, and in modern C++, constexpr functions (see constexpr & static) usually accomplish the same compile-time computation far more readably than the classic template-recursion tricks did.

where to go from here

STL Algorithms & Iterators — the full algorithm library lambdas plug into.
Templates & Generics — generic callables and function templates.
constexpr & static — compile-time computation as an alternative to metaprogramming tricks.

reference

cppreference — lambda expressions
cppreference — std::function