Why Lambda Expression?
Consider the following statement:
int myInt = 52;Here, myInt is an identifier, an lvalue. 52 is a literal, a prvalue. Today, it is possible to code a function specially and put it in the position of 52. Such a function is called a lambda expression. Consider also the following short program:
#includeusing namespace std;
int fn(int par)
int answer = par + 3;
return answer;
int main()
fn(5);
return 0;
Today, it is possible to code a function specially and put it in the position of the argument of 5, of the function call, fn(5). Such a function is called a lambda expression. The lambda expression (function) in that position is a prvalue.
Any literal except the string literal is a prvalue. The lambda expression is a special function design that would fit as a literal in code. It is an anonymous (unnamed) function. This article explains the new C++ primary expression, called the lambda expression. Basic knowledge in C++ is a requirement to understand this article.
Article Content
- Illustration of Lambda Expression
- Parts of Lambda Expression
- Captures
- Classical Callback Function Scheme with Lambda Expression
- The trailing-return-type
- Closure
- Conclusion
Illustration of Lambda Expression
In the following program, a function, which is a lambda expression, is assigned to a variable:
#includeusing namespace std;
auto fn = [](int param)
int answer = param + 3;
return answer;
;
int main()
auto variab = fn(2);
cout << variab << '\n';
return 0;
The output is:
5Outside the main() function, there is the variable, fn. Its type is auto. Auto in this situation means that the actual type, such as int or float, is determined by the right operand of the assignment operator (=). On the right of the assignment operator is a lambda expression. A lambda expression is a function without the preceding return type. Note the use and position of the square brackets, []. The function returns 5, an int, which will determine the type for fn.
In the main() function, there is the statement:
auto variab = fn(2);This means, fn outside main(), ends up as the identifier for a function. Its implicit parameters are those of the lambda expression. The type for variab is auto.
Note that the lambda expression ends with a semicolon, just like the class or struct definition, ends with a semicolon.
In the following program, a function, which is a lambda expression returning the value of 5, is an argument to another function:
#includeusing namespace std;
void otherfn (int no1, int (*ptr)(int))
int no2 = (*ptr)(2);
cout << no1 << " << no2 << '\n';
int main()
otherfn(4, [](int param)
int answer = param + 3;
return answer;
);
return 0;
The output is :
4 5There are two functions here, the lambda expression and the otherfn() function. The lambda expression is the second argument of the otherfn(), called in main(). Note that the lambda function (expression) does not end with a semicolon in this call because, here, it is an argument (not a stand-alone function).
The lambda function parameter in the definition of the otherfn() function is a pointer to a function. The pointer has the name, ptr. The name, ptr, is used in the otherfn() definition to call the lambda function.
The statement,
int no2 = (*ptr)(2);In the otherfn() definition, it calls the lambda function with an argument of 2. The return value of the call, "(*ptr)(2)" from the lambda function, is assigned to no2.
The above program also shows how the lambda function can be used in the C++ callback function scheme.
Parts of Lambda Expression
The parts of a typical lambda function is as follows:
[] ()- [] is the capture clause. It can have items.
- () is for the parameter list.
- is for the function body. If the function is standing alone, then it should end with a semicolon.
Captures
The lambda function definition can be assigned to a variable or used as the argument to a different function call. The definition for such a function call should have as a parameter, a pointer to a function, corresponding to the lambda function definition.
The lambda function definition is different from the normal function definition. It can be assigned to a variable in the global scope; this function-assigned-to-variable can also be coded inside another function. When assigned to a global scope variable, its body can see other variables in the global scope. When assigned to a variable inside a normal function definition, its body can see other variables in the function scope only with the capture clause's help, [].
The capture clause [], also known as the lambda-introducer, allows variables to be sent from the surrounding (function) scope into the lambda expression's function body. The lambda expression's function body is said to capture the variable when it receives the object. Without the capture clause [], a variable cannot be sent from the surrounding scope into the lambda expression's function body. The following program illustrates this, with the main() function scope, as the surrounding scope:
#includeusing namespace std;
int main()
int id = 5;
auto fn = [id]()
cout << id << '\n';
;
fn();
return 0;
The output is 5. Without the name, id, inside [], the lambda expression would not have seen the variable id of the main() function scope.
Capturing by Reference
The above example use of the capture clause is capturing by value (see details below). In capturing by reference, the location (storage) of the variable, e.g., id above, of the surrounding scope, is made available inside the lambda function body. So, changing the value of the variable inside the lambda function body will change the value of that same variable in the surrounding scope. Each variable repeated in the capture clause is preceded by the ampersand (&) to achieve this. The following program illustrates this:
#includeusing namespace std;
int main()
int id = 5; float ft = 2.3; char ch = 'A';
auto fn = [&id, &ft, &ch]()
id = 6; ft = 3.4; ch = 'B';
;
fn();
cout << id << ", " << ft << ", " << ch << '\n';
return 0;
The output is:
6, 3.4, BConfirming that the variable names inside the lambda expression's function body are for the same variables outside the lambda expression.
Capturing by Value
In capturing by value, a copy of the variable's location, of the surrounding scope, is made available inside the lambda function body. Though the variable inside the lambda function body is a copy, its value cannot be changed inside the body as of now. To achieve capturing by value, each variable repeated in the capture clause is not preceded by anything. The following program illustrates this:
#includeusing namespace std;
int main()
int id = 5; float ft = 2.3; char ch = 'A';
auto fn = [id, ft, ch]()
//id = 6; ft = 3.4; ch = 'B';
cout << id << ", " << ft << ", " << ch << '\n';
;
fn();
id = 6; ft = 3.4; ch = 'B';
cout << id << ", " << ft << ", " << ch << '\n';
return 0;
The output is:
5, 2.3, A6, 3.4, B
If the comment indicator is removed, the program will not compile. The compiler will issue an error message that the variables inside the function body's definition of the lambda expression cannot be changed. Though the variables cannot be changed inside the lambda function, they can be changed outside the lambda function, as the above program's output shows.
Mixing Captures
Capturing by reference and capturing by value can be mixed, as the following program shows:
#includeusing namespace std;
int main()
int id = 5; float ft = 2.3; char ch = 'A'; bool bl = true;
auto fn = [id, ft, &ch, &bl]()
ch = 'B'; bl = false;
cout << id << ", " << ft << ", " << ch << ", " << bl << '\n';
;
fn();
return 0;
The output is:
5, 2.3, B, 0When all captured, are by reference:
If all variables to be captured are captured by reference, then just one & will suffice in the capture clause. The following program illustrates this:
#includeusing namespace std;
int main()
int id = 5; float ft = 2.3; char ch = 'A'; bool bl = true;
auto fn = [&]()
id = 6; ft = 3.4; ch = 'B'; bl = false;
;
fn();
cout << id << ", " << ft << ", " << ch << ", " << bl << '\n';
return 0;
The output is:
6, 3.4, B, 0If some variables are to be captured by reference and others by value, then one & will represent all the references, and the rest will each not be preceded by anything, as the following program shows:
using namespace std;int main()
int id = 5; float ft = 2.3; char ch = 'A'; bool bl = true;
auto fn = [&, id, ft]()
ch = 'B'; bl = false;
cout << id << ", " << ft << ", " << ch << ", " << bl << '\n';
;
fn();
return 0;
The output is:
5, 2.3, B, 0Note that & alone (i.e., & not followed by an identifier) has to be the first character in the capture clause.
When all captured, are by value:
If all variables to be captured are to be captured by value, then just one = will suffice in the capture clause. The following program illustrates this:
#includeusing namespace std;
int main()
int id = 5; float ft = 2.3; char ch = 'A'; bool bl = true;
auto fn = [=]()
cout << id << ", " << ft << ", " << ch << ", " << bl << '\n';
;
fn();
return 0;
The output is:
5, 2.3, A, 1Note: = is read-only, as of now.
If some variables are to be captured by value and others by reference, then one = will represent all the read-only copied variables, and the rest will each have &, as the following program shows:
#includeusing namespace std;
int main()
int id = 5; float ft = 2.3; char ch = 'A'; bool bl = true;
auto fn = [=, &ch, &bl]()
ch = 'B'; bl = false;
cout << id << ", " << ft << ", " << ch << ", " << bl << '\n';
;
fn();
return 0;
The output is:
5, 2.3, B, 0Note that = alone has to be the first character in the capture clause.
Classical Callback Function Scheme with Lambda Expression
The following program shows how a classical callback function scheme can be done with the lambda expression:
#includeusing namespace std;
char *output;
auto cba = [](char out[])
output = out;
;
void principalFunc(char input[], void (*pt)(char[]))
(*pt)(input);
cout<<"for principal function"<<'\n';
void fn()
cout<<"Now"<<'\n';
int main()
char input[] = "for callback function";
principalFunc(input, cba);
fn();
cout<