[C++] Lambda expression

C++ Lambdas

Create a Lambda expression in C++

C++ Lambda function with parameters

C++ Lambda function with return type

C++ Lambda function capture clause

capture by value

capture by reference

Example: C++ Lambda function as parameter of STL algorithm


C++ Lambda

C++ Lambda expressionsallow us to define anonymous function objects (functors) that can be used either inline or passed as a single argument.

Lambda expressions were introduced in C++11 to create anonymous functors in a more convenient and concise way.

By using Lambda expressions, we no longer need to overload the () operator in a separate class or struct.

Create a Lambda expression in C++

A basic lambda expression could look like this:

auto hello = []() {
  // lambda function body
};

in,

  • [] is called lambda quoter to indicate the beginning of a lambda expression
  • () is called argument list similar to the () operator in normal functions

The above code is equivalent to:

void hello() {
  // function body
}

Like normal functions, we can invoke lambda expressions in the following ways:

hello();

Note: The auto keyword is used to automatically determine the return type of the lambda expression

Example 1: C++ Lambda function

#include <iostream>
using namespace std;

int main() {

  // print Hello world with Lambda function
  auto hello = []() {
    cout << "Hello World!";
  };

  // call the lambda function
  hello();

  return 0;
}

Output

Hello World!

In the above example, we have created a simple program that prints “Hello World!” in C++ lambda expression

I. Created the lambda function and assigned it to a variable called hello

auto hello = []() {
  cout << "Hello World!";
};

II. Use hello as the variable name, and add the () operator to complete the call to the lambda function:

// print "Hello World!"
hello(); 

C++ Lambda function with parameters

Like regular functions, lambda expressions can also accept parameters. for example:

#include <iostream>
using namespace std;

int main() {

  // lambda function with two integer types
  // take arguments and print their sum
  auto add = [] (int a, int b) {
   cout << "Sum = " << a + b;
  };

  // call the lambda expression
  add(100, 10);

  return 0;
}

Output

Sum = 110

In the above example, we created a lambda function capable of receiving two integer parameters and displaying their sum.

auto add = [] (int a, int b) {
  cout << "Sum = " << a + b;
};

Equivalent to:

void add(int a, int b) {
  cout << "Sum = " << a + b;
}

Then we call the lambda function by passing two integer parameters:

// return 110
add(100, 10);

C++ Lambda function with return type

Like ordinary functions, C++ lambda expressions can also have a return type.

The compiler can implicitly infer the return type of a lambda expression from the return statement.

auto add = [] (int a, int b) {
  // return int type data
  return a + b;
};

In the above example, we did not explicitly define the return type of the lambda function. This is because there is one return statement, and the compiler can judge it clearly.

But for multiple return statements of different data types, we must explicitly define their types. For example:

auto operation = [] (int a, int b, string op) -> double {
  if (op == "sum") {
    // return int type
    return a + b;
  }
  else {
    // return double type
    return (a + b) / 2.0;
  }
};

Note the above code -> double. The return type is clearly defined as double, because there are multiple statements that return different types of values according to the value of op.

No matter what type of values returned by various return statements, they will be explicitly converted to double type and returned.

Example 2: C++ Lambda – explicit return type

#include<iostream>
using namespace std;

int main() {

  // lambda function shows return type 'double'
  // Return sum or average according to the operator
  auto operation = [] (int a, int b, string op) -> double {
    if (op == "sum") {
      return a + b;
    }
    else {
      return (a + b) / 2.0;
    }
  };

  int num1 = 1;
  int num2 = 2;

  // Use sum to mark the completion of the calculation of num1, num2
  auto sum = operation(num1, num2, "sum");
  cout << "Sum = " << sum << endl;

  // Use average to mark the completion of the calculation of num1, num2
  auto avg = operation(num1, num2, "avg");
  cout << "Average = " << avg;

  return 0;
}

Output

Sum = 3
Average = 1.5

In the above example, we created a lambda function to find the operand:

  • two integers and sum
  • average avg of two integers
auto operation = [] (int a, int b, string op) -> double {
  if (op == "sum") {
    // return 'int'
    return a + b;
  }
  else {
    // return 'double'
    return (a + b) / 2.0;
  }
};

