Lambda expressions, which were introduced in C++11 and have been improved in more recent versions of C++, define function objects without requiring a class or a struct with a name. Recent amendments in C++20 add support for template arguments.
In this lesson, you find out
• How to program a lambda expression
• How to use lambda expressions as predicates
• What generic lambda expressions are
• How to program lambda expressions that can hold and manipulate state
A lambda expression can be visualized as a compact version of an unnamed struct (or class) with a public operator()
function. In that sense, a lambda expression is a function object. Before we get into programming lambda functions, let’s revisit a function object from Listing 21.1 (from Lesson 21, “Understanding Function Objects") as an example:
// struct that behaves as a unary function template <typename elementType> struct DisplayElement { void operator()(const elementType& element) const { cout << element << ’ ’; } };
This function object displays the object element
on the screen by using cout
. It would be used in algorithms such as std::for_each()
, as shown in this example:
// Display every integer contained in a vector for_each(numsInVec.cbegin(), // Start of range numsInVec.cend(), // End of range DisplayElement<int>()); // Unary function object
A lambda equivalent of struct DisplayElement
requires a single line of code:
auto lambda = [](const int& element) {cout << element << ’ ’; };
This is what it looks like when used with for_each()
:
// Display elements in vector using a lambda expression for_each(numsInVec.cbegin(), // Start of range numsInVec.cend(), // End of range lambda); // display elements
Tip
Lambda expressions are also called lambda functions.
A lambda expression always starts with square brackets ([]
). C++20 introduced new capabilities such as support for template arguments to lambda expressions, making them more generic and, hence, more powerful.
Lambda expression syntax can be generalized to:
[optional captured variables]<optional template arguments> optional-lambda-specifiers (arguments) { // lambda expression code; }
If you want to use variables that are declared outside the body of a lambda expression, you need to capture these variables. The capture list can include multiple variables separated by commas:
[var1, var2] <class Type> (Type& param) { // lambda code here; }
If you need to modify these variables within the lambda expression, you add the specifier mutable
:
[var1, var2] <class Type> (Type& param) mutable { // lambda code here; }
Caution
A mutable lambda expression allows captured variables ([...]
) to be modified within the lambda expression, but these changes are not reflected outside the expression.
If modifications to the captured variables within the lambda expression are required to be reflected outside it, too, then you use references:
[&var1, &var2] <class Type> (Type& param) { // lambda code here; }
Tip
In addition to mutable
, the other two specifiers that optionally enhance a lambda expression are constexpr
and consteval
. constexpr
indicates the intention to have the lambda evaluated as a constant expression where possible, whereas consteval
makes the lambda an immediate function to be evaluated by the compiler.
C++20 allows lambda expressions to be extremely generic—akin to function objects programmed using template classes. You may optionally specify a template parameter list, as shown here:
[var1, var2] <typename Type1, typename Type2> (Type1 param1, Type2 param2) { // lambda code here; }
In addition, lambda expressions permit multiple input parameters, separated by commas:
[var1, var2] <class Type> (Type param1, Type param2) { // lambda code here; }
With automatic type deduction using the keyword auto
, you can program a generic lambda expression that looks like this:
[var1, var2] (auto param1, auto param2) { // lambda code here; }
A lambda expression that comprises a single return
statement does not need to explicitly specify a return value type because the type is automatically deduced by the compiler. To explicitly declare a return type, you use ->
as follows:
[var1, var2] <class Type> (Type param1, Type param2) -> ReturnType { return (value or expression ); }
Finally, the compound statement {}
can hold multiple statements, each separated by a ;
, as shown here:
[stateVar1, stateVar2](Type1 var1, Type2 var2) -> ReturnType { Statement 1; Statement 2; return (value or expression); }
A simple lambda version of the unary operator(Type)
function that takes one parameter would be the following:
[](Type paramName) { // lambda expression code; }
Note that you can pass the parameter by reference if you so wish:
[](Type& paramName) { // lambda expression code here; }
Use Listing 22.1 to study the usage of a lambda function in displaying the contents of a Standard Template Library (STL) container using the algorithm for_each()
.
Input
0: #include <algorithm> 1: #include <iostream> 2: #include <vector> 3: #include <list> 4: using namespace std; 5: 6: int main() 7: { 8: vector<int> numsInVec{ 101, -4, 500, 21, 42, -1 }; 9: cout << “Display elements in a vector using a lambda: “ << endl; 10: 11: // Display the array of integers 12: for_each(numsInVec.cbegin(), // Start of range 13: numsInVec.cend(), // End of range 14: [](const int& element) {cout << element << ’ ’; }); // lambda 15: 16: cout << endl; 17: 18: list<char> charsInList{ ’a’, ’h’, ’z’, ’k’, ’l’ }; 19: cout << “Display elements in a list using a lambda: “ << endl; 20: 21: // Display the list of characters 22: for_each(charsInList.cbegin(), // Start of range 23: charsInList.cend(), // End of range 24: [](auto& element) {cout << element << ’ ’; }); // lambda 25: 26: return 0; 27: }
Output
Display elements in a vector using a lambda: 101 -4 500 21 42 -1 Display elements in a list using a lambda: a h z k l
Analysis
There are two lambda expressions of interest, in Lines 14 and 24. They do similar tasks and are the same except for the type of the input parameter. They have been customized to the nature of the elements within the two containers. The first expression takes one parameter that is an int
, as it is used to print one element at a time from a vector of integers’ the second expression accepts a char
(automatically deduced by the compiler) as it is used to display elements of type char
stored in a std::list
.
Tip
You might have noticed that the second lambda expression in Listing 22.1 is slightly different:
for_each(charsInList.cbegin(), // Start of range charsInList.cend(), // End of range [](auto& element) {cout << element << ’ ’; } ); // lambda
This lambda expression uses the compiler’s automatic type deduction capabilities, which are invoked using the keyword auto
. The compiler would interpret this lambda expression as
for_each(charsInList.cbegin(), // Start of range charsInList.cend(), // End of range [](const char& element) {cout << element << ’ ’; } );
Note
The code in Listing 22.1 is similar to that in Listing 21.1 except that the latter uses function objects. In fact, Listing 22.1 is a lambda version of the function object DisplayElement<T>
.
If you compare the two listings, you can see that lambda functions make C++ code simple and compact.
A predicate helps make decisions. A unary predicate is a unary expression that returns a bool
, conveying true
or false
. Lambda expressions can return values, too. For example, the following code is a lambda expression that returns true
for numbers that are even:
[](int& num) {return ((num % 2) == 0); }
The nature of the return value in this case tells the compiler that the lambda expression returns a bool
.
You can use a lambda expression that is a unary predicate in algorithms, such as std::find_if()
, to find even numbers in a collection. See Listing 22.2 for an example.
Input
0: #include<algorithm> 1: #include<vector> 2: #include<iostream> 3: using namespace std; 4: 5: int main() 6: { 7: vector<int> numsInVec{ 25, 101, 2017, -50 }; 8: 9: auto evenNum = find_if(numsInVec.cbegin(), 10: numsInVec.cend(), // range to find in 11: [](const int& num){return ((num % 2) == 0); } ); 12: 13: if (evenNum != numsInVec.cend()) 14: cout << “Even number in collection is: “ << *evenNum << endl; 15: 16: return 0; 17: }
Output
Even number in collection is: -50
Analysis
The lambda function that works as a unary predicate is shown in Line 11. The algorithm find_if()
invokes the unary predicate for every element in the range. When the predicate returns true
, find_if()
reports a find by returning an iterator evenNum
to that element. The predicate in this case is the lambda expression that returns true
when find_if()
invokes it with an integer that is even; that is, the result of a modulo operation with 2 is zero.
Tip
Listings 22.1 and 22.2 demonstrate not only a lambda expression as a unary predicate but also the use of const
within a lambda expression.
Remember to use const
for input parameters, especially when they’re references, to avoid unintentional changes to the value of elements in a container.
Listing 22.2 shows a unary predicate that returns true
if an integer is divisible by 2—that is, if the integer is an even number. What if you need a more generic function that returns true
when the number is divisible by a divisor of the user’s choosing? You need to maintain that “state”—that is, the divisor—in the expression:
int divisor = 2; // initial value ... auto element = find_if(begin of a range, end of a range, [divisor](int dividend){return (dividend % divisor) == 0; } );
A list of arguments transferred as state variables ([...]
) is also called the lambda’s capture list.
Note
The lambda expression shown above is a one-line equivalent of the 16 lines of code in Listing 21.3 that define the unary predicate struct IsMultiple<>
.
Listing 22.3 demonstrates the application of a unary predicate, given a state variable, in finding a number in a collection that is a multiple of a divisor supplied by the user.
Input
0: #include <algorithm> 1: #include <vector> 2: #include <iostream> 3: using namespace std; 4: 5: int main() 6: { 7: vector<int> numsInVec{25, 26, 27, 28, 29, 30, 31}; 8: cout << “The vector contains: {25, 26, 27, 28, 29, 30, 31}”; 9: 10: cout << endl << “Enter divisor (> 0): “; 11: int divisor = 2; 12: cin >> divisor; 13: 14: // Find the first element that is a multiple of divisor 15: vector<int>::iterator element; 16: element = find_if(numsInVec.begin(), 17: numsInVec.end(), 18: [divisor](int dividend){return (dividend % divisor) == 0; } ); 19: 20: if(element != numsInVec.end()) 21: { 22: cout << “First element in vector divisible by “ << divisor; 23: cout << “: “ << *element << endl; 24: } 25: 26: return 0; 27: }
Output
The vector contains: {25, 26, 27, 28, 29, 30, 31} Enter divisor (> 0): 4 First element in vector divisible by 4: 28
Analysis
The lambda expression that contains state and works as a predicate is shown in Line 18. divisor
is the state variable, and it is comparable to IsMultiple::Divisor
in Listing 21.3. Hence, state variables are akin to member attributes in a function object class. You are now able to pass states on to your lambda function and customize its usage on the basis of the state.
Note
Listing 22.3 features the lambda expression equivalent of Listing 21.4, without the function object class IsMultiple
. This lambda expression eliminated 16 lines of code!
A binary function takes two parameters and optionally returns a value. A lambda expression equivalent of a binary function would be
[...](Type1& param1Name, Type2& param2Name) { // lambda code here; }
Listing 22.4 shows a lambda expression that multiplies two equal-sized vectors element by element, using std::transform()
, and stores the result in a third vector.
Input
0: #include <vector> 1: #include <iostream> 2: #include <algorithm> 3: 4: int main() 5: { 6: using namespace std; 7: 8: vector<int> vecMultiplicand{ 0, 1, 2, 3, 4 }; 9: vector<int> vecMultiplier{ 100, 101, 102, 103, 104 }; 10: 11: // Holds the result of multiplication 12: vector<int> vecResult; 13: 14: // Make space for the result of the multiplication 15: vecResult.resize(vecMultiplier.size()); 16: 17: transform(vecMultiplicand.begin(), // range of multiplicands 18: vecMultiplicand.end(), // end of range 19: vecMultiplier.begin(), // multiplier values 20: vecResult.begin(), // range that holds result 21: [](int a, int b) {return a * b; } ); // lambda 22: 23: cout << “The contents of the first vector are: “ << endl; 24: for(size_t index = 0; index < vecMultiplicand.size(); ++index) 25: cout << vecMultiplicand[index] << ’ ’; 26: cout << endl; 27: 28: cout << “The contents of the second vector are: “ << endl; 29: for(size_t index = 0; index < vecMultiplier.size(); ++index) 30: cout << vecMultiplier[index] << ’ ’; 31: cout << endl; 32: 33: cout << “The result of the multiplication is: “ << endl; 34: for(size_t index = 0; index < vecResult.size(); ++index) 35: cout << vecResult[index] << ’ ’; 36: 37: return 0; 38: }
Output
The contents of the first vector are: 0 1 2 3 4 The contents of the second vector are: 100 101 102 103 104 The result of the multiplication is: 0 101 204 309 416
Analysis
The lambda expression in this example is shown in Line 21 as a parameter to std::transform()
. This algorithm takes two ranges as input and applies a transformation algorithm that is contained in a binary function (the lambda expression of interest). The return value of the binary function is stored in a target container. The lambda expression accepts two integers as input and returns the result of the multiplication via the return value. This return value is stored by std::transform()
in vecResult
. The output demonstrates the contents of the two containers and the result of multiplying them element by element.
Note
Listing 22.4 demonstrates the lambda equivalent of the function object class Multiply<>
from Listing 21.5.
A function that accepts two parameters and returns either true
or false
to help make a decision is called a binary predicate. Binary predicates find use in sort algorithms, such as std::sort()
, that invoke the binary predicate for any two values in a container to determine which one should be placed after the other. The generic syntax of a binary predicate is
[...](Type1& param1Name, Type2& param2Name) { // return bool expression; }
Listing 22.5 demonstrates a lambda expression being used with template parameters and another being used as a binary predicate with an explicit return type declaration.
Input
0: #include <algorithm> 1: #include <string> 2: #include <vector> 3: #include <iostream> 4: using namespace std; 5: 6: int main() 7: { 8: vector <string> namesInVe”{ “”im”, “J”ck”, “”am”, “A”na” }; 9: 10: // template lambda that displays object on screen 11: auto displayElement = []<typename T>(const T& element) 12: { cout << element ’<’’ ’;}; 13: 14: cout << “The names in vector in order of insertion: “ << endl; 15: for_each(namesInVec.cbegin(), namesInVec.cend(), displayElement); 16: 17: cout “< “ Order after case sensitive sort:” ”; 18: sort(namesInVec.begin(), namesInVec.end()); 19: for_each(namesInVec.cbegin(), namesInVec.cend(), displayElement); 20: 21: cout “< “ Order after sort ignoring case:” ”; 22: sort(namesInVec.begin(), namesInVec.end(), 23: [](const string& str1, const string& str2) -> bool // lambda 24: { 25: string str1LC, str2LC; // LC = lowercase 26: str1LC.resize(str1.size()); // create space to store result 27: str2LC.resize(str2.size()); 28: 29: // Convert strings (each character) to the lower case 30: transform(str1.begin(), str1.end(), str1LC.begin(),::tolower); 31: transform(str2.begin(), str2.end(), str2LC.begin(), ::tolower); 32: 33: return(str1LC < str2LC); 34: } // end of lambda 35: ); // end of sort 36: 37: for_each(namesInVec.cbegin(), namesInVec.cend(), displayElement); 38: 39: return 0; 40: }
Output
The names in vector in order of insertion: jim Jack Sam Anna Order after case sensitive sort: Anna Jack Sam jim Order after sort ignoring case: Anna Jack jim Sam
Analysis
Listing 22.5 is quite novel in that it uses a template lambda expression displayElement
, which is defined in Lines 11 and 12 and used by the algorithm for_each()
in Lines 15, 19 and 37. displayElement
is the lambda equivalent of struct DisplayContents<>
, which you have used multiple times (for instance, in Listing 18.2 to display elements in std::list
and in Listing 19.2 to display elements in std::set
). This one-line lambda expression is generic and powerful.
A rather large lambda expression also spans Lines 23 through 34 as the third parameter of std::sort()
! A lambda that spans multiple statements needs to explicitly declare the return value type (bool
), as shown in Line 23. This lambda expression implements a binary predicate that assists with case-insensitive sorting by first converting the two strings to be compared to the same case (in this example, to lowercase, as shown in Lines 30 and 31) and then comparing the converted strings with each other. This case-insensitive sort helps place "jim"
after "Jack"
, as shown in the output.
Note
This extraordinarily large lambda expression in Listing 22.5 is a lambda version of class CompareStringNoCase
from Listing 21.6, which is used in Listing 21.7.
In this lesson, you learned about an important feature that contributes to modern C++ programming: lambda expressions. You saw that lambda expressions are basically unnamed function objects that can take parameters, that have state, that return values, and that can be multiple lines. You learned how to use lambdas instead of function objects in STL algorithms, such as find()
, sort()
, or transform()
. Lambda expressions make programming in C++ fast and efficient, and you should try to use them where applicable.
Q. How are the state parameters of a lambda expression transferred: by value or by reference?
A. When a lambda expressions is programmed with a capture list, like this:
[var1, var2, ... varN](Type& param1, ... ) { ...expression ;}
the state parameters var1
and var2
are copied (that is, not supplied as a reference). If you want to have them as reference parameters, you use this syntax:
[&Var1, &Var2, ... &varN](Type& param1, ... ) { ...expression ;}
In this case, you need to exercise caution as modifications to the state variables supplied within the capture list continue outside the lambda expression.
Q. Can I use the local variables in a function in a lambda expression?
A. You can pass the local variables in the capture list:
[var1, var2, ... varN](Type& param1, ... ) { ...expression ;}
If you need to capture all variables, you use this syntax:
[=](Type& Param1, ... ) { ...expression ;}
The Workshop provides quiz questions to help you solidify your understanding of the material covered and exercises to provide you with experience using what you’ve learned. Try to answer the quiz and exercise questions before checking the answers in Appendix E and be certain you understand the answers before continuing to the next lesson.
1. How does a compiler recognize the start of a lambda expression?
2. How do you pass state variables to a lambda function?
3. If you need to supply a return value in a lambda expression, how do you do it?
1. Write a lambda binary predicate that helps sort elements in a container in descending order.
2. Write a lambda function that, when used in for_each()
, adds a user-specified value in a container such as a vector.
18.221.15.15