Home C++ Introduction Decisions Loops Input/Output Functions Stack and Heap References Arrays Searching and Sorting Recursion Pointers Character and Strings Structures Classes Inheritance Exceptions Templatess STL Modern C++ Misc Books ----

Lambdas


Contents

Introduction

Sometimes we need a function that will be used in a very limited way. Lambdas allow us to write an expression that will be used as a function.
This paradigm is known as functional programming. We had function objects ( functors ) that involved creating a class and an operator function insided it. Now we can create a function in a much more simplistic and concse way. The capture clause can specify how we can include variables from outside the function.
[=] (parameters) mutable  throw()   -> return-type {body}
1      2            3       4             5           6
1) capture clause
2) parameters
3) mutable
4) throw
5) return type
6) body
A lambda expression translates to a function object with a class and the operator "()" in the class. It's a more convenient notation than creating a class or an actual function. It can be used inline or used as arguments. We can use the handy "auto" to declare the type that the lambda expression will return.

Capture Clause

1) capture clause
This dictates the variables that we can access before
the lambda function.


File: lambdas11.cpp
#include <algorithm>
#include <array>
#include <string>
#include <functional>
#include <iostream>

using namespace std ;


class Integer {
public:
    int x1 ;
    Integer(int x1p)
    {
        x1 = x1p ;
        cout << "Object Created" <<
         x1 <<  endl;

    }


    Integer(const Integer& param1)
    {
        x1 = param1.x1 ;
        cout << "Copy constructor: Object Created" <<
           x1 <<  endl;
    }


    ~Integer()
    {
        cout << "Object Destroyed: " << x1 <<  endl;
    }
};



int main()
{
   int x1 = 10 ;
   int x2 = 10 ;
   //outside variable like x1 is not captured
   auto print1 = [](string str1){
              cout << str1 << endl ;
       } ;

   print1( "Calling the lambda print function." ) ;

   //Trying to access the variable outside the
   //scope

   auto print2 = [](string str1){
              cout << str1 << endl ;
             // cout << x1 << endl ;
       } ;

   print2( "Calling the lambda print2  function." ) ;


   auto print3 = [=](string str1){
              cout << str1 << endl ;
              cout << x1 << endl ;
              //x1=5 ; Compiler error
              //x1 is read only
       } ;

   print3( "Calling the lambda print3  function." ) ;


   auto print4 = [&](string str1){
              cout << str1 << endl ;
              x1=4 ;

       } ;

   print4( "Calling the lambda print4  function." ) ;
   cout << x1 << endl ;

   auto print5 = [x1](string str1){
              cout << str1 << endl ;
              cout << x1 << endl ;
              //cout << x2 << endl ;
              //The "x2" is not accessible here.
       } ;

   print5( "Calling the lambda print5  function." ) ;



   //if ( 1 == 1 )
   //   return 1 ;

   Integer obj1( 10 ) ;


   //A copy is created inside the lambda
   //Copy constructor gets called.
   //When print6 goes out of scope then
   //the obj1 gets destroyed.

   auto print6 = [=](string str1){
              cout << str1 << endl ;
              cout << obj1.x1 << endl ;
              //We cannot modify obj1
       } ;

   print6( "Calling the lambda print6  function." ) ;

  //capture all the variables by reference
  //Copy constructor does not get called.
   cout << "-------------------" << endl ;
   auto print7 = [&](string str1){
              cout << str1 << endl ;
              cout << obj1.x1 << endl ;
       } ;

   print7( "Calling the lambda print7  function." ) ;

   cout << "-------------------" << endl ;
   //Allowing access to a single variable/object
   //Another copy of obj1 gets created here.

   auto print8 = [obj1](string str1){
              cout << str1 << endl ;
              cout << obj1.x1 << endl ;
             // Other variables are not captured
             // cout << x1 << endl ;
       } ;

   print8( "Calling the lambda print8  function." ) ;


   cout << "-------------------" << endl ;
   //Passing a single variable by reference
   auto print9 = [&obj1](string str1){
              cout << str1 << endl ;
              //Allowed
              obj1.x1 = 100 ;
              cout << obj1.x1 << endl ;
       } ;

   print9( "Calling the lambda print9  function." ) ;
   //Changes are preserved after the invocation
   cout << obj1.x1 << endl ;
   cout << "-------------------" << endl ;


   cout << "-------------------" << endl ;
   cout << "mix and match example" << endl ;

   //we can mix and match
   auto print10 = [&obj1, x1](string str1){
              cout << str1 << endl ;
              cout << x1 << endl ;
              cout << obj1.x1 << endl ;
       } ;

   print10( "Calling the lambda print10  function." ) ;
   cout << "-------------------" << endl ;




}
In the above file we see the statement:

   auto print1 = [](string str1){
              cout << str1 << endl ;
	   } ;

   print1( "Calling the lambda print1  function." ) ;

