C++ Lambda Functions
Introduction to Lambdas
Lambda functions (or lambda expressions) are anonymous, inline function objects defined where they are used.
Introduced in C++11, they provide a concise way to define function objects without creating a separate named function or functor.
Basic Lambda Syntax
Components:
- Capture: Which surrounding variables are available inside
- Parameters: Like regular function parameters (optional)
- Return type: Often omitted; deduced automatically (in C++11 it must be explicit unless the body is a single return statement)
- Body: The actual logic
[capture](parameters) -> return_type {
// body
}
// Common optional pieces:
// - 'mutable' to allow modifying by-value captures
// - 'noexcept' / 'constexpr' (C++17+ implicit constexpr when possible)
// - template parameter list (C++20):
// [capture]<typename T>(T x) { /*...*/ }
Simple Lambda Example
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> numbers = {4, 2, 5, 1, 3};
// Sort using a lambda
std::sort(numbers.begin(), numbers.end(),
[](int a, int b) { return a < b; });
// Print using a lambda
std::for_each(numbers.begin(), numbers.end(),
[](int n) { std::cout << n << ' '; });
return 0;
}
1 2 3 4 5
Capture Clauses
Capture Type | Syntax | Description |
---|---|---|
By value (default all used) | [=] | Copies each used variable at lambda creation time; NOTE: this is implicitly captured (as a pointer), not copied |
By reference (default all used) | [&] | Binds each used variable by reference; variables must outlive the lambda |
Specific by value / reference | [x, &y] | Captures x by value, y by reference |
Mixed defaults | [=, &x] or [&, x] | Default plus exceptions; the default must appear first |
None | [] | Captures nothing; only parameters and globals may be used |
Init-capture (C++14+) | [p = std::move(ptr)] | Creates a new captured variable from an expression (great for move-only types) |
Capture this | [this] / [*this] (C++17+) | [this] captures the this pointer; [*this] captures a copy of the current object |
Mutable Lambdas
int main() {
int count = 0;
// 'mutable' allows modifying copies of captured-by-value variables
auto counter = [count]() mutable {
++count;
std::cout << count << ' ';
return count;
};
counter(); // 1
counter(); // 2
std::cout << "\nOriginal count: " << count; // Still 0
return 0;
}
1 2 Original count: 0
Return Type Deduction
C++14 introduced generic lambdas (auto parameters) and init-capture.
- Return type is usually deduced automatically.
- In C++11, omit the return type only if the body is a single return statement; otherwise use a trailing return type (e.g., -> double
).
- C++20 allows a template parameter list on lambdas for more control.
auto add = [](auto a, auto b) { return a + b; }; // generic lambda (C++14)
int sum1 = add(5, 3); // 8
double sum2 = add(2.5, 3.7); // 6.2
Real-World Use Cases
1. Standard Algorithms: Predicates and comparators
2. Event Handlers / Callbacks: Small inline actions
3. Concurrency: Tasks for threads or async operations
4. Deferred Execution: Store work for later (e.g., in containers)
5. Scope Guards: Automatic cleanup logic
#include <thread>
#include <iostream>
int main() {
int value = 42;
// Prefer capturing by value for detached/asynchronous work
std::thread t([value]() {
std::cout << "Hello from thread! Value = " << value << '\n';
});
t.join();
return 0;
}
Conversions & Object Model
Each lambda has a unique, unnamed closure type. Non-capturing lambdas are convertible to function pointers; capturing lambdas are not (but can be stored in std::function
with type-erasure).
Best Practices
1. Keep lambdas concise and focused on a single task
2. Prefer explicit, minimal capture lists over broad [=] or [&]
3. Use 'mutable' only when necessary; understand it affects only by-value captures
4. For complex logic or reuse, prefer named functions/functors
5. Always consider variable lifetimes, especially with reference captures and concurrency