The C++-11 brought lambda expressions, which are a convient way of defining clojures. Before C++-11 a typical way to pass function-like objects to algorithms was to define a class/struct with operator()(…). For example for sorting vector of pairs of 2 integers by the value of the second element would in C++98 look something like:

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

// compare pair of ints by the value of the second element
// in C++98 explicit definition of class/struct or function 
// needed
struct value_less
{
   // ... maybe some other parameters, if needed
    bool operator()(const std::pair<int,int>& p1, const std::pair<int,int>& p2)
    {
        return p1.second < p2.second;
    }
};

int main()
{
    std::vector<std::pair<int, int> > vec;
    for (int i = 1; i < 10; ++i)
        vec.push_back(std::pair<int,int>(i, 10-i));
        
    std::sort(vec.begin(), vec.end(), value_less());   
}

Since C++-11, lambda clojure can be passed to an algorithm directly:

 std::sort(vec.begin(), vec.end(),
              [](const std::pair<int,int>& p1,
                 const std::pair<int,int>& p2)
              { return p1.second < p2.second;});

or since C++14, with the templated lambdas even more simpler:

std::sort(vec.begin(), vec.end(),
              [](const auto& p1, const auto& p2)
              { return p1.second < p2.second;});

So what are lambda’s under the hood ?

For most practical work with lambda’s I came up with the following list, what should be sufficient to know:

  • Each lambda expression has it’s own type, even if they have the same signature, for example:
 // l1 and l2 lambdas have different types
    auto l1 = [](int a, int b) { return a < b; };
    auto l2 = [](int a, int b) { return a < b; };
  • for passing lambda’s around, std::function can be used. In the above example:
    // for using std::function only signature of lambda matters,
    // not it's type !
    // so l2 is of different type 
    // but with same signature as l1
    // so it can be assigned to f
    std::function<bool(int,int)> f = l1; 
    f = l2; 
  • besides providing parameters to lambda, lambda can capture variables from some outer scope by value or by reference by putting them withing lambda capture’s square brackets []:
int n {0};

auto lval = [n](int v) { n += v;};   // capture n by value
lval(1);                             // call the lambda
cout << n << endl;                   // it's still n==0 here

auto lref = [&n](int v) { n += v;};  // capture n by reference
lref(1);                             // call the lambda
cout << n << endl;                   // it's now: n==1
  • lambda’s are similiar to classes, or rather function objects. Lambda can be viewed as regular class with restrictions:
    • class consists only of one member function and that is operator() with provided arguments
    • lambda’s capture block are class’s member variables
    • each lambda function has it’s own unique type, even if they have same signature. In the above example it would look something like
Lambda with capture by valueLambda with capture by reference
int n{0};
auto lval= [n](int v) { n += v;};
int n{0};
auto lref = [&n](int v) { n += v; };
…corresponds to this class:…corresponds to this class:
class Lval
{
public:
Lval(int n) : n{n} {}
void operator()(int v)
{
n += v;
}
private:
// members from lambda’s capture block
int n;
};
class Lref
{
public:
Lref(int& n) : n{n} {}
void operator()(int v)
{
n += v;
}
private:
// members from lambda’s capture block
int& n;
};

The left column is capture by value, and the right column is capture by reference. One important thing to keep in mind if capturing by reference: you better make sure that the referenced variable outlives the lambda, otherwise unpredicted behaviour can happen.

Leave a comment

Your email address will not be published. Required fields are marked *