A Holistic Look at C++ Lambda Expressions

C++11 introduced the concept of lambda expressions which uniquely enclose small code snippets, which are passed as arguments to higher-order functions. A lambda function is declared as anonymous (without a name) but can be stored in a named variable so that it can be called later. The C++ lambda expressions are short inline codes that can implement the functionality required by algorithms or asynchronous methods. The major difference between a regular function and a lambda expression lies in their usability. You generally created a function to reuse it in the program, whereas you cannot reuse a lambda function.

Now, understand the need for a C++ lambda expression and how they differ from the regular functions with the help of a simple example. 

Consider a situation where you want to print the elements of a vector using the for_each method. This article explores two ways to achieve this. First, through the use of a regular function and the second way will be through the use of a lambda expression.

1. Using a Regular Function

#include<bits/stdc++.h>

using namespace std;

// function to print the vector elements

void print(int element)

{

    cout << element << " ";

}

int main(void)

{

    vector<int> v = {10, 20, 30, 40, 50};

    cout << "The vector elements are: \n";

    // pass the print() function as an argument 

    // to the for_each method

    for_each(v.begin(), v.end(), &print);

    cout << "\n"; 

    return 0;

}

C_Plus_Plus_Lambda_1.

2. Using a C++ Lambda Expression

#include<bits/stdc++.h>

using namespace std;

int main(void)

{

    vector<int> v = {10, 20, 30, 40, 50};

    cout << "The vector elements are: \n";

    // define an inline lambda expression

    // to print the vector elements

    for_each(v.begin(), v.end(), [](int element) {

            cout << element << " ";

        });

    cout << "\n";

    return 0;

}

C_Plus_Plus_Lambda_2

In the above example, you illustrated two different approaches to print the elements of a vector. However, in the first approach (i.e. using a regular function), a separate function is defined to print the vector elements. This can cause compilation overhead in the function calls. The second approach overcomes this issue as the lambda expression is defined inline and is only used for printing the elements once in the program. Function calls will not reuse it.

Add Another Star to Your Performance Evaluation

Learn from industry experts for FREEStart Learning
Add Another Star to Your Performance Evaluation

Syntax and Return values

The general syntax to define a C++ lambda is as follows:

[capture clause] (parameters) mutable exception ->return_type

{

   // definition of the lambda body

}

Return Value

A C++ lambda function executes a single expression in C++. A value may or may not be returned by this expression. It also returns function objects using a lambda.

Parts of Lambda Expression

  • Capture Clause

The capture clause (now known as lambda specifier according to the C++14 specifications) determines the type of variables that has to be captured and if the variable is captured by reference or by value. Those variables that start with an ampersand(&) are called by reference and the variables that do not possess any prefix are called by value. The standard syntax of the lambda begins with the capture clause. 

If the capture clause ( [ ] ) in C++ lambda is empty, it implies that no variable was captured so far. The following example shows the how to use an empty capture clause:

#include <iostream>

#include <algorithm>

using namespace std;

int main() {

    int a[] = { 2, 4, 6, 8, 10 }; 

    //using empty lambda function

    for_each(a, a + sizeof(a) / sizeof(int), [](int var) 

    {

        cout<<var<<" ";

    });

    cout << endl;

}

C_Plus_Plus_Lambda_3 

You can also determine how to capture any variable that lies outside, by using the default capture mode. If the capture clause is like this [&], then it means that the variables are captured by reference, and if the capture clause is like this [=], then it implies that value captures the variables.

The following code displays how to capture the variables by reference as well as by value using C++ lambda:

#include <iostream>

using namespace std;

//Driver code

int main()

{

     int var1 = 2;

   int var2 = 4;

  // lambda i stored in the func variable

   auto func = [var1, &var2] { return var1 * var2; }; 

   // Change the values of var1 and var2.

   var1 = 10;

   var2 = 30; 

   // print the lambda result

   cout << "The product of var1 and var2 is: ";

   cout << func() << endl;

}