In main(), we need to first get the sum of num1 and num2 by passing “sum” as the third parameter:

auto sum = operation(num1, num2, "sum");

Here, although the lambda returns an integer value, it is explicitly cast to type double.

Then, we get the average of the two numbers by passing some other strings as arguments:

auto avg = operation(num1, num2, "avg");

C++ Lambda function capture clause

By default, lambda functions cannot access variables inside nested functions. If we need to access these variables, we need to use capture clauses.

Variables can be captured in two ways:

Capture by value

This is similar to call by value. Here, the actual value is copied once when the lambda function is created.

Note: We can only read the variable in the body of the lambda function, but not modify it.

Through a lambda expression that captures the value:

int num_main = 100;

// Get access to num_main from the function body
auto my_lambda = [num_main] () {
  cout << num_main;
};

Here, [num_main] allows the lambda to access the num_main variable.

If we remove num_main from the capture clause, we will make a mistake because num_main cannot be accessed from the body of the lambda function.

capture by reference

This is similar to capturing by reference, that is, the lambda function can access the variable address.

Note: We can read the variable and also modify it inside the lambda function.

A basic lambda expression with reference capture is as follows:

int num_main = 100;

// Access the address of the num_main variable
auto my_lambda = [ & num_main] () {
  num_main = 900;
};

In [ & amp; num_main] we used the & amp; operator. This shows that we are capturing the address of the num_main variable.

Example 3: C++ Lambda capture by value

#include<iostream>
using namespace std;

int main() {

  int initial_sum = 100;

  // Capture by the value of initial_sum
  auto add_to_sum = [initial_sum] (int num) {
    return initial_sum + num;
  };

  int final_sum = add_to_sum(78);
  cout << "100 + 78 = " << final_sum;

  return 0;
}

Output

100 + 78 = 178

In the above example, we have created a lambda expression that returns the sum of a local variable called initial_sum and an integer parameter num.

auto add_to_sum = [initial_sum] (int num) {
  return initial_sum + num;
};

Here, [initial_sum] is passed into the function by capturing the value of initial_sum.

Then, we call the function and store its return value in the final_sum variable.

int final_sum = add_to_sum(78);

In this lambda function:

  • num is 78
  • initial_sum is 100

So the result becomes 100 + 78 which is 178.

Note: Suppose we want to capture multiple variables by value. for example:

auto my_lambda = [a, b, c, d, e] (){
  // lambda function
}

As you can see, listing the variables one by one would make the whole process very cumbersome. To make things easier, we can simply try to use implicit capture by value, for example:

auto my_lambda = [=] (){
  // lambda function
}

Here, [=] means that all variables in the function are captured by value.

Example 4: C++ Lambda capture by reference

#include <iostream>
using namespace std;

int main() {

  int num = 0;

  cout << "Initially, num = " << num << endl;
  
  // [ & amp;num] captures the address of the num variable
  auto increment_by_one = [ & num] () {
    cout << "Incrementing num by 1.\
";
    num++;
  };

  // invoke lambda function
  increment_by_one();

  cout << "Now, num = " << num << endl;

  return 0;
}

Output

Initially, num = 0
Incrementing num by 1.
Now, num = 1

In the above example, we have created a lambda function that increments the value of a local variable num by 1.

auto increment_by_one = [ & num] () {
  cout << "Incrementing num by 1.\
";
  num++;
};

Here [ & num] is used to capture num by reference.

Initially, the value of num is 0.

Then, we call the lambda expression increment_by_one(). Increases the value of num by 1.

Note: In order to capture all variables in nested functions, we can use implicit capture by reference. For example:

auto my_lambda = [ &] (){
  // lambda function
}

Here, [ & amp;] means that all variables are captured by reference.

Example: C++ Lambda function as parameter of STL algorithm

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main() {

  // Initialize integer type vector
  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.";

  return 0;
}

Output

There are 5 even numbers.

In the above example, we used a lambda function in the count_if algorithm to count all even numbers in the nums vector:

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

Note that we use a lambda expression as the third parameter of count_if. This lambda expression receives the integer num. Returns true if num is even.

In addition, we also write lambda expressions in the parameter list.

[](int num) {
  return num % 2 == 0;
}