Introduction
C++ Lambdas (C++11/14) are used widely for writing in-place functions and generally enhances the functionality of C++. Though in-place anonymous function definition is relatively new to C++ as compared to many other languages, it is extremely easy to make use of once understood properly
Background
C++ lambdas owing to its syntax is bit tedious to understand at first even for experienced C and C++ programmers. while for people with experience in languages like JavaScript, Python, etc., it's easy to understand the concerpt but still the syntax is a bit tricky.
This article is expected to clear the clouds on usage of C++ lambdas.
Section 1: Lambda Syntax
The syntax of lambda consists of 3 items:
- [] Open Close square brackets
- () Open Close round brackets
- {} Open Close curly brackets
This statement is syntactically valid C++ lambda and will compile successfully:
int main() {
[](){};
return 0;
}
while it may look weired in the beginning, we'll discuss capture and argument list ([]()) later. Let's first understand how to make lambda functional.
The functional block can be added in lambda by writing code in between open and close curly brackets as we do for any other functions in C/C++.
int main() {
[]() {
cout << "Lambda calling this cout" << endl;
}
return 0;
}
The lambda does have functionality but it will not call itself automatically and we need to explicitly call it. There are two ways to call a lambda:
- In Place calling
- Call by giving a name to it
We'll see both versions in this article, first the in place calling which is similar to calling any function by using ( )
.
int main()
{
[]() {
cout << "Lambda calling this cout" << endl;
}();
return 0;
}
Similarly, function parameter can be passed inside lambda by indicating these parameters inside ( )
. Remember while calling in place we need to provide the parameter argument, otherwise a compiler error will be generated.
int main()
{
[](int val) {
cout << "The value passed in this function is ->" << val << endl;
}(100);
return 0;
}
Section 2: Giving Name to a Lambda Function
The lambdas can be given a name so that they can be used at any later point of time. Only for anonymous lambdas, usage has to be done in-place as described in Section 1.
There are 3 possible ways to give lambda a name.
2.1: Using C++ Auto Keyword
The lambda can be simply assigned a name by using C++ auto keyword. By far, this is the simplest way of assigning a name to a lambda.
int main()
{
auto lfn = []() {
cout << "This lambda is called using the names" << endl;
};
lfn();
return 0;
}
2.2: Using std::function<>
The "auto
" keyword automatically generates the function pointers. However, we can explicitly use std::function<>
to hold that function pointer. The code snippet below shows usage of std::function<>
. The function below takes integer as function argument.
int main()
{
std::function<void(int)> lfnfunc = [](int val) {
cout << "The value passed in this function is ->" << val << endl;
};
lfnfunc(200);
return 0;
}
2.3: Using Plain 'C' Style Function Pointers
Lambdas are just functions and can be assigned to a 'C' style function pointers. Though I'll seriously discourage you to use this:
int main()
{
void(*cstylefp)(int) = [](int val) {
cout << "The value passed in this function is ->" << val << endl;
};
cstylefp(300);
return 0;
}
Section 3: Lambda Return Types
Just like normal functions, lambdas can also return values. The return value is provided after round brackets and before curly brackets. The return types are specified with Arrow (->) sign in lambdas.
int main()
{
int retval = []() -> int { return (int)1; }();
return 0;
}
Just like the above, we can also give name to a lambda function with return types. As explained above, the best way to use the same is "auto
" keyword.
int main()
{
auto lfnParam = [](int val) ->int {
cout << "lambda takes and interger ->" <<
val << "<- and returns an Integer" << endl;
return val * 100;
};
int retval1 = lfnParam(100);
return 0;
}
Section 4: The lambda Capture List
The capture list is one of the unique properties of lambda and helps it behave like closures in other languages like JavaScript.
The capture list is mainly used to pass local variables to the lambda functions without explicitly using them as part of parameters list, i.e., inside round brackets.
The parameters can be passed by value, reference or a combination of both.
4.1: Passing the Locals by Value
Local variables can be passed into lambda function by specifying the same in the capture list. We can then access the variables inside the lambda function.
In the code below, we are accessing the local variables 'x
' and 'y
' inside the lambda function.
int main()
{
int x = 10, y = 20;
auto retVal = [x, y]() -> int
{
return x + y;
}();
cout << "retVal => " << retVal << "
x => " << x << " y => " << y << endl;
return 0;
}
4.2: Passing the Locals by Reference
Local variables can also be passed by reference in the capture list. If passed by reference, the update to local variables will be reflected outside lambdas.
In the code below, we are accessing local variable passed by reference and modifying the same:
int main()
{
int x = 10, y = 20;
auto retVal1 = [&x, &y]() -> int { x++; y++; return x + y; }();
cout << "retVal1 => " << retVal1 << "
x => " << x << " y => " << y << endl;
return 0;
}
4.3: Passing All Local Variables By Value (At once)
There are situations where all local variables need to be passed by value, we have a way where we can do the same without explicitly mentioning each one of them. We can do it via using '=
' sign in capture list as shown in the sample code below:
int main()
{
int x = 10, y = 20, z = 30;
auto retval2 = [=]() -> int { return x + y + z; }();
cout << "retVal2 =>" << retval2 << endl;
return 0;
}
4.4: Passing All Local Variables by Reference (At Once)
In situations where all local variables need to be passed by reference, we can do it via using '&
' sign in capture list as shown in the sample code below:
int main()
{
int x = 10, y = 20, z = 30;
auto retval3 = [&]() -> int
{ x++; y++; z++;
return x + y + z;
}();
cout << "retVal3 =>" << retval3 << "
x => " << x << "
y =>" << y << " z=> " << z << endl;
return 0;
}
4.5: Passing Some Local Variables by Value and Some by Reference
The variables can be passed in capture list in mixed and match way, where some variables can be passed by value and some by Reference as depicted in code below:
int main()
{
int x = 10, y = 20;
auto retval4 = [x, &y]() -> int
{ y++;
return x + y;
}();
cout << "retVal4 =>" << retval4 << "
x => " << x << " y =>" << y << endl;
return 0;
}
Note 1: We cannot modify pass by value variables, i.e., we can't do x++;
in the above example.
Note 2: lambda capture works only for local scope and cannot be used for global variables.
Points of Interest
The lambdas especially the capture lists make some task easy, especially when you want to achieve some functionality in nested function.
History