thecodex.expert · The Codex Family of Knowledge
C++

Lambda Expressions

A lambda that captures a local variable by reference and then outlives that variable’s scope is a real, genuinely dangerous dangling-reference bug — not a hypothetical one.

C++20 / C++23 [=] copies, [&] references Last verified:
Canonical Definition

[capture](parameters) { body } defines an anonymous, inline function object. The capture clause is C++'s distinguishing feature here: [=] captures every used outer variable by value (a copy, taken at the moment the lambda is created), [&] captures by reference (accessing the actual outer variable, which can change later), and specific names like [x, &y] mix the two explicitly per-variable. This forces a deliberate choice most languages with implicit closures don't require — and getting it wrong, particularly capturing by reference a variable that goes out of scope before the lambda is called, produces a genuine dangling-reference bug.

Basic lambda syntax

[](int a, int b) is the capture clause (empty here — nothing from the outer scope is used) followed by the parameter list, just like an ordinary function.

C++basic_lambda.cpp
auto add = [](int a, int b) { return a + b; };
std::cout << add(3, 4) << "\n";   // 7

std::vector<int> nums = {5, 2, 8, 1};
std::sort(nums.begin(), nums.end(), [](int a, int b) {
    return a > b;   // descending order — passed directly as the comparator
});

[=] vs [&]: capture by copy or by reference

[=] captured threshold's VALUE at the moment the lambda was created — later changing threshold has no effect on the already-captured copy; [&] would instead see the live, current value each time the lambda runs.

C++capture_modes.cpp
int threshold = 10;

auto byValue = [=](int x) { return x > threshold; };   // COPIES threshold at creation time
auto byRef = [&](int x) { return x > threshold; };      // references the LIVE threshold

threshold = 100;
std::cout << byValue(50) << "\n";   // true — still using the OLD copy (threshold was 10)
std::cout << byRef(50) << "\n";       // false — sees the CURRENT threshold (100)

The real danger: dangling references

badLambda captured x BY REFERENCE, but x no longer exists once makeLambda returns — calling badLambda() later reads memory that's already been reclaimed, genuine undefined behavior, not a hypothetical concern.

C++dangling_capture.cpp
std::function<int()> makeLambda() {
    int x = 42;
    return [&]() { return x; };   // captures x BY REFERENCE — x is a LOCAL variable
}   // x is destroyed here, when makeLambda returns

auto badLambda = makeLambda();
// badLambda();   // UNDEFINED BEHAVIOR — x no longer exists

// the fix: capture by VALUE instead, when the lambda might outlive the current scope
// return [=]() { return x; };   // this copy of x survives

Sources

1
ISO/IEC 14882 (C++ Standard), "Lambda expressions," cppreference.com/w/cpp/language/lambda.