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 value | Lambda 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.