C_Plus_Plus_Lambda_4

Now, consider two external variables var1 and var2. The lambda accesses Var1 by reference and the lambda accesses var2 by value. For this scenario, all the following cases are equivalent:

[&var1, var2]

[var2, &var1]

[&, var2]

[var2, &]

[=, &var1]

[&var1, =]

The capture clause in C++ lambda will throw an error if it used an identifier more than once within the capture clause. For example, if the capture default is “&”, then no other identifier in the same capture clause can have “&” as the prefix. Similarly, if the capture default is “=”, then no other identifier in the same capture clause can have “=” as the prefix. 

The following code snippet illustrates the above:

struct my_clause { void func(int var); }; 

void my_clause::func(int var) 

{

    [&, var]{};        // OK

    [&, &var]{};       // ERROR: var has prefix & when & 

                       //is the default identifier

    [=, this]{};       // ERROR: = is the default identifier

    [=, *this]{};      // OK

    [var, var]{};      // ERROR: repetition of var

}

A lambda expression uses a variable without being captured if :

  • The variable is being initialized with a const keyword and is passed by reference.
  • The variable is static or global.

  • Parameter List (Optional)

Apart from capturing variables, C++ lambda also allows you to pass arguments or parameters to a lambda.

auto a = [] (int var1, int var2)

{

    return var1 * var2;

};

Here, “a” is a named variable that is storing the lambda that accepts two integer type variables, var1 and var2. The lambda is executing the product of var1 and var2 and returning the same.

The following example illustrates the parameter list in C++ lambda:

#include <iostream>

using namespace std;

//Driver Code

int main()

    // Assign the lambda expression that adds two numbers to an auto variable.

    auto func = [](int var1, int var2)

    {

        return var1 * var2; 

    };     

    //print the lambda result

    cout << "The product of var1 and var2 is "<<func(5, 4) << endl;

}

C_Plus_Plus_Lambda_5  

The new regulations of C++14 allow you to pass generic variables to the lambda as parameters. You can use the auto keyword to make the variable generic. This makes the function call operator a template and during the function call, you can pass the variables of any data type as parameters. 

auto a = [] (auto var1, auto var2)

{

    return var1 * var2;

};

Here, unlike the previous code snippet, var1 and var2 have generic data types. 

You can even pass a lambda expression as the parameter to another lambda expression. Such functions are also known as the “higher-order lambda function”.

auto func2 = [](const auto& func1, int x) 

    {        

        return func1(int x) + 10;

    };

Here, func2 is storing a lambda expression that passes func1 (another lambda expression) as its parameter and adding 10 to the result.

The following code declares a C++ lambda expression that returns another lambda expression that adds two numbers:

#include <iostream>

using namespace std;

int main()

{

    auto sum = [](int var1) -> auto 

    {

        // The returned lambda expression captures 

        //parameter var2 by value.

        return [=](int var2) 

       { 

            return var1 + var2; 

        };

    };

    //higher order stores a lambda expression that accepts 

    //another lambda expression as its parameter

    auto higherorder = [](const auto& func, int var3) 

    { 

        // The lambda expression applies the argument var3 to

        //the function func and multiplies by 3.

        return func(var3) * 3;

    };

    // function call

    auto result = higherorder(sum(5), 4);

    // print result

    cout << "The sum is: ";

    cout << result << endl;

}

C_Plus_Plus_Lambda_6

  • Mutable Specification (Optional)

The function call of a C++ lambda expression is const by value by default. This prevents the function from creating data members that can be mutated. To avoid this, a mutable keyword is used. The mutable specification allows the mutation or modification of the variables that are captured by value by the body of the lambda expression.

The following example explains the usage of a mutable keyword in detail:

#include <iostream>

using namespace std;

//Driver Code

int main()