We are using the handy "auto" to define the type for the
lambda expression. As we will see later the lambda is essentially a
functor object.

We are not trying to access the variables defined in the scope above the
lambda definition.

   auto print2 = [](string str1){
              cout << str1 << endl ;
             // cout << x1 << endl ;
	   } ;

   print2( "Calling the lambda print2  function." ) ;

The capture close is empty ( "[]" ). So if we try
to access "x1" then we get a compiler error.

   auto print3 = [=](string str1){
              cout << str1 << endl ;
              cout << x1 << endl ;
              //x1=5 ;
              //x1 is read only

	   } ;

   print3( "Calling the lambda print3  function." ) ;

Here we place the equal sign in the capture clause "[=]".
This means we have access to the other variables by value.
We cannot change the values as the "x1" is considered read only.
If we do want to change the value then we need to pass "x1" by
reference.

   auto print4 = [&](string str1){
              cout << str1 << endl ;
              x1=4 ;

	   } ;

   print4( "Calling the lambda print4  function." ) ;
   cout << x1 << endl ;
We see in the output that the value of "x1" change was retained.

Calling the lambda print4  function.
4

   auto print5 = [x1](string str1){
              cout << str1 << endl ;
              cout << x1 << endl ;
              //cout << x2 << endl ;
              //The "x2" is not accessible here.
	   } ;

   print5( "Calling the lambda print5  function." ) ;

We can choose to make only certain variables accessible
to the lambda expression. In the above we are making "x1"
accessible but denying access to any other variables such
as "x2".

   Integer obj1( 10 ) ;

   //A copy is created inside the lambda
   //Copy constructor gets called.
   //When print6 goes out of scope then
   //the obj1 gets destroyed.

   auto print6 = [=](string str1){
              cout << str1 << endl ;
              cout << obj1.x1 << endl ;
              //We cannot modify obj1
	   } ;

We can also choose to capture objects.In the above we are capturing
the object "obj1" and another copy gets created in the lambda
expression even though the object name stays the same which is "obj1".
We can see that because the "copy constructor" gets called when we
invoke this lambda object.

