Lambda Expression in C++
Lambda expressions are inline anonymous functions i.e. they do not have a name. Lambda has various parts such as capture clause, parameters, keywords, body, exceptions, etc. There are three ways in which we can capture external variables to our lambda expression. Lambda and functors are very similar to each other. Lambdas are compact versions of functors. Lambdas are very useful while writing STL algorithms.
Use Cases of Lambda Function in C++ with Standard Functions
Lambda functions in C++ are in-place functions that can be written for short code snippets that are not intended to be reused, so there is no need to name them as well. It was introduced in Modern C++ beginning from C++11. Writing Lambda expression instead of the function wherever necessary makes the code compact, clean, and easy to understand.
We can define lambda expression locally where we want to pass it to a function as an argument or where we are required to call it. They are anonymous function objects which are used widely for writing in-line functions.
Let us see how we can create and use Lambda Expression
Syntax
A lambda consists of the capture clause, parameters, return type, and body of the method.
- capture clause - it is a list of variables that are to be copied inside the lambda function in C++. We can also use it to initialize variables.
- parameters - zero, one or more than one argument to be passed to the lambda at execution time.
- mutable - Mutable is an optional keyword. It lets us modify the value of the variables that are captured by the call-by-value when written in the lambda expression.
- return type - It is optional as the compiler evaluates it. Though, in some complex cases compiler can't make out the return type and thus we need to specify it.
- body of the method - It is the same as the usual method definition. All the operations to be performed when the lambda expression is called are written here.
Let us see an example of the Lambda Expression.
Example:
Here is an example of a lambda function in C++ to understand the syntax and use. Let us take a list of numbers greater than 1 and separate the prime numbers from the list. Also, we will add them to another list using the lambda expression.
Output:
In the example above, we visit each element of the numbers vector. v1 is the list in which we are going to store prime numbers. We are capturing (so that we can use it in our lambda function) v1 by reference and the mutable keyword allows us to edit it as per the program's requirement. If the number is prime it is added to v1 and lastly, we print the components of the list.
Ways to Capture External Variables From Enclosing Scope
Lambda function in C++ is more powerful than the ordinary function. Ordinary functions can only use global variables or the variables passed to the function. Whereas lambda expressions can use local variables present in the main method as well as parameters passed to the lambda expression along with global variables. To use variables other than parameters, we use the capture clause.
There are three ways of capturing these external variables from the enclosing scope:
Capture by Reference
External variables can be captured using their reference to lambda expressions. We pass the address of the variable to the capture clause of the respective expression. This will refer to the original variable and thus changes will also reflect in the original one.
Syntax:
Here we are passing the reference of two variables num1 and num2 using the & symbol.
Capture by Value
We can also pass the values of the external variables to the lambda expression. Here we simply pass the variable name to the capture clause. Capturing by value means that we are effectively copying the value of the variable into a new variable that is found inside the lambda expression. The original variable is not affected by the lambda function in C++.
Syntax:
sum must be initialized before using in the capture clause. We are passing the value of the sum to the lambda expression.
Capture by Both (mixed capture)
We can pass more than one variable in the capture clause. Also, not all these variables need to be passed as values or passed by reference. Thus, we can pass both as well as the combination of the values and reference as well.
Syntax:
In the above code, we are passing id by reference and name by value. It can have multiple variables in the capture clause and they can be references and/or values.
Example:
Input:
Output:
In the example above, we are passing userpw by value and password by reference. It returns Login Successful as the password matches user input. checkPasscode is a name given to the lambda expression. It stores no value as our lambda expression doesn't return anything.
The Syntax Used for Capturing All Variables
To be able to capture all the variables and use them in our lambda expression we use & and =.
- [&] - It is used to capture all variables by reference. Here we are passing the address of the variable. Thus, if we make any changes to the variable in the lambda expression, it is reflected in the actual variable passed.
- [=] - It is used to capture all variables by value. The changes made to these values are not reflected in the actual variable. Here we are only passing the value of the variable. The original variable is not affected at all.
Note: when the capture clause is empty, i.e. [], then it can only access variables that are local to it.
Pros & Cons of Capture by Reference
When we capture variables by reference, it uses the address of the original variable, and thus, all the modifications are made on the variable value itself. Whereas, when we pass a value it creates another copy of the variable and modifications take place on it. Also, if a function returns a lambda function, we should not use capture-by-reference as the reference will not be valid.
More on the New Lambda Syntax
Return Values
Return type is mentioned after -> the arrow. If it is not mentioned, the compiler evaluates it on its own. In the case of complex programs, we are required to specify it. The return value must be returned inside the lambda function if not void.
Throw Specifications
Throw Specifications (also called Exceptions) are optional to mention in the lambda function in C++. We can specify which exceptions our lambda expression throws. As we are passing this to another function as an argument, this function expects only a certain set of exceptions to be thrown by lambda. It is written after a mutable keyword and after parameters in the absence of a mutable keyword.
Conclusion
- Lambda expressions are short code snippets that are not to be reused and act as an anonymous function.
- Lambda expression has various components such as capture clause, parameters, mutable and auto keywords, exceptions, return type, etc.
- The auto keyword enables us to assign a name to a lambda expression and also to make a generic lambda.
- In the capture clause, the creation and initialization of variables can be done. To edit these variables we use the mutable keyword.
- Lambda expressions and functors are very similar to each other. Lambdas are created for single use without a name and in the main method, unlike functors.
- Lambda's are written while implementing STL algorithms in C++. They make the code readable and compact.