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:

 // 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 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; 
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 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 Reply

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