The same name strategy works fine because we are not allowed
to modify "obj1".
Of course in order to modify "obj1" we need to use the reference
clause.
  //capture all the variables by reference
  //Copy constructor does not get called.
   cout << "-------------------" << endl ;
   auto print7 = [&](string str1){
              cout << str1 << endl ;
              cout << obj1.x1 << endl ;
              obj1.x1 = 5 ;
	   } ;

   print7( "Calling the lambda print7  function." ) ;


   //Allowing access to a single variable/object
   //Another copy of obj1 gets created here.

   auto print8 = [obj1](string str1){
              cout << str1 << endl ;
              cout << obj1.x1 << endl ;
             // Other variables are not captured
             // cout << x1 << endl ;
	   } ;
   We can choose to allow access to just a single object as the
   above snippet shows. We could also place more variables in the
   capture clause separated by commas.


   auto print9 = [obj1, x1](string str1){
              cout << str1 << endl ;
              cout << obj1.x1 << endl ;
             // Other variables are not captured
              cout << x1 << endl ;
	   } ;
   print9( "Calling the lambda print9  function." ) ;

   Passing the object by reference.

   auto print10 = [&obj1](string str1){

   We can choose to make some variable as pass by reference
   and others pass by value.
   //we can mix and match
      auto print11 = [&obj1, x1](string str1){
                 cout << str1 << endl ;
                 cout << x1 << endl ;
                 cout << obj1.x1 << endl ;
   	   } ;

   print11( "Calling the lambda print11  function." ) ;
   cout << "-------------------" << endl ;

   In the below we are stating that "obj1" is passed by reference
   and all other parameters are passed by value.

   auto print12 = [&obj1, =](string str1){
                 cout << str1 << endl ;
                 cout << x1 << endl ;
                 x1 = 13 ;
                 obj1.x1 = 12 ;
                 cout << obj1.x1 << endl ;
   	   } ;

   print12( "Calling the lambda print12  function." ) ;
   cout << "-------------------" << endl ;

STL

We shall examine how lambdas are used in STL.
File: lambdas12.cpp
 #include <algorithm>
 #include <array>
 #include <string>
 #include <functional>
 #include <iostream>

 using namespace std ;

 class customSort1
 {
   public:
    bool operator()(int const& s1, int const& s2)
     {
        return s1 < s2 ;
       }


 };

 struct Add {
     int operator()(int a, int b) const
     {
      cout << "Inside Add operator." << endl ;
      return a + b;
     }
 };


 int main()
 {
     //Using lambda in stl sort algorithm
     std::array<int, 10> arr1{5, 7, 4, 2, 8, 6, 1, 9, 0, 3};

     auto print1 = [&arr1](const string& rem)
     {
         for (auto element : arr1)
             std::cout << element << ' ';
         std::cout << ": " << rem << '\n';
     };

     sort(arr1.begin(), arr1.end());
     print1("sorted with the default operator<");

     auto customSort = [] (int const& s1, int const& s2) -> bool
       {
        return s1 > s2 ;
       };
     sort(arr1.begin(), arr1.end(), customSort);
     print1("sorted with the customSort ");


     //Inline
     sort(arr1.begin(), arr1.end(), [] (int const& s1, int const& s2) -> bool
       {
        return s1 > s2 ;
       });
     print1("sorted with the inline lambds ");


     //Using the built in functor "greater"
     //Signature of the greater is
     //template <class T> struct greater {
     //  bool operator()(const T& lhs, const T& rhs) const;
     //};
     sort(arr1.begin(), arr1.end(), greater<int>() );



     //Use a custom functor object
     customSort1 customSort1Object ;
     sort(arr1.begin(), arr1.end(), customSort1Object );
     print1("Sorted with the functor object.");


     //Return statements can be implicit or
     //explicit.
     //Example of implicit return
     auto add = [] (int a, int b) {
       // always returns an 'int'
       return a + b;
         };
      cout <<  "add called:" << add(4, 5)  << endl ;

     //Explicit return statement
     auto add1 = []  (int a, int b ) -> double {
       return a + b  ;

     };
     cout << "add1 called:" <<  add1(4, 6)  << endl ;


     //inline example
      // initialize vector of integers
       vector<int> nums = {1, 2, 3, 4, 5, 8, 10, 12};

       int even_count = count_if(nums.begin(), nums.end(),
       [](int num) {
         return num % 2 == 0;
       }   );

     cout << "There are " << even_count << " even numbers."
          << endl ;



    //Without using auto
    //A lambda is a functor object.
    function<void(int, int)> add2 = [] (int a, int b) {
     cout << "Sum: " << a + b << endl ;
     };

    add2(1, 2);

    Add add3Object ;
    //Assign a class object to a function object
    function<void(int, int)> add3 = add3Object ;
    add3( 10, 20 ) ;


}
We have the concept of a function that can be defined with it's body.
Then came along "functors". This is a class with an operator "()" such
as:

    bool operator()(int const& s1, int const& s2)
     {
        return s1 < s2 ;
     }

that can then be called directly by creating an object of the class and
then just doing something like:

obj1( 4, 5 ) ;

The next language enhancement to this approach is lambdas. We have an
expression that is like a function but is actually a functor.


     auto print1 = [&arr1](const string& messg)
     {
         for (auto element : arr1)
             std::cout << element << ' ';
         std::cout << ": " << messg << '\n';
     };
In the above snippet we define a lambda "print1" that
passes the "arr1" object and takes a string as an argument.
We are basically printing out the elements of the "arr1"
object.

We first sort using the std::sort function:

sort(arr1.begin(), arr1.end());

without any custom comparators. This sorts the container
in an ascending order.

     auto customSort = [] (int const& s1, int const& s2) -> bool
       {
        return s1 > s2 ;
       };
     sort(arr1.begin(), arr1.end(), customSort);

In the above we define a lambda object "customSort" that takes
2 integer arguments and returns a boolean value. This sorts the
container in a descending order. The signature for "sort" specifies
the third argument to a functor that takes 2 arguments and returns
a boolean value. Notice that we have explicitly specified the return
type with the "-> bool" part of the expression.

     sort(arr1.begin(), arr1.end(), [] (int const& s1, int const& s2) -> bool
       {
        return s1 > s2 ;
       });
     print1("sorted with the inline lambds ");

We do not have to have a name for the lambda expression and can embed
the expression in the function call as shown above.

   sort(arr1.begin(), arr1.end(), greater() );

 In this call we are using the built in functor "greater" from the
 "" header file. The signature is as:

 template  struct greater {
   bool operator()(const T& lhs, const T& rhs) const;
   };

 As we can see we can use lambda expressions and functors
 interchangeably.

	 class customSort1
	 {
	   public:
		bool operator()(int const& s1, int const& s2)
		 {
			return s1 < s2 ;
		}


	 };

      //Use a custom functor object
      customSort1 customSort1Object ;
      sort(arr1.begin(), arr1.end(), customSort1Object );
      print1("Sorted with the functor object.");

  In the above we have our own functor custom class. We create
  an object of that and pass it to the sort function.

       //Explicit return statement
     	auto add1 = []  (int a, int b ) -> double {
    	  return a + b  ;

       };
       cout << "add1 called:" <<  add1(4, 6)  << endl ;

  The above snippet shows how to specify the type that the lambda
  expression will return. The type is specified just before the body
  of the lambda expression.

      //Without using auto
      //A lambda is a functor object.
      function add2 = [] (int a, int b) {
       cout << "Sum: " << a + b << endl ;
       };

      add2(1, 2);

  The above snippet shows an alternative to using "auto" to
  declare the type for lambda. Since a lambda object is a functor,
  we can use the stl function to specify that the functor takes 2
  integer arguments and returns null. We can then call the functor object
  just as we would call a lambda object.

Dangling Reference

We need to be careful that if we capture the variables by reference then that variable does not go out of scope when we use the lambda object.
File: dangle1.cpp
#include <iostream>
#include <functional>

std::function<int()> make_adder(int x1)
{
    return [&x1]() {
        return x1 + 1;
    };
}

int main()
{
    auto add_one = make_adder(5);
    std::cout << add_one() << std::endl; // This will likely cause a crash or undefined behavior
    return 0;
}
From the above file we have:

std::function make_adder(int x1)
{
    return [&x1]() {
        return x1 + 1;
    };
}

This is clearly wrong. The "x1" is a local variable to the function
"make_adder" . When we use it:

    auto add_one = make_adder(5);
    std::cout << add_one() << std::endl;
    // This will likely cause a crash or undefined behavior

When the lambda "add_one" is used in the cout statement the return
value is referencing the local variable "x1" that has gone out
of scope. The behavior at this point is undefined. This is the
same problem that we encounter with a local reference such as:

int&  function1()
{
  int x1 = 5 ;
  return x1 ;
}

When the "function1()" is called from some other place in the code
then the value being to referred to has gone out of scope.


Mutable

We can change the value of the parameter in the capture clause in the lambda function but that change is not refected in the code calling the lambda function. It's as if the parameter is local to the lambda expression.
File: lambdas13.cpp
Output:
$./a.exe
In main():
a1 = 1, b1 = 1

In add_one():
a1 = 2, b1 = 2

In main() After calling the lambda function:
a1 = 1, b1 = 2
We are passing 2 variables in the parameters clause. The "a1" is passed by value and the "b1" is passed by reference. We are

Invocation


File: lambdas14.cpp
#include <algorithm>
#include <array>
#include <string>
#include <functional>
#include <iostream>

using namespace std ;



int main()
{
  int num1 = 1;
  int num2 = 2;

  auto lambda1Obj = [] (int a, int b) {
      return a + b;
       }  ;

  cout << "lambda1Obj invoked: " << lambda1Obj( 5,6) << endl ;

  //invoked right away
  auto sum = [] (int a, int b) {
    return a + b;
  } (num1, num2);

  cout << "The sum of " << num1 << " and " << num2
  << " is " << sum << endl   ;



}
Output:
$ g++ lambdas14.cpp ; ./a.exe
lambda1Obj invoked11
The sum of 1 and 2 is 3


We have seen how the lambda expression can be evaluated.
We can invoke it inline or we can assign it to a lambda
object and then call the lambda as shown in the code above.

  auto lambda1Obj = [] (int a, int b) {
      return a + b;
       }  ;

  cout << "lambda1Obj invoked: " << lambda1Obj( 5,6) << endl ;

The above snippet shows the lambda object being created. We can
also have the expression executed at the point of definition with
the below snippet.

  //invoked right away
  auto sum = [] (int a, int b) {
    return a + b;
  } (num1, num2);

  cout << "The sum of " << num1 << " and " << num2
  << " is " << sum << endl   ;

Here the variable "sum" is of type integer. The lambda
expression gets executed with arguments "num1" and "num2" .

Exceptions


File: except1.cpp
#include <iostream>
#include <stdexcept>

using namespace std ;

int main()
{

  auto divide = [](int a, int b) -> int
    {
        if (b == 0) {
            throw runtime_error("Division by zero!");
        }
        return a / b;
    };

    try
    {
        int result = divide(10, 0);
        cout << "Result: " << result << endl;
    }
    catch (const runtime_error& e)
    {
        cerr << "Error: " << e.what() << endl;
    }


  auto divide1 = [](int a, int b) noexcept  -> int
    {
        if (b == 0) {
            cout << "Divisor is zero. Can't divide." << endl ;
            return 0;
        }
        return a / b;
    };

  auto divide2 = [](int a, int b) noexcept  -> int
    {
        if (b == 0) {
            cout << "Divisor is zero. Can't divide." << endl ;
             throw runtime_error("Division by zero!");
        }
        return a / b;
    };


}
Output:
$ rm a.exe ; g++ except1.cpp ; ./a.exe
except1.cpp: In lambda function:
except1.cpp:41:14: warning: ‘throw’ will always call ‘terminate’ [-Wterminate]
   41 |              throw runtime_error("Division by zero!");
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Error: Division by zero!


We can have a lambda expression throw an exception. The below snippet
shows how this can be done.

  auto divide = [](int a, int b) -> int
    {
        if (b == 0) {
            throw runtime_error("Division by zero!");
        }
        return a / b;
    };

    try
    {
        int result = divide(10, 0);
        cout << "Result: " << result << endl;
    }
    catch (const runtime_error& e)
    {
        cerr << "Error: " << e.what() << endl;
    }

 The lambda expression checks if the divisor is 0 and if so
 throws a "runbtime_error" exception which is caught later on in
 a try block upon the execution of the lambda object "divide" .

  auto divide1 = [](int a, int b) noexcept  -> int
    {
        if (b == 0) {
            cout << "Divisor is zero. Can't divide." << endl ;
            return 0;
        }
        return a / b;
    };

 We can also have exception specification as shown above. The
 "noexcept" is stating that this lambda expression does not
 throw an exception.


  auto divide2 = [](int a, int b) noexcept  -> int
    {
        if (b == 0) {
            cout << "Divisor is zero. Can't divide." << endl ;
             throw runtime_error("Division by zero!");
        }
        return a / b;
    };

 In the above we have the "noexcept" specification but the
 lambda expression throws an exception. In this case we
 get a compiler warning.