Visual walkthrough — Lambda expressions — capture list (by value, by reference)
5.2.22 · D2· Coding › C++ Programming › Lambda expressions — capture list (by value, by reference)
Yeh parent idea ki picture-derivation hai the capture-list topic mein.
Step 1 — Ek variable bas memory mein ek labelled box hai
KYA. Kisi bhi lambda ke exist karne se pehle, hamare paas ek plain local variable hai.
int n = 10;WHY yahan se shuru karein. "By value" vs "by reference" ki poori kahani yeh hai ki ek number physically kahan rehta hai. Hum ek box ko copy karne ya box ki taraf point karne ki baat tab tak nahi kar sakte jab tak hum box ko dekh nahi lete.
PICTURE. Figure dekho: n function ke stack frame mein ek chhota box hai. Label n bas ek naam hai jo insaan use karta hai; box khud 10 pattern hold karta hai. Uski location — ise address kaho (box ka street number) — woh cheez hai jise hum baad mein copy karenge ya point karenge.
- ::: woh naam jo hum code mein type karte hain
- the box ::: actual memory jo store karti hai
- the address ::: box ki location, jise references yaad rakhenge

Step 2 — Lambda secretly ek struct build ho raha hota hai
KYA. Jab tum lambda likhte ho, compiler ek hidden class (closure type) invent karta hai aur uska ek object banata hai.
auto f = [/* capture */](int x){ return x + n; };WHY. Ek function ki apne calls ke beech mein koi memory nahi hoti. Lekin is lambda ko n yaad rakhna hai. Kisi callable se memory attach karne ka ek hi tarika hai — usse fields wala ek object banana, yaani ek struct. Toh compiler tumhare liye ek likh deta hai. Capture list decide karti hai ki andar kaun se fields jaate hain.
PICTURE. Figure mein do cheezein side by side hain: tumhari ek line of code, aur woh hidden struct jo compiler generate karta hai. Struct mein ek special method operator() hai — woh code jo tab run hota hai jab tum f(...) likhte ho. Upar wali khali jagah woh hai jahan captures agle steps mein land honge.

Step 3 — Capture by value [n]: compiler struct mein box ko copy kar deta hai
KYA. [n] likhne se struct mein ek int n; field add ho jaata hai aur creation time par current value copy ho jaati hai.
int n = 10;
auto f = [n](int x){ return x + n; }; // copy YAHAN bani, n hai 10Generated struct:
WHY by value yahan. Hum chahte hain ki lambda portable aur safe ho — ek self-contained cheez jo apna data khud carry kare. Ek copy yahi achieve karti hai: struct ab ek bilkul alag box ka owner hai.
PICTURE. Mint arrow follow karo: value 10 outer box se ek naye, doosre box mein flow karti hai jo struct f ke andar rehta hai. Ab do boxes 10 hold karte hain. Is moment se dono ka koi rishta nahi.

Step 4 — Original badlo: by-value copy bilkul nahi hilti
KYA. Copy banne ke baad hum outer variable ko mutate karte hain.
n = 99; // sirf OUTER box edit hota hai
std::cout << f(0); // 10 print hoga, 99 nahiWHY yeh case important hai. "Snapshot" ka poora matlab yahi hai. Copy Step 3 mein hui thi jab n 10 tha. n = 99 assign karna outer box mein likhta hai; struct ka inner box kabhi usse connected nahi tha.
PICTURE. Coral arrow outer box mein 99 likhta hai. Inner box (andar f ke) abhi bhi 10 read karta hai — koi arrow usse touch nahi karta. Jab f(0) run hoga, term by term:

Step 5 — Capture by reference [&n]: struct address store karta hai, copy nahi
KYA. [&n] likhne se ek int& n; field add hoti hai — ek reference, jo usi box ka doosra naam hai.
int n = 10;
auto g = [&n](int x){ return x + n; }; // struct &n (the address) store karta haiGenerated struct:
WHY reference yahan. Kabhi kabhi hum chahte hain ki lambda live original se baat kare — uski latest value padhe ya use change kare. Reference exactly yahi karta hai: uske paas apna koi data nahi hota, bas yeh yaad rehta hai ki asli data kahan hai. Compare karo references vs pointers se — reference pointer ka polite twin hai: same "ek box ki taraf point karo" idea, * ki zaroorat nahi.
PICTURE. Is baar koi doosra box nahi banta. Balki ek lavender arrow struct se nikal ke outer box ki taraf wapas point karta hai. Struct ek signpost hai, warehouse nahi.

Step 6 — Original badlo: by-reference lambda turant dekh leta hai
KYA. Outer variable mutate karo, phir lambda call karo.
n = 99;
std::cout << g(0); // 99 print hogaWHY. g ne kabhi 10 store nahi kiya. Usne box tak pahunchne ka raasta store kiya. g ke andar n padhna matlab hai "arrow follow karo, jo bhi abhi wahan hai woh padho." Humne abhi usi box mein 99 likha.
PICTURE. Coral arrow outer box mein 99 likhta hai; g ka lavender signpost abhi bhi usi box ki taraf point karta hai, isliye ab woh 99 read karta hai. Term by term:

Step 7 — mutable case: by-value copy ko original touch kiye bina edit karna
KYA. Default mein operator() const hota hai, isliye by-value field read-only hoti hai. mutable ise unlock karta hai.
int n = 10;
auto c = [n]() mutable { n++; return n; };
c(); // 11
c(); // 12 (inner box calls ke beech persist karta hai)
n; // 10 (outer box kabhi nahi hila)WHY yeh edge case. Log assume karte hain "maine n naam rakha, toh main n change kar sakta hoon." Lekin copy ko change karna tabhi legal hai jab tum mutable se const guard hata do. Tab bhi, tum struct ke apne box ko edit karte ho, outer wale ko kabhi nahi — unke beech koi arrow nahi hai (Step 3 ne unhe tod diya tha).
PICTURE. ++ sirf inner box par act karta hai: do calls mein 10 → 11 → 12. Outer box 10 par untouched baitha hai. Dekho const member functions — operator() par wahi const hai jo edit forbid karta hai jab tak tum mutable nahi bolte.

Step 8 — Degenerate case: reference apne box se zyada jeeta hai (dangling)
KYA. Ek by-reference lambda us function ke bahar return ho jaata hai jiske local variable ki taraf woh point karta hai.
auto make() {
int x = 42;
return [&x]() { return x; }; // BUG
}
auto bad = make();
bad(); // undefined behaviour — box chala gayaWHY yeh sabse important case hai. Step 5 ne sikhaya tha ki reference bas ek address hai. Jab make() return karta hai, uski stack frame tod di jaati hai — us address par woh box demolish ho jaata hai. bad ke andar ka signpost ab ek malbhe ki taraf point karta hai. Yeh directly object lifetime and scope se juda hai aur sabse zyada threads aur async ke saath matter karta hai, jahan lambdas aksar us code se zyada jeete hain jinhone unhe spawn kiya. Compare karo std::function se jo aise closure ko kaafi der baad bhi store karta hai jab uski birthplace khatam ho chuki hoti hai.
PICTURE. Left frame: make() ke andar, box x = 42 exist karta hai aur arrow valid hai. Right frame: make() return kar chuka hai, frame grey ho gaya hai, box chala gaya hai, aur arrow khali jagah ki taraf point kar raha hai — crash.
Fix. By value capture karo ([x] ya [=]) taaki value closure ke andar zinde rahe aur uske saath travel kare — demolish karne ke liye koi arrow nahi.

Ek picture ka summary
Upar sab kuch ek contrast mein collapse ho jaata hai: value box ko andar copy karti hai; reference ek arrow bahar point karta hai. Copy portable lekin frozen hoti hai. Arrow live hota hai lekin tab tak hi valid hai jab tak uska box khada hai.

Recall Feynman retelling — poora walkthrough simple words mein
Ek box n se shuru karo jo 10 hold karta hai (Step 1). Jab tum lambda likhte ho, compiler chupke se ek chhota robot banata hai — ek struct jisme ek "do it" button hai, operator() (Step 2). Ab sawaal yeh hai ki robot kya carry karta hai. Agar tumne [n] likha, toh compiler 10 ki ek copy robot ke andar ek pocket mein daal deta hai (Step 3). Bahar wale n ko 99 karo aur robot kaandhey uchhkata hai — uski pocket mein abhi bhi 10 hai (Step 4). Agar tumne [&n] likha, toh robot koi number nahi carry karta, bas ek note jisme likha hai "number wahan hai" (Step 5). Bahar wale n ko 99 karo aur robot ab 99 report karta hai, kyunki woh har baar box dobara padhta hai (Step 6). Default mein robot ka button const sealed hai, isliye woh apni pocket copy par scribble nahi kar sakta — mutable kaho aur ab woh kar sakta hai, lekin sirf apni copy par, real box par kabhi nahi (Step 7). Ek deadly trap: agar robot ka note ek aisi box ki taraf point karta hai jo uske function ke khatam hone par demolish ho jaati hai, toh robot ek aisi ghar ki directions hold kar raha hai jo ab exist nahi karti — unhe follow karo aur crash (Step 8). Isey saath le jaana ho? Asli cheez apni pocket mein rakh lo (value). Ghar par rehna ho aur freshest value chahiye? Note rakho (reference).
[n] copy ke baad, outer box aur struct ka inner box connected hain?
Ek [&n] closure physically kya store karta hai?
operator() ka const hona by-value edits ke liye kyun relevant hai?
[n]() { n++; } illegal hai jab tak tum mutable add nahi karte.Kya mutable tumhe original captured variable change karne deta hai?
Returned lambda mein dangling reference kyun hoti hai?
[&x] ek local ka address store karta hai; jab function return karta hai uski frame destroy ho jaati hai, isliye address reclaimed memory ki taraf point karta hai (UB).