Lambda expressions in C++ provide us a concise and flexible way to define anonymous functions inline by eliminating the need for separate function declarations and making code more readable and maintainable. Lambda expressions are commonly used for functional programming, multithreading, event-driven programming, and operations on the Standard Template Library (STL).
In this blog, we will discuss C++ lambda expressions with their types, when to use lambda expressions, common mistakes and best practices for using lambda expressions, passing arguments to lambda expressions, their use with std::function and STL, and mutable lambda expressions.
Table of Contents:
What is a Lambda Expression in C++?
A lambda expression is an anonymous function in C++ that can be defined inline without requiring a separate function declaration. It is commonly used in sorting, filtering, and modifying collections. Since it can eliminate the need for function pointers, it makes code more readable and maintainable. Also, lambda expression is widely used in multithreading, event-driven programming, and functional-style operations, and it helps to write cleaner, more expressive, and efficient C++ code.
Syntax:
[capture](parameters) -> return_type { function_body; };
Where:
- [capture]: It specifies which variables from the surrounding scope should be available inside the lambda.
- (parameters): It defines the input parameters.
- -> return_type: It specifies the return type of the lambda function.
- { function_body; }: It contains the actual code to be executed.
Example:
Output:
The code shows the basic implementation of the lambda expression. Here, greet is a lambda function that directly prints the output from the body, as no return type is provided in the code.
Components of a Lambda Expression in C++
A lambda expression in C++ has a few components that define its behavior and functionality. Below, you are going to learn about the main components that are widely used:
1. Capture Clause ([ ])
The capture clause in the lambda expression is used to describe how the external variables from the surrounding scope are accessed within the lambda. It allows the lambda to directly use the external variables without passing them as function parameters.
Common Capture Methods:
- [ ] (Empty): In this method, no variables are captured as the lambda cannot access external variables.
- [=]: When you use this method, it captures all the surrounding scope variables by value.
- [&]: In this method, all surrounding scope variables are captured by reference.
- [var]: This method captures only var by value.
- [&var]: This method captures only var by reference.
- [=, &var]: It captures all variables by value except var, which is captured by reference.
- [&, var]: It captures all variables by reference except var, which is captured by value.
Example:
Output:
Here, Lambda1 ([=]) captures x and y variables with their values and then prints x + y = 30 but does not change the original variables, as Lambda2 ([&]) captures x and y by reference and changes x (x += y) to 30. Then you get the final output is 30 from lambda1 and 30 again from printing x, since x was changed by lambda2.
2. Parameter List
The parameter list in a lambda expression is similar to the parameter list of a regular function, as it defines the inputs that the lambda function can easily accept. It is enclosed in parentheses () after the capture clause. Also, if no parameters are required, it can be left empty.
Example:
Output:
In this code, the lambda takes two integer parameters a and b, and when multiply(4, 5) is called, it returns 4 * 5 = 20. This shows how lambda expressions accept parameters like regular functions.
3. Return Type (-> type)
The return type of a lambda expression in C++ can be directly specified using the -> type syntax. It can be useful when the return type cannot be easily determined by the compiler or if there are many return types. Also, in C++, explicit return types improve code clarity and ensure correctness in complex lambda expressions.
Example:
Output:
This code shows a lambda function divide with an explicit return type double, converts a to double before division, and the output is 3.33333 when dividing 10 by 3.
4. Lambda Body ({ })
The lambda body is enclosed within { } and contains the logic that will be executed when the lambda function is called. The lambda body works like a normal function body, and it can have several statements, variable declarations, and return statements. If the lambda expression returns a value, the return statement inside the body should match its return type.
Example:
Output:
The lambda sum takes two integers as parameters, and inside the function body {}, it declares a local variable result to store the sum and returns the computed value, which is printed in main().
When Should You Use Lambda Expressions in C++?
You can use the lambda expressions in many conditions. Below are a few points when you should use lambda expressions in C++:
- You can use lambda expressions when there is no need for a separate function declaration for the short and one-time use functions.
- You can use lambda expressions to make code more readable, clear, and efficient.
- Also, lambda expressions can be used in multithreading, custom comparators(used to define custom sorting orders), and capturing variables in scope.
- You can use lambda expressions in sorting, filtering, and condition-based operations in STL containers.
- Use lambda expressions when you are dealing with the inline functions, as it is an easy way to define them.
- You can use lambda expressions to improve the concurrency and parallelism in the programs in C++.
- You can use lambda expressions when you need to use local variables inside a function without directly passing them as arguments.
Common Mistakes While Using Lambda Expressions in C++
Below are a few points that describe the common mistakes while using lambda expressions in C++:
- A holding reference can result from incorrectly captured variables by references without ensuring that they would be accessible in lambda expressions.
- Changing captured variables by reference that are not mutable inside the lambda can create errors since they are immutable.
- Capturing larger objects by value in the lambda expressions might be inefficient and increase memory usage unnecessarily.
- Returning a reference to a local variable from inside a lambda leads to undefined behavior.
- If a lambda captures shared resources without synchronization, a race condition can arise.
- Accessing global variables directly inside lambda expressions can lead to errors and poor maintainability.
- Lambda expressions with captures cannot be converted to function pointers directly, and it can lead to compilation errors.
Best Practices for Using Lambda Expressions in C++
- You should capture only the necessary variables and use [=] carefully to avoid unnecessary copies.
- Always define the return types using -> type to prevent deduction errors when there are multiple return statements with different types of lambda expressions.
- You should avoid using complex or multi-line lambda expressions when the logic of the code is too big.
- Always use lambda expressions for callbacks, algorithms, and event handlers, but avoid using them for complex, reusable logic.
- You should mark the captured variable as mutable when a lambda modifies the captured variables to avoid undefined behavior.
- Prefer the use of lambda expressions or template-based function parameters to prevent additional overhead.
- You must keep the lambda expressions inline when a lambda expression is performing only a short, single-operation task like sorting or filtering.
- To improve performance, mark the lambda expression as constexpr when a lambda function can be evaluated at compile time.
How to Pass Arguments to Lambda Expressions in C++
There are multiple ways to pass the arguments to Lambda expressions in C++. Below are the main methods to pass the arguments to lambda expressions:
Syntax for passing arguments:
[Capture Clause](Parameter List) -> Return Type { Function Body ;};
Method 1: Passing by Value
When the arguments in lambda expressions are passed by value, then the changes done inside the lambda expressions do not affect the variables outside.
Example:
Output:
The code shows how a lambda expression is passed with two integer arguments by value, and when the function is called, it adds the values, and then the output is printed.
Method 2: Passing Arguments by Reference
When the arguments in lambda expression are passed by reference, then it allows the changes or modifications outside the lambda expression of the original variables. Also, it provides efficient memory management, thus avoiding creating unnecessary copies for large objects.
Example:
Output:
The code shows that the original values of a and b are modified inside the lambda expression since x and y are passed by reference.
Method 3: Passing Arguments by Constant Reference
When the arguments are passed by constant reference to lambda expressions, then it ensures that the copies of large objects are not created because this improves efficiency while preventing modification of the original data. This method is useful when values are to be read without changing them in any way.
Example:
Output:
The code shows that x and y are passed by constant reference (const &), and no modification can be done to variable values inside the lambda expressions.
Method 4: Passing Arguments with Default Values
In lambda expressions, when an argument is not provided to compile the code more efficiently, then an alternate default value is assumed and used.
Example:
Output:
The code shows that when multiply(5, 3) is called, it uses both arguments (5 and 3), when multiply(5) is called, it defaults b to 2.
Method 5: Using Variadic Arguments (...)
In most cases, the number of arguments is unknown until run-time, and using the variadic arguments allows lambda expressions to take a variable number of parameters, just like the variadic templates used in function declarations.
Example:
Output:
The code shows how the lambda printArgs uses variadic template syntax auto... args to accept any number of arguments of any type, and the fold expression (std::cout << ... << args) expands to print all arguments one by one.
Using Lambda Expressions with Standard Template Library(STL) in C++
Lambda expressions are commonly used with the Standard Template Library (STL) to provide a simple mechanism for operations like sorting, filtering, and transformation. This allows concise, inline function definitions directly for the algorithms like std::sort, std::for_each, and std::find_if.
Example:
Output:
The code shows how the lambda function inside std::sort is used to compare the elements in descending order, thus simplifying the sorting logic without defining a separate function for it.
Using Lambda Expressions with std::function in C++
The std::function is a very flexible wrapper function that works like a pointer function but allows a pushback operation to be stored and called with a specific signature. Code becomes much more reusable and efficient with std::function used in conjunction with lambda expressions as it provides the storage of lambda expressions in variables for later usage.
Example:
Output:
This code shows how the std::function is used to store a lambda function to add two integers a+b.
Using a Lambda as a Callback in C++
Using lambda as a callback in C++ helps us to pass the inline, anonymous functions as an argument to another function. It is used in event handling and asynchronous programming in C++. Using the lambda expressions as a callback function improves code readability and efficiency.
Example:
Output:
The code shows how a lambda function is passed as a callback to another function. Also, the process() takes an integer and a function pointer parameter, which prints the received values.
Mutable Lambda Expressions in C++
Lambda expressions in C++ capture variables as const when passed by value, which means that they cannot be modified inside the lambda body by default. Then, the mutable keyword is used to allow the modification of captured variables inside the lambda body.
Example:
Output:
The code shows that the mutable keyword allows count to be modified inside the lambda expression, and since count is captured by value, changes inside the lambda expression do not affect the original variable outside.
Conclusion
Lambda expression is a very powerful feature in C++ as it provides concise, efficient, and inline definitions of the functions. It is used to improve the redundancy, readability, and clarity of the code. Understanding the use of lambda expressions, its methods, and best practices provides us with more efficiency and maintainability.
FAQs
1. What is a lambda expression in C++?
A lambda expression is an anonymous function in C++, which can be defined inline without declaring the function separately. Lambda expression is commonly used in sorting, filtering, and altering collections of elements.
2. Can lambda expressions modify captured variables?
Lambda captures variables as const when passed by value; thus, to modify captured variables, a lambda, the mutable keyword has to be specified inside the body of the lambda.
3. Can lambda expressions be used with the Standard Template Library (STL)?
Yes, lambda expressions are commonly used with STL algorithms such as std::sort, std::for_each, and std::find_if to define inline functions for these algorithms.
4. What are common mistakes when using lambda expressions?
The common mistakes are capturing variables incorrectly, modifying non-mutable captured variables, inefficiently capturing large objects by value, and returning references to local variables.
5. When should I use lambda expressions in C++?
You can use lambda expressions in C++ when defining small, one-time-use functions, working with STL algorithms, handling event-driven programming, or multithreading operations.