C++ Cheatsheet

Lambdas

Use this C++ reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Syntax

[ capture-list ] ( parameters ) specifiers -> return-type { body }

All parts except the capture list and body are optional.

auto greet = []() { std::cout << "hello\n"; };
greet();

auto add = [](int a, int b) { return a + b; };   // return type deduced
add(2, 3);   // 5

auto square = [](double x) -> double { return x * x; };  // explicit return type

Capture List

Captures give the lambda access to variables from the enclosing scope.

int x = 10, y = 20;

// Capture by value (copy made at lambda creation time)
auto f1 = [x]() { return x; };           // x captured by value
auto f2 = [=]() { return x + y; };       // all local variables captured by value

// Capture by reference
auto f3 = [&x]() { x = 99; };            // x captured by reference
auto f4 = [&]() { x = 1; y = 2; };       // all locals by reference (dangerous if lambda outlives scope)

// Mixed
auto f5 = [=, &x]() { return x + y; };   // everything by value, except x by ref
auto f6 = [&, x]() { return x + y; };    // everything by reference, except x by value

// Capture this (member variables)
struct Foo {
    int val = 42;
    auto getLambda() {
        return [this]() { return val; };           // capture this by pointer
        // return [*this]() { return val; };        // capture this by value (C++17)
        // return [=, this]() { return val; };      // C++20 (= no longer implies this)
    }
};

// Init capture / generalized capture (C++14) — create new variable in capture
int z = 5;
auto f7 = [w = z * 2]() { return w; };            // w = 10, stored in closure
auto f8 = [v = std::move(vec)]() { return v.size(); }; // move into lambda
auto f9 = [ptr = std::make_unique<int>(7)]() { return *ptr; };  // unique_ptr in lambda

mutable Lambdas

By default, value captures are const inside the body. Use mutable to allow modification (modifies the copy inside the closure, not the original).

int n = 0;
auto counter = [n]() mutable { return ++n; };  // n in closure incremented
counter();  // 1
counter();  // 2
// n in outer scope is still 0

// Without mutable:
auto bad = [n]() { return ++n; };  // compile error: n is const

Lambda as std::function and Template Parameter

#include <functional>

std::function<int(int, int)> f = [](int a, int b){ return a + b; };
f(2, 3);  // 5

// Prefer auto over std::function (no type erasure overhead)
auto g = [](int a, int b){ return a + b; };

// Pass to algorithm
std::vector<int> v = {3, 1, 4, 1, 5};
std::sort(v.begin(), v.end(), [](int a, int b){ return a > b; });  // descending

// Higher-order functions
auto applyTwice = [](auto f, auto x) { return f(f(x)); };
applyTwice([](int n){ return n * 2; }, 3);  // 12

Generic Lambdas (C++14)

auto parameters make a lambda a template:

auto add = [](auto a, auto b) { return a + b; };
add(1, 2);        // int
add(1.0, 2.0);    // double
add(std::string("a"), "b");  // string

// Explicit template parameter (C++20)
auto typed = []<typename T>(T a, T b) { return a + b; };
typed(1, 2);
typed(1.0, 2.0);

// Constrain with concepts (C++20)
auto intOnly = []<std::integral T>(T a, T b) { return a + b; };

Immediately Invoked Lambda Expression (IILE)

// Complex initialization in one expression
const int result = [&]() {
    if (x > 0) return x * 2;
    if (y > 0) return y * 3;
    return 0;
}();

// Init a const with a switch-like expression
const std::string name = [](int code) -> std::string {
    switch (code) {
        case 1: return "alpha";
        case 2: return "beta";
        default: return "unknown";
    }
}(statusCode);

Recursive Lambdas

Lambdas cannot refer to themselves by name directly. Use one of:

// Method 1: std::function (slower — type erasure)
std::function<int(int)> fib = [&fib](int n) -> int {
    return n <= 1 ? n : fib(n-1) + fib(n-2);
};
fib(10);

// Method 2: Pass self as parameter (C++14+)
auto fib2 = [](auto self, int n) -> int {
    return n <= 1 ? n : self(self, n-1) + self(self, n-2);
};
fib2(fib2, 10);

// Method 3: Explicit this parameter (C++23 deducing-this)
auto fib3 = [](this auto self, int n) -> int {
    return n <= 1 ? n : self(n-1) + self(n-2);
};
fib3(10);

noexcept and constexpr Lambdas

auto safe = []() noexcept { return 42; };
auto ce   = []() constexpr { return 42; };       // implicitly constexpr if possible (C++17)
constexpr auto val = ce();                        // evaluated at compile time

// Explicit constexpr (C++17)
auto f = [](int x) constexpr { return x * 2; };
static_assert(f(5) == 10);

Storing Lambdas and Type Deduction

// Each lambda has a unique, unnamed type — use auto
auto f = [](int x){ return x; };

// Store heterogeneous lambdas in std::function
std::function<void()> handlers[] = {
    []{ std::cout << "A\n"; },
    []{ std::cout << "B\n"; }
};

// std::function has overhead; for homogeneous sets, use function pointers if stateless
int (*fp)(int) = [](int x){ return x * 2; };  // stateless lambda → function pointer

// Type of lambda (for template, decltype)
auto myLambda = [](int x){ return x; };
using LT = decltype(myLambda);

Lambdas in STL Algorithms

#include <algorithm>
#include <vector>

std::vector<int> v = {1, -2, 3, -4, 5};
int threshold = 2;

// Remove negatives
std::erase_if(v, [](int x){ return x < 0; });  // C++20

// Sort by absolute value
std::sort(v.begin(), v.end(), [](int a, int b){ return std::abs(a) < std::abs(b); });

// Count elements above threshold (capture by value)
int cnt = std::count_if(v.begin(), v.end(), [threshold](int x){ return x > threshold; });

// Transform in-place
std::transform(v.begin(), v.end(), v.begin(), [](int x){ return x * x; });

// Reduce with custom accumulator
int product = std::accumulate(v.begin(), v.end(), 1, [](int acc, int x){ return acc * x; });

// Find first negative
auto it = std::find_if(v.begin(), v.end(), [](int x){ return x < 0; });

Common Gotchas

// 1. Dangling reference capture
auto bad_lambda() {
    int local = 5;
    return [&local]() { return local; };  // UB: local destroyed on return
}
// Fix: capture by value, or use init capture to move

// 2. Capturing this when object may be destroyed
struct Widget {
    std::function<void()> callback_;
    void setup() {
        callback_ = [this]{ use(val_); };  // dangerous if Widget is destroyed first
        // Fix: callback_ = [w = weak_from_this()]{ if (auto s = w.lock()) s->use(s->val_); };
    }
    int val_ = 0;
};

// 3. Default capture [=] in member functions captures 'this', not members directly
struct S {
    int x = 5;
    auto f() {
        return [=]{ return x; };   // actually captures 'this'; x is this->x
    }
};

// 4. Forgetting mutable for stateful value captures
int count = 0;
auto inc = [count]() { return ++count; };    // error: count is const
auto inc2 = [count]() mutable { return ++count; }; // OK: modifies closure copy