Lambda Functions and Wrappers in C++
Learn via video course
Overview
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.
Lambda Function in C++
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 on its own. In case of complex programs, we are required to specify it. Return value must be returned inside the lambda function if not void.
Throw Specifications
Throw Specifications (also called Exceptions) are optional to mention in lambda function in C++. We can specify which exceptions our lambda expression throws. As we are passing this to another function as an argument, thus this function expects only a certain set of exceptions to be thrown by lambda. It is written after mutable keyword and after parameters in absence of mutable keyword.
The Auto Keyword
Auto keyword in C++ lets us assign a name to a lambda function. We can call this lambda function using its name just like we call variables by their names. We can also use the auto keyword in passing arguments to the lambda expression. This enables us to perform certain operations on multiple data types. It is also called generic lambda. We use the auto keyword in place of the data type.
For example:
Output:
In the above example, add is a lambda expression. auto before add gives a name to the lambda expression. Thus, using this name we can call it multiple times. auto before a and b parameters allows us to pass data of different types. The compiler deduces the type of data based on the data provided.
Initialize Member Variables and Mutable Keyword
In the capture clause, we can create or initialize variables. Also, we can capture existing external variables. To be able to modify any variable that is passed to lambda expression using capture clause, we are supposed to write mutable keyword after parameters list in () parenthesis. It is optional, thus when the capture clause is empty or we don't want to change the variables of the capture clause we can skip writing mutable in the lambda expression.
Let us look at an example to understand the concept better.
Output:
In the example above, the value variable is created and initialized in the capture clause. Its value is initialized as 10. We add value to x and change the value to 18. This is possible as we are using the mutable keyword after parameters in lambda expression else it throws an exception. Lastly, we print the values of the value and x variable.
Immediately Invoke a C++ Lambda (IIFE)
Since lambdas are anonymous expressions that are to be used and forgotten. It is a good way to directly execute code without populating the global namespace if the code is not dependent on any conditions.
Output:
In the above example, we have student details that are present in different variables. It is to be grouped in a format and printed. Lambda function is declared, defined, and called using (), right after } (closing curly bracket). Since the function is to be executed only, it is directly called after defining. This is also known as immediately invoking lambda in C++.
Note: [=] in the above code captures all the external variables by value.
Lambdas vs. Functors
Basically, functors are not functions, they are function objects. Objects of a class in C++ that defines the operator() method (also referred to as call operator or the application operator) are called functors. Objects of this class look like a function as they end with () parenthesis. A functor is advantageous over normal functions as it can support multiple independent states, one for each functor instance, while functions only support a single state.
Lambdas are anonymous function objects that are widely used for writing in-place functions. These are not reused as well as not named.
Let us look at an example to understand the similarities and differences between the two. We will take a functor to encode a given integer value and we will create a lambda function to decode the encoded value.
Output:
In the example above MyFunctor is a functor. It is a class that extends operator(), and takes x as a parameter. It performs some operations on it to encode the value and returns it.
To decode the encoded value in x, we use the variable y to store the result. We use a lambda expression for the calculation of the original value.
Both the functor and lambda expressions are very much similar to each other. The only difference is lambda doesn't have a name and its code is in simplified form. We can use functors at multiple places as well as they store state whereas lambdas cannot be used more than once.
Functors are objects of classes so they are a little more complex than lambdas. Also, their implementation lies with the class and it is used in the main() method whereas lambda expression is implemented in the main() method itself.
Lambda and the STL
STL algorithms operate on container elements like vectors, arrays, stacks, etc. using function objects, these function objects are passed as an argument to the algorithms.
For example:
Output:
The above code prints the colors whose length is greater than or equal to 5.
In the example above we are passing an anonymous function object with implementation directly to the STL function. Without Lambda expression, we will have to create a function class, the object of that function class and then we can pass a single line function to the STL. Rather, lambda not only makes the code compact but also reduces the complexity of the program with the same functionality.
Applications of Lambdas with Appropriate Example
Applications of lambda expression:
- Lambda expressions are lightweight, readable, and compact.
- Lambdas improve the locality of the code, functors are written far away from the place they are called.
- Lambdas allow generic implementation of the function i.e. we can define a general function and use it for multiple data types like int, float, double, etc.
- Using lambda we can overload the same function multiple times in the main method itself.
- Lambdas are good at storing the temporary state of a variable.
Let us take an example of a list of employees, we will check the hours they worked a week. If they worked more than 68 hours, then we will add incentives of 20 percent on their salary.
Output:
- employee is an array, the first column stores the employee id, the second column stores the number of hours they worked, and the third column stores the initial salary.
- Using lambda expression we will check the hours worked by each employee and add incentives if the working hours are more than 68 hours a week. We are capturing employee by the reference, the lambda expression doesn't take any parameters and it also doesn't return any value. It makes changes to the employee table thus mutable keyword is used.
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 generic lambda.
- In the capture clause, creation and initialization of variables can be done. To edit these variables we use the mutable keyword.
- Lambda expressions and functors are very much 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.