top of page
  • Writer's pictureSunil Kumar Yadav

Understanding C++ Lambda

Updated: Dec 19, 2021

C++ 11 standard introduced many new features in the C++ language making it truly a modern language. Features introduced since C++11 were very significant in comparison to earlier iterations. One such feature adopted since C++ 11 is Lambda. Many programmers consider lambda as ineffectual as intended results can be achieved by functions or function objects. Whether you like or dislike the lambda, but it's important to understand how lambdas work. In this article, we will understand where lambdas make more sense and how to use




What is a Lambda function or Lambda Expression?

C++11 and later, a lambda expression or simply lambda—is a convenient way of defining an anonymous (unnamed) function object (a closure) right at the location where it's invoked or passed as an argument to a function. Typically lambdas are used to encapsulate a few lines of code that are passed to algorithms or asynchronous functions.

In short, lambda is a short snippet of code that isn't worth naming and will not be reused later.

The syntax for lambda expression is:

[Capture clause] (parameter_list) mutable exception ->return_type
 {
 Method definition;
 }

Capture clause: Lambda introducer as per C++ specification.

parameter_list: Also called lambda declarations. Is optional and is similar to the parameter list of a method.

mutable: Optional. Enables variables captured by a call by value to be modified.

exception: Exception specification. Optional. Use “noexcept” to indicate that lambda does not throw an exception.

return_type: Optional. The compiler deduces the return type of the expression on its own. So we don't need to specify a trailing return type explicitly i.e. using -> return-type. But, in some complex cases, the compiler is unable to deduce the return type and we need to specify that.

Method definition: Lambda body enclosed within { ... }


Let's try to understand more about lambdas using a simple example. If we want to count numbers of odd elements in a sequence general answer would be to write a function that counts the odd numbers. Similarly, if we want to count numbers of elements in a sequence that are odd then we need to write another function to do the same. You can see the problem clearly, we need to write multiple functions which are not be used more than once in our application yet they would be part of our codebase. Let's try to use lambdas for the same use case.

// Example 1
// C++ program to find number of occurence of odd number in a sequence
#include<iostream>
#include<vector>        // vector
#include<algorithm>     // count_if() 
using namespace std;

int main()
{
  vector<int> vi{1,2,3,4,5,6,7,8,9};
  
  auto odd_nums=count_if(begin(vi),end(vi), [](int x){return (x%2)!=0;} );
  
  cout<<"odd numbers in sequence are: "<<odd_nums<<endl;

  return 0;
}

From the above example, we can see by the simple use of the lambda function inside the count_if() standard algorithm we've achieved the same result in a single line, without the need of having a separate function to be passed as the predicate. Now if we want to find a number of even numbers in sequence then we've to simply change the comparison inside the lambda expression. Since we are using the STL algorithm, even if we chain contain from vector to list, core logic does not change.


// Lambda expression to return true if input integer is odd, else false
[](int x){return (x%2)!=0;}

Let's try to understand what's going on. In the lambda expression capture clause i.e. [ ] is empty which means lambda expression does not have access to other variables and it can only have access to parameter list and local variables (if any) defined inside lambda body. Inside the lambda body, we are returning true if the input integer is odd else false.


From example 1, we've seen how to use lambda inside algorithm but what if we want to reuse lambda further down the program? We can simply create generalized lambda to reuse the lambda expression. Syntax of generalized lambda is:


auto lambda_name = [Capture clause] (parameter_list) mutable exception -> return_type
 {
    Method definition;
 }

Let's try to understand generalized lambda with the below simple example to find a given number is odd or not.


// Example 2
// Generalized lambda to find whether number odd or not
#include <iostream>
#include<vector>        // vector
using namespace std;

int main()
{
  vector<int> vi{1,2,3,4,5,6,7,8,9};
  
  auto is_odd=[](int x)->bool{ return (x%2)!=0; };  // generalized lambda
  
  cout<<std::boolalpha<<is_odd(85)<<endl;     // output True
   
  return 0;
}

More on Capture Clause

A capture clause of lambda definition is used to specify which scoped variables are accessible and whether they are captured by reference or by value. For example, the empty capture clause [ ] indicates that no variable used by lambda i.e. lambda has access to variables that are local to it. If we need access to variables from scope before lambda expression, we can use [ = ] which will capture all variables by value, and using [ &] will allow access to all variables by reference inside the lambda expression.


Below is short summary of the capture clause:

[]         // No capture. Access to only local scope variables.  
[=]        // capture all variables by value.
[&]        // capture all variables by refernce.
[=,&x]     // capture all variables by value except x by refernce
[&x, y]     //OK, explicitly specified capture by value and refrence
[&, &x]     // error, & is the default still sum_var preceded by &
[i, i]      //error, i is used more than once

Using the above information let's try to implement a simple example to demonstrate how to mix and match the capture clauses to pass a certain variable as a reference and certain as by value. In the below example we are iterating over a vector of integers and printing square of values present in each index of the vector using for_each() STL algorithm by passing lambda expression in predicate section.


// Example 3
// Pass by refernce and mutable example
#include<iostream>
#include<vector>        // vector
#include<algorithm>     // count_if() 
using namespace std;

int main()
{
    vector<int> vi{1,2,3,4,5,6,7,8,9};
    int x{100};
    int y{200};
  
    for_each(begin(vi),end(vi), [&x, y] (int i) mutable 
                            {
                                cout<<i*i<<endl;
                                x=1;  // x passed as refernce and mutable
                                y=1;  // y passed by value
                            } );
    
    cout<< x <<'\t'<< y << endl;   // x=1 and y=200
    return 0;
}

Conclusion

Lambda expressions are simple to use and provide similar functionality as a regular function. Lambdas are very suitable to be used in STL algorithms as predicates or passed as an expression to function arguments.

90 views0 comments

Recent Posts

See All

Comments


bottom of page