{

    int var1 = 1;

    int var2 = 1;

    //var 1 is passed by reference

    //var 2 is passed by value

    [&, var2] (int x) mutable { var1 = ++var2 + x; }(6);

    cout << var1 << endl << var2 << endl;

}

C_Plus_Plus_Lambda_7

Here, var1 is passed by reference, so its value should have remained 1. But since you have applied the mutable keyword, its value has been modified and become 8. 

  • Exception-specification (Optional)

The exception specification is used to determine that no exception is being thrown by the lambda expression. For this, you can use the noexcept exception specifier. The following code snippet demonstrates the usage of noexcept exception specifier:

int main() 

{

    //empty capture clause

    []() noexcept { throw 5; }();

}

  •  Trailing-Return-Type (Optional)

The trailing-return type is quite similar to the return type of any ordinary method or function. It is implied in a function by using the trailing-return-type keyword “ ->” just before the return type. In the absence of a trailing-return type, the function by default considers the basic return type.

  • Lambda Body

The body of a C++ lambda expression is composed of all those things that the body of a normal function or a method is made up of. These are the type of variables that the body of a function or a lambda expression can hold:

  • Local variables
  • Global Variables
  • Captured variables (variables within [ ] )
  • Arguments/Parameters
  • Data members of a class

The following code displays how you can include various types of variables in the body of a lambda expression:

#include <iostream>

#include <algorithm>

#include <vector>

#include <string>

using namespace std;

template <typename T> void print(const string& str, const T& c) {

    cout << str;

    for (const auto& it : c) {

        cout << it << " ";

    }

    cout << endl;

}

void insert(vector<int>& v)

{

   // A local static variable.

    static int val = 1;

    //The local static variable val is modified and 

    //used by the lambda expression t

    generate(v.begin(), v.end(), [] 

    { 

        return val++; 

    });

}

//Driver Code

int main()

{

    // The total number of vector elements

    const int total = 9;

    // vector elements initially set to 1

    vector<int> v(total, 1);

    // These variables hold the previous two elements of the vector.

    int var1 = 2;

    int var2 = 2;

    // every current element is set as the 

    //sum of the previous two elements

    // lambda is the 3rd parameter

    generate_n(v.begin() + 2,

        total - 2,

        //lambda expression modifies the copy of 

        //the external variables var1 and var2

        [=]() mutable throw() -> int 

        { 

        // Generate current value.

        int n = var1 + var2;

        // Update previous two values.

        var1 = var2;

        var2 = n;

        return n;

    });

    print("Vector before the function call genetare_n(): ", v);

    // Print var1 and var2.

    // var1 and var2 are captured by value and 

    //hence holds their original value

    cout << "var1: " << var1 << " var2: " << var2 << endl;

    // Insert the first sequence of numbers in the vector

    insert(v);

    print("Vector after 1st call to insert(): ", v);

    // Insert the next sequence of numbers in the vector

    insert(v);

    print("Vector after 2nd call to insert(): ", v);

}

C_Plus_Plus_Lambda_8 

In the above program, you created a lambda expression that allocates the current element of the vector v as the sum of its previous two elements. This body of the lambda expression modifies the copies of var1 and var2 with the help of the mutable keyword. And since both of these variables are captured by value, their initial value does not change at all.

The Constexpr Lambda Expressions

The C++17 version allowed the lambda expressions to be used in constant expressions. C++ lambda can be used in a constexpr or declared as a constexpr. If a lambda expression satisfies all the requirements of a constant expression, then it will be an implicit constexpr, even without specifying constexpr.

The following example illustrates a lambda expression that has been implicitly defined as a constexpr.

#include<iostream>

using namespace std;

int main(){

    // define a lambda that executes a constant expression

    auto answer = [](int n)

    {

        return 100 * n;

    };

    constexpr int result = answer(5);

    cout << result << "\n"; 

}

C_Plus_Plus_Lambda_9.

In the above example, the constexpr is not specified while defining the lambda expression. But since the lambda is executing a constant expression, it implicitly defines it as a constexpr.

