Functions You Write Where You Use Them
On the previous page you saw how overloading lets several functions share a name. But sometimes you don't want a named function at all - you need a tiny piece of logic once, right where you use it, and naming it would just add clutter. That's what a lambda is: an anonymous function you can define inline.
A lambda has a distinctive four-part shape:
[capture](parameters) -> return_type { body }
The [] is the giveaway that you're looking at a lambda. The return type is optional - the compiler usually deduces it. Here's the simplest possible one:
greet is just a variable (its type is unspeakable, so you store it with auto) that you can call with (). Lambdas with parameters work exactly like normal functions:
Captures: Reaching Into the Surrounding Scope
The part that makes lambdas more than just nameless functions is the capture list - the []. It lets the lambda use variables from the scope where it was defined, not just its own parameters.
Capture by value with [x]: the lambda gets its own copy, frozen at the moment the lambda is created.
Notice scale(5) printed 50, using the factor value of 10 that existed when the lambda was created. Capture by value takes a snapshot.
Capture by reference with [&x]: the lambda refers to the original variable, sees later changes, and can modify it.
You can also capture everything used by the lambda with [=] (all by value) or [&] (all by reference). They're convenient, but being explicit - [total] or [&total] - documents exactly what the lambda touches and is easier to reason about.
The Dangling-Reference Trap
Capturing by reference is powerful and dangerous in equal measure. The reference is only valid as long as the original variable is alive. If the lambda outlives what it captured, you get a dangling reference and undefined behavior - the program might crash, might print garbage, might appear to work by accident.
This is the classic mistake: returning a lambda that captures a local by reference.
auto makeCounter() {
int count = 0;
return [&count]() { return ++count; }; // BUG: count dies here
}
// The returned lambda now references destroyed memory.
When makeCounter returns, its local count is destroyed, but the lambda still holds a reference to it. Calling the returned lambda touches dead memory. The fix is to capture by value so the lambda owns its own state:
Rule of thumb: capture by reference only when the lambda is used immediately and locally (as with algorithms below). The moment a lambda is stored, returned, or runs later, prefer capturing by value.
mutable and Return Types
Did you spot the mutable in that last example? By default, a by-value capture is const inside the lambda - you can read the copy but not change it. Adding mutable lets the lambda modify its own copies between calls.
mutable only affects the lambda's private copy - the outer seen is untouched, which is the whole point of capturing by value.
Most of the time the compiler deduces the return type just fine. You only need to spell it out with -> when there's ambiguity, such as a lambda that could return different types on different branches:
// Without -> the compiler can't decide between int and double
auto half = [](int n) -> double {
if (n % 2 == 0) return n / 2; // int
return n / 2.0; // double
};
Lambdas and Algorithms: The Real Payoff
The reason lambdas were added to C++ is to feed short pieces of logic to standard-library algorithms. Before lambdas, you had to write a separate named function or a clunky function object far from where it was used. Now the logic lives right at the call site.
The most common example is a custom sort order:
Captures shine here because the lambda can pull in a value to filter or count against. This counts how many numbers exceed a threshold the user chose:
Because these lambdas are used immediately and don't outlive the surrounding function, capturing by reference ([&passMark]) would also be safe here - but by value is just as clear and never dangles.
Next: Pointers
Lambdas quietly raised a deeper question: when you capture [&x], the lambda is holding onto the location of x, and that location stays valid only while x lives. That idea - a value that refers to where something lives in memory, and what happens when the thing it points to disappears - is exactly what the next page is about. We'll meet pointers head-on: how to take an address, how to follow it, and how the same dangling problem you just saw shows up across all of C++.
Frequently Asked Questions
What is a lambda in C++?
A lambda is an anonymous function you can write inline, right where you use it. The syntax is [captures](parameters){ body }. It's perfect for short, one-off operations - like the comparison you hand to std::sort - without having to declare a separate named function elsewhere.
What is the difference between capturing by value and by reference in a C++ lambda?
[x] captures a copy of x frozen at the moment the lambda is created. [&x] captures a reference to the original x, so the lambda sees later changes and can modify it. Use [&] only while the captured variables are guaranteed to outlive the lambda, or you get a dangling reference.
Why does my C++ lambda say it can't modify a captured variable?
By-value captures are const inside the lambda by default. Add the mutable keyword - [x]() mutable { x++; } - to let the lambda change its own copy. Note this only changes the lambda's copy, not the original variable outside.