C++, End to End
Lambdas and Function Objects
A lambda is an anonymous function you can define right where you need it, often as an argument to another function. Before lambdas existed (pre-C++11), the idiom for "a value that behaves like a function" was a function object, a class with operator() overloaded. Lambdas mostly replaced that pattern for one-off cases, but function objects are still exactly what a lambda compiles down to, so understanding both makes the whole idea click.
Lambda syntax, piece by piece
Breaking down [](int a, int b) { return a + b; }:
[]is the capture list: what variables from the surrounding scope the lambda can see. Empty here, so it can only use its own parameters.(int a, int b)is the parameter list, exactly like a regular function.{ return a + b; }is the body, exactly like a regular function.- The return type is deduced automatically here. You can specify it explicitly with
-> intafter the parameter list if the deduction would be ambiguous or you just want to be explicit.
Capturing by value vs by reference
[=] captures everything used in the body by value; [&] captures everything used by reference. Mixing is allowed too, [x, &y] captures x by value and y by reference specifically. Prefer naming exactly what you capture over the blanket [=]/[&] forms once a lambda gets non-trivial: it documents intent, and it avoids accidentally capturing something you didn't mean to.
The dangling-reference risk
A lambda that captures by reference is only as valid as the thing it references. If the lambda outlives the variable it captured (stored for later, returned from a function, handed off to run asynchronously), that reference dangles, and using it is undefined behavior, exactly like any other dangling reference.
The fix is almost always to capture by value instead when the lambda's lifetime might outlast the enclosing scope:
Note the mutable keyword there. A lambda's captured-by-value members are const by default (calling the lambda can't modify its own copies), so a lambda that needs to modify its captured state across calls, like this counter incrementing its own copy of count, needs mutable to allow it.
Function objects (functors)
A function object is any class with operator() overloaded, letting an instance of it be called like a function. This is exactly what lambdas desugar to: every lambda is compiler-generated syntactic sugar for an anonymous class with an operator(), and (for reference captures) member references to whatever it captured.
Writing a function object by hand today is rare, lambdas are shorter and clearer for almost every case a functor used to handle. It's worth knowing the pattern anyway: you'll see it in older code, and understanding that a lambda IS a function object (just one the compiler writes for you) removes a lot of the mystery around what capture actually means underneath.
std::function and its tradeoff
std::function<Signature> is a type-erased wrapper that can hold any callable matching a given signature, a lambda, a function pointer, a function object, regardless of its actual underlying type. This is genuinely useful when you need to store heterogeneous callables (a vector of different callbacks, say) or pass a callable across an API boundary without templating that API on the callable's exact type.
The tradeoff: std::function erases the callable's real type behind a virtual-call-like interface, which typically means a small heap allocation for anything that doesn't fit in its internal small-buffer optimization, plus indirect call overhead. A template parameter (template <typename Callable> void apply(Callable f)) keeps the callable's real type known at compile time, so the compiler can inline the call, no allocation, no indirection, but it means the function itself must be a template, which isn't always possible or desirable (e.g. it can't appear in a stored member of a non-template class, or across a compiled library boundary). Reach for std::function when you genuinely need runtime polymorphism over callables; reach for a template parameter when you don't.
Try it yourself: fix a dangling capture›
This function returns a lambda that's supposed to check whether a number is above a threshold set at call time. It has the same dangling-reference bug shown above. Find and fix it.
std::function<bool(int)> makeThresholdCheck(int threshold) {
return [&threshold](int x) { return x > threshold; }; // BUG: captures a reference to a parameter
}threshold is a function parameter, a local variable that stops existing the moment makeThresholdCheck returns. Capturing it by reference produces exactly the same dangling-reference bug as the counter example above, just with a parameter instead of a local variable. The fix is identical: capture by value so the lambda owns its own independent copy.