66 Intermediate The Hundred
The Lambda That Outlived Its Capture
the counter that lost count
#include <iostream>
auto make_counter() {
int count = 0;
return [&count] { return ++count; };
}
void log_progress() {
int steps[8] = {11, 22, 33, 44, 55, 66, 77, 88};
std::cout << " (progress: step " << steps[7] << ")\n";
}
int main() {
auto next = make_counter(); // a fresh counter: 1, 2, 3, ...
std::cout << next() << "\n";
log_progress();
std::cout << next() << "\n";
std::cout << next() << "\n";
}
Run it. Does it count 1, 2, 3?
Answer
No. Typical output (GCC on x86-64): the same garbage number three times — 32767,
32767, 32767 — and a slightly different number on the next run. This is
undefined behavior: on another setup it may even appear to work.
Why¶
[&count] stores a reference — under the hood, a pointer — to a variable living in
make_counter's stack frame. The moment make_counter returns, that frame is gone, but
the lambda survives in main, still pointing at the dead slot; every next() is now a
read-modify-write of reclaimed memory — undefined behavior. Worse, ordinary calls like
log_progress and operator<< reuse that same stack region between reads, which is why
the counter doesn't even advance: each ++count increments freshly restomped garbage.
GCC 11 compiles this without a peep, even with -Wall -Wextra. AddressSanitizer catches
it at runtime, though: -fsanitize=address plus ASAN_OPTIONS=detect_stack_use_after_return=1
reports stack-use-after-return inside the lambda's operator(). Capturing this has
the same failure mode with members — that's entry 78.
The fix¶
Give the closure its own copy — capture by value, or drop the local entirely with an
init-capture (mutable lets the lambda modify what it owns):
return [count]() mutable { return ++count; }; // copy of the local
return [count = 0]() mutable { return ++count; }; // init-capture: no local at all
Takeaway: a lambda that outlives the current scope must never capture locals by reference — if the closure needs it later, the closure must own it.
Try it: g++ -std=c++17 main.cpp -o demo && ./demo