In C + + 11, a lambda expression (often referred to as a "lambda") is an easy way to define an anonymous function object at the location that is called or passed as a parameter to the function. LAMBDA is typically used to encapsulate a small number of lines of code passed to an algorithm or asynchronous method. This article defines what Lambda is, compares lambda with other programming techniques, describes its advantages, and provides a basic example.
Parts of a LAMBDA expression
The ISO C + + standard shows a simple lambda passed to the Std::sort () function as a third parameter:
#include <algorithm>
#include <cmath>
void abssort(float* x, unsigned n) {
std::sort(x, x + n,
// Lambda expression begins
[](float a, float b) {
return (std::abs(a) < std::abs(b));
} // end of lambda expression
);
}
This diagram shows the components of a lambda:
The Capture clause, also known as a Lambda bootstrap in the C + + specification. )
Parameter list (optional). (also known as a lambda declarator)
Variable specification (optional).
Exception specification (optional).
Trailing return type (optional).
"Lambda Body"
Capture clause
Lambda can introduce a new variable in its body (with c++14), and it can also access (or "capture") variables within the perimeter range. Lambda starts with a capture clause (a lambda bootstrap in standard syntax) that specifies which variables to capture and whether to capture them by value or by reference. Variables with the number (&) prefix are accessed by reference, and variables without that prefix are accessed by value.
An empty capture clause [] indicates that the body of a lambda expression does not access variables in the enclosing scope.
You can use the default capture mode (Capture-default in standard syntax) to indicate how to capture any external variables referenced in a lambda: [&] means all variables referenced by a reference are captured, and [=] means that they are captured by value. You can use the default capture mode, and then explicitly specify the opposite pattern for a specific variable. For example, if the lambda body accesses the external variable total by reference and accesses the external variable factor by value, the following capture clause is equivalent:
[&total, factor]
[factor, &total]
[&, factor]
[factor, &]
[=, &total]
[&total, =]
When using Capture-default, only the variables mentioned in the lambda are captured.
If the capture clause contains Capture-default&, then no capture in the identifier of the capture clause can take the form of & identifier. Similarly, if the capture clause contains capture-default=, the capture clause cannot be taken in the form of = identifier. Identifier or this cannot occur more than once in the capture clause. The following code snippet shows some examples.
struct S { void f(int i); };
void S::f(int i) {
[&, i]{}; // OK
[&, &i]{}; // ERROR: i preceded by & when & is the default
[=, this]{}; // ERROR: this when = is the default
[i, i]{}; // ERROR: i repeated
}
The capture heel ellipsis is a package extension, as shown in the following mutable parameter template example:
template<class... Args>
void f(Args... args) {
auto x = [args...] { return g(args...); };
x();
}
To use a lambda expression in the body of a class method, pass the this pointer to the CAPTURE clause to provide access to the methods and data members of the enclosing class. For an example that shows how to use a lambda expression with a class method, see "Example: Using a lambda expression in a method" in the example of a lambda expression.
When using the capture clause, it is recommended that you keep the following points in mind (especially when using a multi-threaded lambda):
Reference captures can be used to modify external variables, but value capture does not do so. (mutable allows you to modify the copy without modifying the original item.) )
A reference capture reflects an update of an external variable, but the value capture is not reflected.
A reference capture introduces a lifetime dependency, while a value capture has no life-time dependency. This is especially important when lambda is running asynchronously. If you capture a local variable by reference in an asynchronous lambda, the local variable will most likely disappear at the time the lambda runs, resulting in a run-time access violation.
Universal Capture (C++14)
In c++14, new variables can be introduced and initialized in the Capture clause without having them present in the enclosing scope of the lambda function. Initialization can be represented by any arbitrary expression, and the type of the new variable is deduced from the types generated by the expression. One benefit of this feature is that in c++14, you can capture only moving variables (such as std::unique_ptr) from the perimeter and use them in your lambda.
pNums = make_unique<vector<int>>(nums);
//...
auto a = [ptr = move(pNums)]()
{
// use ptr
};
Parameter list
In addition to capturing variables, lambda can also accept input parameters. The argument list (known as the lambda declarator in standard syntax) is optional and, in most respects, resembles the argument list of a function.
int y = [] (int first, int second)
{
return first + second;
};
In c++14, if the parameter type is generic, you can use the Auto keyword as the type descriptor. This tells the compiler to create the function call operator as a template. Each auto instance in the argument list is equivalent to a different type parameter.
auto y = [] (auto first, auto second)
{
return first + second;
};
A lambda expression can use another lambda expression as its argument. For more information, see "Higher-order Lambda expressions" in the sample lambda expression topic.
Because the parameter list is optional, the parameter is not passed to the lambda expression, and its lambda-declarator: does not contain exception-specification, trailing-return-type, or mutable , you can omit the empty brackets.
Variable specification
Typically, the function call operator of Lambda is const-by-value, but the use of the mutable keyword can be canceled. It does not generate a mutable data member. With a mutable specification, the body of a lambda expression can modify a variable captured by a value. Some examples later in this article show how to use mutable.
Exception specification
You can use the throw () exception specification to indicate that a lambda expression does not throw any exceptions. As with normal functions, if a lambda expression declares a C4297 exception specification and the lambda body throws an exception, the Visual C + + compiler generates a warning throw () as follows:
// throw_lambda_expression.cpp
// compile with: /W4 /EHsc
int main() // C4297 expected
{
[]() throw() { throw 5; }();
}
return type
The return type of the lambda expression is automatically deduced. You do not need to use the Auto keyword unless you specify a trailing return type. Trailing-return-type is similar to the return type portion of a normal method or function. However, the return type must be followed by the argument list, and you must include the Trailing-return-type keyword, preceded by the return type.
If the lambda body contains only one return statement or its expression does not return a value, you can omit the return type portion of the lambda expression. If the lambda body contains a single return statement, the compiler infers the return type from the type of the return expression. Otherwise, the compiler infers the return type to void. The following code sample fragment illustrates this principle.
auto x1 = [](int i){ return i; }; // OK: return type is int
auto x2 = []{ return{ 1, 2 }; }; // ERROR: return type is void, deducing
// return type from braced-init-list is not valid
A lambda expression can generate another lambda expression as its return value. For more information, see "Higher-order lambda expressions" in the example of a lambda expression.
LAMBDA body
The lambda body of a lambda expression (compound-statement in standard syntax) can contain any content that the body of a normal method or function can contain. Both the normal function and the body of the lambda expression can access the following variable types:
Capture variables from the enclosing scope, as described earlier.
Parameters
Declaring variables locally
Class data member (when declaring and capturing this within a class)
Any variable that has a static storage duration (for example, global variables)
The following example contains a lambda expression that explicitly captures the variable n by value and implicitly captures the variable m by reference:
// captures_lambda_expression.cpp
// compile with: /W4 /EHsc
#include <iostream>
using namespace std;
int main()
{
int m = 0;
int n = 0;
[&, n] (int a) mutable { m = ++n + a; }(4);
cout << m << endl << n << endl;
}
Output:
50
Since the variable n is captured by value, the value of the variable remains unchanged 0 after the lambda expression is called. The mutable specification allows modification of n in Lambda.
Although a lambda expression can only capture variables with automatic storage duration, you can use variables with static storage duration in the body of a lambda expression. The following example uses the Generate function and a lambda expression to assign a value to each element in a vector object. A lambda expression modifies a static variable to produce the value of the next element.
void fillVector(vector<int>& v)
{
// A local static variable.
static int nextValue = 1;
// The lambda expression that appears in the following call to
// the generate function modifies and uses the local static
// variable nextValue.
generate(v.begin(), v.end(), [] { return nextValue++; });
//WARNING: this is not thread-safe and is shown for illustration only
}
The following code example uses the function in the previous example and adds an example of a lambda expression that uses the STL algorithm generate_n. The lambda expression assigns the elements of a vector object to the sum of the first two elements. The mutable keyword is used so that the body of a lambda expression can modify a copy of the external variables x and y that the lambda expression captures by value. Because lambda expressions capture the original variables x and y by value, their values are still 1 after the lambda is executed.
// compile with: /W4 /EHsc
#include <algorithm>
#include <iostream>
#include <vector>
#include <string>
using namespace std;
template <typename C> void print(const string& s, const C& c) {
cout << s;
for (const auto& e : c) {
cout << e << " ";
}
cout << endl;
}
void fillVector(vector<int>& v)
{
// A local static variable.
static int nextValue = 1;
// The lambda expression that appears in the following call to
// the generate function modifies and uses the local static
// variable nextValue.
generate(v.begin(), v.end(), [] { return nextValue++; });
//WARNING: this is not thread-safe and is shown for illustration only
}
int main()
{
// The number of elements in the vector.
const int elementCount = 9;
// Create a vector object with each element set to 1.
vector<int> v(elementCount, 1);
// These variables hold the previous two elements of the vector.
int x = 1;
int y = 1;
// Sets each element in the vector to the sum of the
// previous two elements.
generate_n(v.begin() + 2,
elementCount - 2,
[=]() mutable throw() -> int { // lambda is the 3rd parameter
// Generate current value.
int n = x + y;
// Update previous two values.
x = y;
y = n;
return n;
});
print("vector v after call to generate_n() with lambda: ", v);
// Print the local variables x and y.
// The values of x and y hold their initial values because
// they are captured by value.
cout << "x: " << x << " y: " << y << endl;
// Fill the vector with a sequence of numbers
fillVector(v);
print("vector v after 1st call to fillVector(): ", v);
// Fill the vector with the next sequence of numbers
fillVector(v);
print("vector v after 2nd call to fillVector(): ", v);
}
Output:
vector v after call to generate_n() with lambda: 1 1 2 3 5 8 13 21 34
x: 1 y: 1
vector v after 1st call to fillVector(): 1 2 3 4 5 6 7 8 9
vector v after 2nd call to fillVector(): 10 11 12 13 14 15 16 17 18