Lesson 22. Lambda Expressions

Lambda expressions are a compact way to define and construct function objects without a name. These expressions were introduced in C++11. In this lesson, you find out

Image How to program a lambda expression

Image How to use lambda expressions as predicates

Image What are C++14 generic lambda expressions

Image How to program lambda expressions that can hold and manipulate a state

What Is a Lambda Expression?

A lambda expression can be visualized as a compact version of an unnamed struct (or class) with a public operator(). In that sense, a lambda expression is a function object like those in Lesson 21, “Understanding Function Objects.” Before jumping into analyzing the programming of lambda functions, take a function object from Listing 21.1 (from Lesson 21) 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 an object element on the screen using cout and is typically used in algorithms such as std::for_each():

// 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 expression compacts the entire code including the definition of the function object into three lines:

// Display every integer contained in a vector using lambda exp.
for_each (numsInVec.cbegin (),    // Start of range
          numsInVec.cend (),      // End of range
          [](const int& element) {cout << element << ' '; } );

When the compiler sees the lambda expression, in this case

[](const int& element) {cout << element << ' '; }

it automatically expands this expression into a representation that is similar to struct DisplayElement<int>:

struct NoName
{
    void operator () (const int& element) const
    {
        cout << element << ' ';
    }
};


Tip

Lambda Expressions are also called Lambda Functions.


How to Define a Lambda Expression

The definition of a lambda expression has to start with square brackets[]. These brackets essentially tell the compiler that the lambda expression has started. They are followed by the parameter list, which is the same as the parameter list you would supply your implementation of operator() if you were not using a lambda expression.

Lambda Expression for a Unary Function

The lambda version of a unary operator(Type) that takes one parameter would be the following:

[](Type paramName) {  // lambda expression code here;  }

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 algorithm for_each().

LISTING 22.1 Displaying Elements in a Container via Algorithm for_each() That Is Invoked with a Lambda Expression Instead of a Function Object


  0: #include <algorithm>
  1: #include <iostream>
  2: #include <vector>
  3: #include <list>
  4:
  5: using namespace std;
  6:
  7: int main ()
  8: {
  9:    vector <int> numsInVec{ 101, -4, 500, 21, 42, -1 };
 10:
 11:    list <char> charsInList{ 'a', 'h', 'z', 'k', 'l' };
 12:    cout << "Display elements in a vector using a lambda: " << endl;
 13:
 14:    // Display the array of integers
 15:    for_each (numsInVec.cbegin (),    // Start of range
 16:              numsInVec.cend (),        // End of range
 17:              [](const int& element) {cout << element << ' '; } ); //  lambda
 18:
 19:    cout << endl;
 20:    cout << "Display elements in a list using a lambda: " << endl;
 21:
 22:    // Display the list of characters
 23:    for_each (charsInList.cbegin (),     // Start of range
 24:              charsInList.cend (),        // End of range
 25:              [](auto& element) {cout << element << ' '; } ); // lambda
 26:
 27:    return 0;
 28: }


Output Image

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 Image

There are two lambda expressions of interest in Lines 17 and 25. They are similar, save for the type of the input parameter, as they have been customized to the nature of the elements within the two containers. The first takes one parameter that is an int, as it is used to print one element at a time from a vector of integers, whereas the second 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 may 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 uses the compiler’s automatic type deduction capabilities invoked using keyword auto. This is an improvement to lambda expressions that are supported by compilers that are C++14 compliant. 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 with the exception that the latter uses function objects. In fact, Listing 22.1 is a lambda version of function object DisplayElement<T>.

Comparing the two, you realize how lambda functions have the potential to make C++ code simpler and more compact.


Lambda Expression for a Unary Predicate

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.

LISTING 22.2 Find an Even Number in a Collection Using a Lambda Expression for a Unary Predicate and Algorithm std::find_if()


  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 Image

Even number in collection is: -50

Analysis Image

The lambda function that works as a unary predicate is shown in Line 11. 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 modulus operation with 2 is zero).


Note

Listing 22.2 not only demonstrates 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 a reference to avoid unintentional changes to the value of elements in a container.


Lambda Expression with State via Capture Lists [...]

In Listing 22.2, you created a unary predicate that returned true if an integer was divisible by 2—that is, the integer is an even number. What if you want 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”—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

