Lambda Expressions

Course 2 · Ch 7
Lambda Expressions
cpp1-6's own operator() mechanism, generated automatically by the compiler

cpp2-3's std::sort works with any callable — this chapter introduces the concise way real C++ code writes one inline, and reveals it's built from a mechanism already covered.

Lambda Syntax

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

[capture](params) { body } — a lambda passed directly as std::sort's custom comparator, exactly cpp2-3's own algorithm-plus-callable pattern.

Capture Lists

The real new concept: how a lambda accesses variables from its surrounding scope.

  • [] — captures nothing
  • [=] — captures everything used, by value (a copy)
  • [&] — captures everything used, by reference
  • [x] — captures just x, by value
  • [&x] — captures just x, by reference

What a Lambda Actually Is — A Class With operator()

The real mechanism, connecting directly back to cpp1-6: a lambda is compiler-generated syntactic sugar for an anonymous class, with captured variables as member fields and an overloaded operator() containing the lambda's body. Not a new language feature at its core — cpp1-6's own mechanism, applied automatically. Capturing by value copies into those member fields at the point the lambda is created, not when it's called — a real, sometimes-surprising timing detail.

// [x](int y) { return x + y; } is roughly sugar for: class __lambda { int x; // captured member field, copied at creation time public: __lambda(int x_) : x(x_) {} int operator()(int y) { return x + y; } };

std::function

A type-erased wrapper capable of holding any callable matching a given signature — a lambda, a function pointer (c2-7's own material), or a class with operator(). Useful because every lambda genuinely has its own unique, compiler-generated type, even when two lambdas look identical in source — std::function gives them a common type to be stored or passed around as.

std::function<int(int, int)> op = [](int a, int b) { return a + b; };

Contrasted With Rust's Own Closures

Rust closures work via the same underlying idea — the compiler generates a hidden struct capturing referenced variables, implementing one of the Fn/FnMut/FnOnce traits, genuinely comparable to operator(). The real difference: Rust's own capture rules are inferred automatically from how the closure body actually uses each variable (by reference, mutable reference, or move), rather than requiring an explicit capture-list syntax the way C++ does. A genuine ergonomic difference — but the underlying "anonymous callable object" mechanism is, once again, a real point of convergence, alongside RAII/Drop and monomorphization.

ConceptC++ LambdaRust Closure
Underlying mechanisman anonymous class with operator()a hidden struct implementing Fn/FnMut/FnOnce
Capture specificationexplicit — [x], [&x], [=], [&]inferred automatically from usage
Each closure's typeunique, unnameable — needs std::function or autounique, unnameable — needs a generic bound or Box<dyn Fn>
Capture only what's needed, by the narrowest means
Prefer [x] over [=], and [&x] over [&] — both a performance consideration (avoiding unnecessary copies) and a safety consideration (fewer captured references means fewer chances of a dangling reference).
A reference-capturing lambda that outlives its captured variable dangles
A lambda capturing by reference ([&] or [&x]) that's stored and called after the enclosing function has returned holds a dangling reference — exactly cpp1-8's own dangling-reference warning, reached through a lambda's capture instead of an explicit reference variable.

Coding Challenges

Challenge 1

Use std::sort with a lambda comparator to sort a std::vector in descending order, then print the result.

📄 View solution
Challenge 2

Write a lambda that captures a local int by value and one that captures the same variable by reference. After creating both lambdas, change the original variable's value, then call both lambdas and explain why they produce different results.

📄 View solution
Challenge 3

Explain precisely why a lambda is described as "not a new language feature at its core," tying your answer directly back to cpp1-6's own operator overloading material and what a lambda actually compiles down to.

📄 View solution

Chapter 7 Quick Reference

  • [capture](params) { body } — lambda syntax; used directly with cpp2-3's own algorithms
  • []/[=]/[&]/[x]/[&x] — capture nothing, everything by value, everything by reference, or specific variables
  • A lambda is compiler-generated sugar for a class with operator()cpp1-6's own mechanism, automated
  • By-value captures copy at lambda creation time, not call time
  • std::function — a type-erased wrapper for any callable, since every lambda's real type is unique and unnameable
  • Reference-capturing lambdas that outlive their captured variables dangle — the same bug class as cpp1-8
  • Next chapter: const-correctness and modern C++ style — closing Course 2