Lambda expression | C++

/Lambda expression | C++

Introduction

C++11 provides the ability to create anonymous functions, called lambda functions. It allows a function to be defined at the point where it’s needed in another expression. It is a function that we can write inline in our code in order to pass in to another function.

Syntax

[ capture clause ] (parameters) -> return-type  
{   
   // Definition of method   
}

[] identifier, called the capture specification, tells the compiler we’re creating a lambda function. Next up, like any other function, we need an argument list: (). In C++11, if the compiler can deduce the return value of the lambda function, it will do it rather than force you to include it. In some complex case as in conditional statement, compiler can’t make out the return type and we need to specify that.

 

Capture

A lambda with empty capture clause [ ] can access only those variable which are local to it. A lambda expression can have more power than an ordinary function by having access to variables from the enclosing scope. We can capture external variables from enclosing scope by three ways :

  • Capture by reference i.e. [&] : capture all external variable by reference
  • Capture by value i.e.  [=] : capture all external variable by value
  • Capture by both (mixed capture) i.e. [a, &b] : capture a by value and b by reference

 

Capture example

  • Capture by value
    auto upperBound = 42;
    [upperBound](int value) {
      return 0 < value && value < upperBound;
    }
  • Capture by reference
    auto upperBound = 42;
    [&upperBound](int value) {
      return 0 < value && value < upperBound;
    }
  • Capture all by value
    [=] will save non-static local variables needed in the body of the lambda by value.

    m_upperBound = 42;
    [=](int value) {
      // Doesn't compile, m_upperBound is not a non-static local
      return 0 < value && value < m_upperBound; 
    }
  • Capture all by reference
    [&] with this capture block, all the necessary and available variables will be captured by reference. Same notions apply here as for capturing all variables by value.
  • Capture all by value, and except few
    With using [=, &divisor] as a capture, everything will be captured by value except for the variable that is explicitly listed preceded with an &.
  • Capture this
    You can also save the surrounding object like this: [this]. this is a pointer to the enclosing object, so if you capture this, you’ll have access to the members.

    [this](int value) {
      return 0 < value && value < this->m_upperBound;
    }

 

Parameters

The list of parameters, as usual, come in between parentheses (()). Some remarks:

  • In C++11 you cannot use auto as a type-specifier. But since C++14, you may.
  • If there are no parameters passed to a lambda, the empty list can be omitted. Meaning that []{} is a valid lambda expression. Though for readability reasons, it’s better not to remove the empty parenthesis.

 

Return type

The return type of lambda expressions can be and most often is omitted when

  • it is void
  • if it deducible (so if you could use auto)

If you do have to or want to declare them, you must use the [trailing return type syntax] meaning that you will declare the type between the parameter list and the body, putting the type after an arrow like this:

[](int value) -> bool {
  return 0 < value && value < 10;
}

 

Body

It’s just a normal body. As a best practice, it should be a quite lean one. If you need something longer, heavier, maybe a lambda is not the way you go. As a reminder let’s mention that you can work with the following variables:

  • local variables declared in the body
  • parameters passed into the lambda
  • non-static local variable captured within the square brackets called a “capture”

 

Example

// Lambda expression
using namespace std; 

int main() 
{ 
  vector<int> v1 = {3, 1, 7, 9}; 
  vector<int> v2 = {10, 2, 7, 16, 9}; 

  // Access v1 and v2 by reference 
  auto pushinto = [&] (int m) 
  { 
    v1.push_back(m); 
    v2.push_back(m); 
  }; 

  // Pushes 20 in both v1 and v2 
  pushinto(20); 

  // Access v1 by copy 
  [v1]() 
  { 
    for (auto p = v1.begin(); p != v1.end(); p++) 
    { 
      cout << *p << " "; 
    } 
  }; 

  int N = 5; 

  // Find first number greater than N 
  // [N] denotes, can access only by value 
  vector<int>:: iterator p = find_if(v1.begin(), v1.end(), [N](int i) 
  { 
    return i > N; 
  }); 

  // Count numbers greater than or equal to N 
  // [=] denotes, can access all variable 
  int count_N = count_if(v1.begin(), v1.end(), [=](int a) 
  { 
    return (a >= N); 
  }); 
}

 

 

September 20th, 2019|Categories: Programming|Tags: |
avatar
  Subscribe  
Notify of