The following example illustrates a lambda expression that has been explicitly defined as a constexpr. 

#include<iostream>

using namespace std;

int main(){

    // define a constexpr lambda 

    constexpr auto product = [](const auto &x, const auto &y) 

    { 

        return x * y; 

    };

    constexpr int result = product(100, 5);

    cout << result << "\n"; 

}

C_Plus_Plus_Lambda_10

In the above program, you have defined the lambda expression as a constant expression explicitly using the constexpr specifier. The lambda expression here returns the product of two integers.

A lambda expression that has been defined as a constexpr (implicitly or explicitly) can also be converted to a function pointer, and this results in a constexpr function pointer. The following code snippet illustrates this:

// an implicit constexpr lambda expression

// converted into a function pointer (i.e.addFive )

auto addFive = [](int n)

    {

        return n + 5;

    };

constexpr int(*addfive)(int) = addFive;

In the above code snippet, the lambda expression is implicitly a constexpr and it is converted into a function pointer. The resulting function pointer ddFive is also a constexpr.

Full Stack Web Developer Course

To become an expert in MEAN StackView Course
Full Stack Web Developer Course

Generic Lambdas

In C++14 and higher versions, you can also define generic lambdas which can have arbitrary type arguments. You can define a generic lambda expression only when it contains at least one auto parameter. A generic lambda is equivalent to functors with templated call operators. The following two code snippets explain this:

auto myGenericLambda = [] (auto var) { return var; };  

The following code snippet is equivalent to the above code. The following implementation is done by performing operator overloading of the template function.

struct myGenericLambda

{

  template <typename T>

  auto operator() (T var) const {return var; }

};

The following example illustrates generic lambda in C++:

#include <bits/stdc++.h>

using namespace std;

int main() {

    // a generic lambda

    auto add = [](auto var1, auto var2){ return var1 + var2; };

    // this will compute 4 + 6 i.e. 10

    cout << add(4, 6) << "\n"; 

    // this will concatenate the string i.e. "Hey There"

    cout << add(string("Hey "), string("There")) << "\n";

   return 0;

}

C_Plus_Plus_Lambda_11.

In the above program, a generic lambda is defined with two auto parameters. This generic lambda returns the sum when two integers are passed and returns a concatenated string when two string arguments are passed.

Generic Lambdas and Static Variables

A generic lambda creates a different version of itself when it is called with parameters of different types. However, if static variables are used within the definition of a lambda expression, then in that case these static variables will not be shared between the different generated lambda versions. The following program illustrates this:

#include<bits/stdc++.h>

using namespace std;

int main()

{

  // Print a value and count how many times @print has been called.

  auto lambdaWithoutStatic = [](auto value) {

      int count1{ 0 };

      std::cout << count1++ << ": " << value << '\n';

    };

  auto lambdaWithStatic = [](auto value) {

      static int count2{ 0 };

      std::cout << count2++ << ": " << value << '\n';

    };

  cout << "Lambda without static variable: \n";

  lambdaWithoutStatic("hello"); 

  lambdaWithoutStatic("world");

  lambdaWithoutStatic(10); 

  lambdaWithoutStatic(20); 

  cout << "Lambda with static variable: \n";

  lambdaWithStatic("hello"); 

  lambdaWithStatic("world"); 

  lambdaWithStatic(10); 

  lambdaWithStatic(20); 

  return 0;

}

C_Plus_Plus_Lambda_12

In the above program, the generic lambda has been called by passing an integer and a string type arguments. This will create different versions of the lambda expression. The variable count1 in the lambda lambdaWithoutStatic is shared by all the generic versions and the value of count1 is 0 for all versions. But in lambdaWithStatic, the static variable count2 is not shared by the different generic versions, and hence the value of  count2 is incremented in each call.

Return Type Deduction and Trailing Return Types