Such a lambda expression is a one-line equivalent of the 16 lines of code seen in Listing 21.3 that defines unary predicate struct IsMultiple<>.

Thus, lambdas introduced in C++11 improve programming efficiency by leaps and bounds!


Listing 22.3 demonstrates the application of a unary predicate given a state variable in finding a number in the collection that is a multiple of a divisor supplied by the user.

LISTING 22.3 Demonstrating the Use of Lambda Expressions That Hold State to Check Whether One Number Is Divisible by Another


  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 Image

The vector contains: {25, 26, 27, 28, 29, 30, 31}
Enter divisor (> 0): 4
First element in vector divisible by  4: 28

Analysis Image

The lambda expression that contains state and works as a predicate is shown in Line 18. divisor is the state-variable, comparable to IsMultiple::Divisor that you saw in Listing 21.3. Hence, state variables are akin to members in a function object class that you would have composed in days prior to C++11. You are now able to pass states on to your lambda function and customize its usage on the basis of the same.


Note

Listing 22.3 features the lambda expression equivalent of Listing 21.4, without the function object class IsMultiple. Lambda expressions introduced in C++11 have served a reduction in 16 lines of code!


The Generic Syntax of Lambda Expressions

A lambda expression always starts with square brackets and can be configured to take multiple state variables separated using commas in a capture list [...]:

[stateVar1, stateVar2](Type& param) { // lambda code here; }

If you want to ensure that these state variables are modified within a lambda, you add keyword mutable:

[stateVar1, stateVar2](Type& param) mutable { // lambda code here; }

Note that here, the variables supplied in the capture list [] are modifiable within the lambda, but changes do not take effect outside it. If you want to ensure that modifications made to the state variables within the lambda are valid outside it, too, then you use references:

[&stateVar1, &stateVar2](Type& param) { // lambda code here; }

Lambdas can take multiple input parameters, separated by commas:

[stateVar1, stateVar2](Type1& var1, Type2& var2) { // lambda code here; }

If you want to mention the return type and not leave the disambiguation to the compiler, you use -> as in the following:

[stateVar1, stateVar2](Type1 var1, Type2 var2) -> 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);
}


Note

If your lambda expression spans multiple lines, you are required to supply an explicit return type.

Listing 22.5 later in this lesson demonstrates a lambda function that specifies a return type and spans multiple lines.


Thus, a lambda function is a compact, fully functional replacement of a function object such as the following:

template<typename Type1, typename Type2>
struct IsNowTooLong
{
   // State variables
   Type1 var1;
   Type2 var2;
   // Constructor
   IsNowTooLong(const Type1& in1, Type2& in2): var1(in1), var2(in2) {};

   // the actual purpose
   ReturnType operator()
   {
      Statement 1;
      Statement 2;
      return (value or expression);
   }
};

Lambda Expression for a Binary Function

A binary function takes two parameters and optionally returns a value. A lambda expression equivalent of the same would be

[...](Type1& param1Name, Type2& param2Name) {  // lambda code here;  }

A lambda function that multiplies two equal-sized vectors element by element using std::transform() and stores the result in a third vector is shown in Listing 22.4.

LISTING 22.4 Lambda Expression as a Binary Function to Multiply Elements from Two Containers and Store in a Third


  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 Image

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 Image

The lambda expression in question is shown in Line 17 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 return value of the binary function is stored in a target container. This binary function is a lambda expression that takes 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 was the demonstration of the lambda equivalent of function object class Multiply<> in Listing 21.5.


Lambda Expression for a Binary Predicate

A binary function that returns true or false to help make a decision is called a binary predicate. These predicates find use in sort algorithms, such as std::sort(), that invoke the binary predicate for any two values in a container to know 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 used in a sort.

LISTING 22.5 Lambda Expression as a Binary Predicate in std::sort() to Enable Case-Insensitive Sort


  0: #include <algorithm>
  1: #include <string>
  2: #include <vector>
  3: #include <iostream>
  4: using namespace std;
  5:
  6: template <typename T>
  7: void DisplayContents (const T& input)
  8: {
  9:    for (auto element = input.cbegin();
 10:          element != input.cend ();
 11:          ++ element )
 12:       cout << *element << endl;
 13: }
 14:
 15: int main ()
 16: {
 17:    vector <string> namesInVec{ "jim", "Jack", "Sam", "Anna" };
 18:
 19:    cout << "The names in vector in order of insertion: " << endl;
 20:    DisplayContents(namesInVec);
 21:
 22:    cout << "Order after case sensitive sort: " << endl;
 23:    sort(namesInVec.begin(), namesInVec.end());
 24:    DisplayContents(namesInVec);
 25:
 26:    cout << "Order after sort ignoring case:" << endl;
 27:    sort(namesInVec.begin(), namesInVec.end(),
 28:        [](const string& str1, const string& str2) -> bool // lambda
 29:        {
 30:           string str1LC; // LC = lowercase
 31:
 32:           // Assign space
 33:           str1LC.resize (str1.size ());
 34:
 35:           // Convert every character to the lower case
 36:           transform(str1.begin(), str1.end(), str1LC.begin(),::tolower);
 37:
 38:           string str2LC;
 39:           str2LC.resize (str2.size ());
 40:           transform(str2.begin(), str2.end(), str2LC.begin(),::tolower);
 41:
 42:           return (str1LC < str2LC);
 43:        }  // end of lambda
 44:       );  // end of sort
 45:
 46:    DisplayContents(namesInVec);
 47:
 48:    return 0;
 49: }


Output Image

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 Image

This demonstrates a genuinely large lambda function spanning Lines 28–43 as the third parameter of std::sort()! What this lambda function demonstrates is that a lambda can span multiple statements, the prerequisite being that the return value type is explicitly specified as shown in Line 28 (bool). The output demonstrates the content of the vector as inserted, where "jim" is before "Jack". The content of the vector after a sort without a supplied lambda or predicate as shown in Line 23 sorts "jim" after "Sam", as this is a case-sensitive via std::less<> executed using string::operator<. Finally, a case-insensitive sort() that uses a lambda expression to first convert the string to lowercase and then compares them is seen in Lines 28–43 that places "jim" after "Jack" as the user typically would expect.


Tip

This extraordinarily large lambda in Listing 22.5 is a lambda version of Listing 21.6, class CompareStringNoCase, used in Listing 21.7.

Clearly, this example also demonstrates that a function object as seen in Listing 21.6 is reusable in multiple std::sort() statements, if required, and also in other algorithms that need a binary predicate, while a lambda would need to be rewritten every time it needs to be used.

So, you need to use lambdas when they’re short, sweet, and effective.


Summary

In this lesson, you learned about an important feature introduced in C++11: lambda expressions. You saw how lambdas are basically unnamed function objects that can take parameters, have state, return values, and be multiple lined. You learned how to use lambdas instead of function objects in STL algorithms, helping find(), sort(), or transform(). Lambdas make programming in C++ fast and efficient, and you should try to use them where applicable.

Q&A

Q Should I always prefer a lambda over a function object?

A Lambdas that span multiple lines as shown in Listing 22.5 might not help increase programming efficiency over function objects that are easily reused.

Q How are the state parameters of a lambda transferred, by value or by reference?

A When a lambda is programmed with a capture list as this:

[Var1, Var2, ... N](Type& Param1, ... ) { ...expression ;}

the state parameters Var1 and Var2 are copied (not supplied as a reference). If you want to have them as reference parameters, you use this syntax:

[&Var1, &Var2, ... &N](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.

Q Can I use the local variables in a function in a lambda?

A You can pass the local variables in a capture list:

[Var1, Var2, ... N](Type& Param1, ... ) { ...expression ;}

If you want to capture all variables, you use this syntax:

[=](Type& Param1, ... ) { ...expression ;}

Workshop

The Workshop provides quiz questions to help you solidify your understanding of the material covered and exercises to provide you with experience in 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 going to the next lesson.

Quiz

1. How does a compiler recognize the start of a lambda expression?

2. How would you pass state variables to a lambda function?

3. If you need to supply a return value in a lambda, how would you do it?

Exercises

1. Write a lambda binary predicate that would help sort elements in a container in descending order.

2. Write a lambda function that, when used in for_each(), adds a user-specified value to that in a container such as vector.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.194.182