Lambdas are Anonymous Functions
A lambda expression (also called a lambda or closure) allows us to define an anonymous function inside another function. The nesting is important, as it allows us both to avoid namespace naming pollution, and to define the function as close to where it is used as possible.
The syntax for lambdas is one of the weirder things in C++:
[ captureClause ] ( parameters ) -> returnType
{
statements;
}
- The capture clause can be empty if no captures are needed.
- The parameter list can be empty is no parameters are required. It can also be omitted entirely unless a return type is specified.
- The return type is optional, and if omitted,
auto
will be assumed (thus using type deduction used to determine the return type).
Also note that lambdas (being anonymous) have no name, so we don't need to provide one.
A trivial lambda definition looks like:
#include <iostream>
int main()
{
[] {}; // a lambda with an omitted return type, no captures, and omitted parameters.
return 0;
}
Basic Syntax:
auto lambda = [](int a, int b) -> int {
return a + b;
};
- Here,
auto
is used to let the compiler deduce the lambda's return type and the lambda itself is defined between the square brackets ([]
). The parameter follow the lambda introducer, and the body is enclosed within curly braces.
[capture_clause](parameter_list) -> return_type {
// lambda body
}
- Capture Clause: Specifies which variables are accessible inside the lambda.
- Parameter List: The list of input parameters, similar to a function.
- Return Type: The return type of the lambda, If omitted, the return type is deduced by the compiler.
- Lambda Body: The code block containing the logic of the lambda.
Capture Clauses:
Capture clauses determine how variables from the enclosing scope are accessed inside the lambda. Here are the main capture clauses:
1. Capture by Value ([x]
):
Capturing by value makes a copy of the variable for using inside the lambda. Changes inside the lambda affect only the copy, not the original.
int x = 5;
auto lambdaByValue = [x](int y) {
// x is captured by value
return x*2;
};
Internal Representation:
The compiler generates a closure type with a member variable holding the copied value:
struct LambdaCaptureByValue {
int x; // copy of the original 'a'
LambdaCaptureByValue(int x) : x(x) {}
int operator()() const {return x * 2;}
};
2. Capture by Reference ([&x]
):
Capturing by reference allows the lambda to use the original variable. Any changes inside the lambda are reflected in the variable outside.
Example:
int x = 5;
// Explicit Capture by reference: only x is captured by
// reference
auto lambdaByValue = [&x](int y) {
// x is captured by reference
x *= 2;
return x;
};
Internal Representation:
The compiler creates a closure type with a reference member:
struct LambdaCaptureByReference {
int& x; // reference to the original 'x' variable
LambdaCaptureByReference(int& x) : x(x) {}
int operator()() const {return x + 5;}
};
3. Capture All Variables:
int x = 5, y = 10;
auto lambdaCaptureAll = [=]() {
// x and y are captured by value
};
4. Capture All Variables by Reference:
int x = 5, y = 10;
auto lambdaCaptureAllByReference = [&]() {
// x and y are captured by reference
};
Syntax | Description |
---|---|
[]() {} | No captures |
[=]() {} | Captures everything by copy (not recommended) |
[&]() {} | Captures everything by reference (not recommended) |
[x]() {} | Captures x by copy |
[&x]() {} | Captures x by reference |
[&, x]() {} | Captures x by copy; everything else by reference |
[=, &x]() {} | Captures x by reference; everything else by copy |
Generic Lambdas:
Starting from C++14, lambdas can be made generic by using the auto
specifier in the parameter list. This allows them to operate on a variety of types.
int x = 5, y = 10;
auto lambdaCaptureAllByReference = [&]() {
// x and y are captured by reference
};
Lambda Return Type Deduction:
If the return type of a lambda is not specified, the compiler can deduce it based on the return statements within the lambda body.
auto lambdaWithAutoReturnType = [](int a, int b) {
return a + b; // Compiler deduces the return type as int
};