The return type of C++ lambda expressions can be either specified explicitly while defining the lambda expression or it can be deduced automatically by the compiler. In the case of automatic return type deduction by the compiler, all return statements inside a lambda should return the same type. If the return type of the return statements differs, then in such a case the compiler throws an error. This can be understood by the following example:

// no explicit return type in lambda definition

auto sum = [](int var1, int var2, bool result) { 

    if (result)

      // deduced return type: integer

      return var1 + var2;

    else

      // ERROR: double return type does 

      // not match with the above integer return type

      return static_cast<double>(var1) + var2; 

  } 

 

In the above example, there is no explicit return type in the definition of the lambda expression. So, the compiler will deduce the return type automatically. However, in this case, an error will be thrown because the two return statements are returning different types (integer and double). 

To avoid such situations, you can use the trailing return type. The trailing return type can be specified explicitly while defining the lambda. The compiler will then automatically perform implicit type conversions in the return statements. Also, this will not require any change in the lambda body. The following program illustrates the railing return type in C++ lambda:

#include <iostream>

using namespace std;

int main()

{

  auto sum = [](int var1, int var2, bool result) -> double { 

    if (result)

      // compiler will perform implicit return type conversion

      return var1 + var2;

    else

      // return type double

      return static_cast<double> (var1) / var2; 

  }; 

  cout << sum(8, 6, true) << "\n";

  cout << sum(8.6, 5.4, false) << "\n";

  return 0;

}

C_Plus_Plus_Lambda_13

In the above program, a trailing return type double is specified while defining the lambda expression. The first return statement is returning an integer type result. So, the compiler will implicitly do conversion of the return type from integer to double

Examples

The following examples illustrate the C++ lambda and their various usages:

Example 1:  Using Capture Less Lambda With Standard Functions. 

#include <bits/stdc++.h>

using namespace std;

// define function to print elements of a vector

void displayVector(vector<int> v)

{

   // define a lambda to display vector elements

   for_each(v.begin(), v.end(), [](int x)

   {

      cout << x << " ";

   });

   cout << "\n";

}

int main()

{

   // initialize a vector

   vector<int> v {5, 2, 4, 6, 3, 4, 2, 8}; 

   // print the vector elements

   cout << "The original elements in the vector are: ";

   displayVector(v);

   cout << "\n";

   // define a lambda to find the

   // first element less than 5

   // and point an iterator to the returned value

   vector<int>:: iterator itr = find_if(v.begin(), v.end(), [](int x)

   {

      return x < 5;

   });

   cout << "The first element smaller than 5 is : " << *itr << "\n";

   cout << "\n";

   // define a lambda to be used as a comparator

   // by the sort function to sort the vector elements

   // in descending order

   sort(v.begin(), v.end(), [](const int& a, const int& b)

   {

      return a > b;

   });

   // print the vector elements after sorting

   cout << "The vector elements after sorting are: ";

   displayVector(v);

   cout << "\n";

   // define a lambda to calculate the number of 

   // elements less than or equal to 6

   int count = count_if(v.begin(), v.end(), [](int x)

   {

      return (x >= 6);

   });

   // print the count of elements less than or equal to 6

   cout << "The count of elements less than or equal to 6 are: ";

   cout << count << "\n";

   cout << "\n";

   // define a lambda to print unique elements in the vector

   itr = unique(v.begin(), v.end(), [](int x, int y)

   {

      return x == y;

   });

   // resize the vector to the size equal to 

   // the no. of the unique elements

   v.resize(distance(v.begin(), itr));

   // print all unique elements of the vector

   cout << "Unique elements in the vector are: ";

   displayVector(v);

   cout << "\n";

   // declare an array

   int a[] = {1, 4, 5, 6, 8, 10, 9, 7};

   // define a lambda in the accumulator function to 

   // find the sum of all array elements

   int sum = accumulate(a, a + 8, 1, [](int x, int y)

   {

      return x + y;

   });

   cout << "Sum of the array elements is : " << sum << "\n";

   cout << "\n";

   // define a lambda and store it in a named variable

   auto cube = [](int x)

   {

      return x * x * x;

   };

   cout << "The cube of 3 is : " << cube(3) << "\n\n";

   return 0;

}

