5.2.22 · D1C++ Programming

Foundations — Lambda expressions — capture list (by value, by reference)

3,395 words15 min readBack to topic

Before you can read the parent note you must own about a dozen small ideas. This page builds each one from absolute zero, in the order they depend on each other. Nothing here uses a symbol before it is drawn.


1. int and friends — the basic types (what a box can hold)

The common built-in types you should recognise:

  • int → a whole number (the default number type).
  • double → a number with a decimal point (like 3.14).
  • char → a single character (like 'A').
  • bool → a truth value: true or false.

2. A variable is a labelled box in memory

Figure — Lambda expressions — capture list (by value, by reference)

Look at the figure: the name n is a sticky label, the box is a real location in memory (it has an address — a house number), and the value 10 is what currently sits inside. These are three different things, and the whole capture-list topic is about which of them a lambda grabs.

  • The name is how your code refers to the box.
  • The address is where the box physically is.
  • The value is what is inside right now — and it can change later (n = 99; swaps the contents).

3. = means "put into", not "is equal to"

This single distinction is what makes the parent's Example 1 (n=1 then n=99, lambda still prints 1) even possible to talk about: the box's contents were overwritten, but a copy taken earlier was not.


4. The address-of operator &n — "take the box's house number"

int n = 10;
&n;   // an expression: the address (location) of the box n

5. Scope — where a box exists, and when it dies

Figure — Lambda expressions — capture list (by value, by reference)

In the figure, the box x lives only inside the function make(). The moment make() returns, the whole shelf is cleared — x's box no longer exists. Anyone still holding the address (&x) of that box is now pointing at empty space.


6. A copy vs an alias (this is the whole topic)

Figure — Lambda expressions — capture list (by value, by reference)

The figure shows both side by side:

  • Left (copy): two separate boxes. Change the original to 99; the copy still shows 1. → This is capture by value [n].
  • Right (reference &): one box, two labels. Change through either label and both "see" 99. → This is capture by reference [&n].

7. auto — "compiler, you name the type"

auto add = [](int a, int b){ return a + b; };  // its type is unnameable -> auto

8. struct and brace-initialization — the box that holds many boxes

struct Point {      // blueprint: two boxes bundled together
    int x;
    int y;
};
Point p{3, 4};      // make a Point; x gets 3, y gets 4 (brace-initialization)

This matters because the next section's functor — and every lambda — is exactly such a struct, and its captured variables are its members, filled in with brace-init when the lambda object is born.


9. A function that lives inside a value: the functor

struct Adder {
    int base;                                         // a member box
    int operator()(int x) const { return x + base; }  // call syntax: a(5)
};
Adder a{10};   // brace-init: base gets 10 now
a(5);          // 15  -- looks like a function call, is really a.operator()(5)

10. WHEN the copy happens — at creation, not at call

Figure — Lambda expressions — capture list (by value, by reference)

Read the timeline in the figure left to right:

  1. int n = 1; — the box n holds 1.
  2. auto f = [n]{ return n; };creation: the copy is taken here, so the member box inside f is set to 1 and frozen.
  3. n = 99; — this overwrites the outside box only; the member copy inside f was already made and is untouched.
  4. f()call: it reads its own member box, which still says 1.

11. const and mutable — the read-only lock and its key

This is the reason the parent says a by-value capture is read-only. The compiler generates the lambda's operator() const by default, so its member copy cannot be touched.

Figure — Lambda expressions — capture list (by value, by reference)

The figure shows the generated closure struct: members on top (the captures), a const operator() below. A padlock sits on the members because of const; the word mutable is the key that removes it.


12. The this pointer — capturing the enclosing object

struct Widget {
    int value = 5;
    auto getReader() {
        return [this]{ return value; };  // [this] -> lambda can see value
    }
};

13. Generalized captures — init-capture and move-capture (C++14)

int y = 7;
auto f = [x = y + 1]{ return x; };   // member x starts at 8
auto data = std::make_unique<int>(10);
auto job = [p = std::move(data)]{ return *p; };  // ownership moved into the lambda
// data is now empty; p inside the lambda owns the int

14. Putting the notation together


Prerequisite map

int and basic types

variable = named box

assignment = overwrite the box

address-of and n = get the box location

reference = second name for a box

copy = new independent box

scope = when the box dies

auto = compiler picks type

lambda

struct and brace-init

functor = object you can call

const member = read only promise

mutable = the key that unlocks copies

capture by reference

capture by value at creation

dangling reference trap

this pointer

init and move capture

Everything on the left is a "box" idea; everything feeding H (the lambda) is what the parent page silently assumes you already own.


Equipment checklist

Recall Self-test: am I ready for the parent note?

What does the type word int tell the compiler? ::: That the box is a whole-number box (an integer) — its size and the kind of value it holds. What are the three separate things a variable has? ::: A name (label), an address (where it lives), and a value (what is inside now). What does n = 99; actually do? ::: It overwrites the box named n with the value 99 (assignment, a command — not equality). What does the expression &n produce? ::: The address (memory location) of the box n — used by by-reference capture to remember where the box lives. The symbol & has two jobs — what are they? ::: Before a name (&n) it means "address of"; glued to a type (int&) it means "reference / second name for a box". What is a reference int&? ::: A second name for an existing box; no new box is made, and writing through it changes the original. How is a copy different from a reference? ::: A copy is a brand-new independent box; changing the original never affects it. What is scope, and what happens at the closing }? ::: The region where a box is alive; at } the box is destroyed and any saved address to it becomes invalid. WHEN does a by-value capture copy the variable — at creation or at call? ::: At creation (when the lambda object is built), once; that frozen copy is why n=1; n=99; still prints 1. Why must you write auto for a lambda's type? ::: Its real type is a compiler-generated name you cannot write out, so auto lets the compiler fill it in. What is a struct, and what does a{10} do? ::: A struct bundles several member boxes under one name; a{10} builds the object and fills its members in order (brace-initialization). What is a functor? ::: An object with operator() so it can be called like a function; captures become its members. Why is a by-value capture read-only by default, and what fixes it? ::: The generated operator() is const, forbidding member changes; the keyword mutable cancels that const so the private copy becomes writable. Does mutable change the original outside variable? ::: No — it only unlocks the lambda's internal copy; the original is untouched. What does [this] capture, and what does [=] do about the object? ::: [this] stores the address of the current object so the lambda can reach its members; [=] in a member function also captures this (not a copy) — use [*this] to copy the object. What does [x = std::move(q)] do? ::: An init/move-capture: it builds a fresh member x by moving ownership of q into the lambda (C++14), leaving q empty. Decode [=, &b]. ::: Capture everything used by value (copy) except b, which is captured by reference.