⏱ 5 min read

πŸ“‘ Table of Contents

  1. Lambda Functions in Modern C++ 1.1. Quick overview 1.2. Syntax 1.3. Basic examples 1.4. Capture modes 1.5. Lambda in a Class (Capturing this) 1.6. mutable lambdas 1.7. Generic lambdas (C++14) 1.8. Using lambdas with STL 1.9. std::move capture (C++14/17) 1.10. Storing Lambdas in std::function 1.11. UML-Style Diagram (Conceptual) 1.12. Best practices 1.13. Runnable C++ Examples

🎯 Main Topics Covered

  1. Quick overview
  2. Syntax
  3. Basic examples
  4. Capture modes
  5. Lambda in a Class Capturing this
  6. mutable lambdas
  7. Generic lambdas C14
  8. Using lambdas with STL … and 5 more

Lambda Functions in Modern C++

Lambda functions are essential for writing concise, expressive code β€” especially with STL algorithms(eg. sort), callbacks, event handeling, threading, and functional patterns.

This guide covers the main concepts, syntax, examples, properties, pitfalls, and best practices.


Quick overview

  • Anonymous function objects generated by the compiler.
  • Can capture local variables by value or reference.
  • Implemented as a compiler-generated class with an operator().
  • Available since C++11; features improved in C++14 / C++17 / C++20.

Syntax

[capture](/learning/programming-language/cpp/lambda-functions/parameters) mutable-exception -> return_type {
    body
};
  • capture β€” how the lambda accesses variables from the enclosing scope.

  • parameters β€” function parameter list (optional if none).

  • mutable β€” allows modifying captured variables that were captured by value.

  • -> return_type β€” optional trailing return type (often inferred).

  • The lambda expression produces a unique, unnamed closure type; assignable to auto.

Basic examples

No Capture

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

With parameters and inferred return

auto add = [](int a, int b) { return a + b; };
std::cout << add(2,3); // 5

With explicit return type

auto div = [](double a, double b) -> double {
    if (b == 0) throw std::runtime_error("div by zero");
    return a / b;
};

Capture modes

  • By value β€” captures copies of variables: [x, y] or [=] (capture all by value)
    int x = 10;
    auto f = [x]() { cout << x << endl; };  
    
  • By reference β€” captures references: [&x, &y] or [&] (capture all by reference)
    int x = 10;
    auto f = [&x]() { x++; };
    
  • Capture Everything by Value
    [=]() { ... }
    
  • Capture Everything by Reference
    [&]() { ... }
    
  • Mixed β€” e.g. [=, &z] (capture all by value, z by reference)
    [x, &y]() { ... }
    
  • this capture β€” capture current object pointer: [this] or use [=] (C++20 allows [=, this] semantics more explicitly)

Lambda in a Class (Capturing this)

class Test {
public:
    int x = 42;

    void run() {
        auto f = [this]() {
            cout << x;
        };
        f();
    }
};

Example

int a = 1;
int b = 2;

auto valCap = [a]() { return a + 10; };  // uses a copy
auto refCap = [&b]() { b += 5; };        // modifies original b

mutable lambdas

Captured-by-value variables are const inside the lambda by default. mutable makes the captured copy modifiable:

int x = 5;
auto f = [x]() mutable { x += 2; std::cout << x << "\n"; }; // prints 7
f();
std::cout << x << "\n"; // still 5

Generic lambdas (C++14)

Use auto in parameter lists to create templated lambdas:

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

Using lambdas with STL

std::vector<int> v = {3,1,4,1,5};
std::sort(v.begin(), v.end(), [](int a, int b){ return a < b; });
std::for_each(v.begin(), v.end(), [](int x){ std::cout << x << " "; });

std::move capture (C++14/17)

Capture move-only types (e.g., std::unique_ptr) into a lambda using move-capture:

auto p = std::make_unique<int>(42);
auto lam = [ptr = std::move(p)](){ std::cout << *ptr; }; // ptr moved into lambda

This is essential to capture move-only resources safely.

Storing Lambdas in std::function

std::function<int(int)> square = [](int x){
    return x * x;
};

UML-Style Diagram (Conceptual)

+------------------------------+
|        Lambda Object         |
+------------------------------+
| - capture list (values/refs) |
| - operator() (parameters)    |
+------------------------------+
| + invoked like a function    |
+------------------------------+

Internally, a lambda is a compiler-generated class with:

  • captured variables β†’ data members

  • body β†’ operator()

Example:

[x]() { return x + 1; }

Compiler generates something like:

class __Lambda_1 {
    int x_copy;
public:
    __Lambda_1(int x) : x_copy(x) {}
    int operator()() const { return x_copy + 1; }
};

This explains why capture types matter.

Best practices

  • Use auto for local lambdas; use std::function only if you need type-erasure or dynamic dispatch.

  • Prefer capture-by-value for safety if the lambda may outlive the local scope.

  • Use mutable sparingly; prefer explicit state in enclosing scope or return updated values.

  • Avoid heavy logic in lambdas β€” extract to named functions for clarity and testability.

  • Be explicit about captures when readability matters ([=] and [&] are concise but can hide intent).

  • For asynchronous or multi-threaded code, ensure captured objects live long enough.

Runnable C++ Examples

Full runnable .cpp examples are available here:

πŸ‘‰ For runnable C++ examples, see the accompanying Lambda Examples.

This includes:

  • Sorting with lambdas
  • Generic lambdas
  • Move-capture examples
  • Closure state counters

Use them to practice and reinforce concepts.