C_Plus_Plus_Lambda_14

In the above program, lambda with empty capture clauses is used with various STL functions. The lambda is used to define comparators for these standard functions. The sort function uses a lambda to sort the elements in descending order. Similarly, you define a lambda to return the boolean result by comparing elements with 6 for the count_if() function, which calculates the count of elements less than or equal to 6.

 Example 2:  Using Lambda With Various Capture Clauses.

#include <bits/stdc++.h>

using namespace std;

int main()

{

    // initialize two vectors

    vector<int> myVector1 = {4, 2, 10, 8};

    vector<int> myVector2 = {8, 3, 8, 15, 10}; 

    // define a lambda to insert elements into the vector.

    // using capture clause "&" to accept arguments by reference.

    // insertElements is a named variable for lambda.

    auto insertElements = [&] (int m)          

    {

        myVector1.push_back(m);

        myVector2.push_back(m);

    }; 

    // insert 50 into myVector1 and myVector 2.

    insertElements(50); 

    // accessing myVector1 elements by copy

    [myVector1]()

    {

        for (auto itr = myVector1.begin(); itr != myVector1.end(); itr++)

        {

            cout << *itr << " ";

        }

    };

    cout << "\n"; 

    // declare an integer variable

    int num = 7;

     // define lambda to find first vector element less than num

    // using the find_if method.

    vector<int>:: iterator itr = find_if(myVector1.begin(), myVector1.end(), [num](int x)

    {

        return x > num;

    });

     cout << "The first element less than 6 is : " << *itr << "\n";

    // define lambda to find the count of elements

    // less than or equal to num, using the count_if method.

    // the "=" clause accepts arguments by value

    int count = count_if(myVector1.begin(), myVector1.end(), [=](int x)

    {

        return (x >= num);

    });

     // print the number of elements less than or equal to 6

    cout << "The count of elements which are less than or equal to 6 are : ";

    cout << count << "\n\n";

}

C_Plus_Plus_Lambda_15 

In the above program, the captured versions of lambda are used with various STL functions. The different capture clauses are used to manipulate vector elements by value, copy, and reference. The “[&]” clause is used to accept an element by its reference and insert it into the vector. The “[myVector1]” clause is used to access the vector elements by copy and print them. The “[=]” clause is used here to accept the argument by value and find the count of elements in the vector that are less than or equal to 6.

Advance your career as a MEAN stack developer with the Full Stack Web Developer - MEAN Stack Master's Program. Enroll now!

Final Thoughts!

In this article, you learned another important utility function called lambda expressions in C++. You saw its syntax and return values with a few examples. You also saw the different parts of a lambda expression, generic lambdas, constexpr lambdas, and other associated concepts.

To know more about basic c++, you should check out our complete guide on C++ Programming for Beginners.

You can also learn professional software development skills and technologies through our course on Full Stack Web Development. With the help of this course, you will be able to learn concepts such as agility, DevOps, AWS, HTML, CSS, Java, etc.

If you want to access well-crafted free online courses by top instructors, you should check out the complete list of free online courses by Simplilearn.

If you have any queries or suggestions for us, please mention them in the comment box and our experts answer them for you as soon as possible.

Happy Learning!

About the Author

SimplilearnSimplilearn

Simplilearn is one of the world’s leading providers of online training for Digital Marketing, Cloud Computing, Project Management, Data Science, IT, Software Development, and many other emerging technologies.

View More
  • Disclaimer
  • PMP, PMI, PMBOK, CAPM, PgMP, PfMP, ACP, PBA, RMP, SP, and OPM3 are registered marks of the Project Management Institute, Inc.