📑 Table of Contents
- 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.mutablelambdas 1.7. Generic lambdas (C++14) 1.8. Using lambdas with STL 1.9.std::movecapture (C++14/17) 1.10. Storing Lambdas instd::function1.11. UML-Style Diagram (Conceptual) 1.12. Best practices 1.13. Runnable C++ Examples
🎯 Main Topics Covered
- Quick overview
- Syntax
- Basic examples
- Capture modes
- Lambda in a Class Capturing this
- mutable lambdas
- Generic lambdas C14
- 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]() { ... } thiscapture — 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
autofor local lambdas; usestd::functiononly 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.