[ русский ]

Introduction to C++11: lambda expressions

In previous posts I familiarized you with some interesting features of the new C++11 Standard. Today I continue the diving and tell about lambda expressions.

The C++11 Standard finally provided such useful thing as lambda expressions. Advanced C++ developer will exclaim: "But lambda are already exist in boost!". That's right. But the new lambda are more powerful and, at my humble opinion, more practical. However I'm not going to compare realization of these lambdas. My point is to tell what is lambda and what it is used for.

Without getting into the origins of appearance of lambdas, I tell, that lambda is a shorter form of functor. Some kind of nameless functor. Consider the following example.

Suppose we have some integer vector. The task is to sort the items so that elements on the left were odd, and on the right — even. To accomplish this, we need to write a functor and pass it to std::sort.

struct Comparator : public std::binary_function<int, int, bool>
{
    bool operator()(int lhs, int rhs)const
    {
        if (lhs & 1  &&  rhs & 1)
            return lhs < rhs;
        return lhs & 1;
    }
};

std::sort(vec.begin(), vec.end(), Comparator());

Writing a functor is an easy task, but, anyway we write too much code and code readability goes lower. Writing a whole functor class just to use it once isn't the best design. So here we can use the lambda functions. With their help the above written code can be written as:

std::sort(vec.begin(), vec.end(), [](int lhs, int rhs) -> bool {
    if (lhs & 1  &&  rhs & 1)
        return lhs < rhs;
    return lhs & 1;
});

This form is more illustrative and more compact. But let me consider the syntax in detail. In general, it can be written as:

[captures](arg1, arg2) -> result_type { /* code */ }
arg1, arg2
are arguments, i.e. that is passed by the algorithm to the functor(lambda)
result_type

is a type of return value. It may seem a bit unusual, because before the type was always written before the entity (variable, function). But you get used to it quickly.

Note

It is worth noting that if lambda consists only of the return operator, the type might not be specified. E.g:

std::sort(vec.begin(), vec.end(), [](int lhs, int rhs) {
    return lhs & 1;
});

Now let's talk about the captures. Captures define the environment variables that should be available within the lambda. These variables can be captured by value or by reference.

int max = 4;

// by value
std::sort(vec.begin(), vec.end(), [max](int lhs, int rhs) {
    return lhs < max;
});
// by reference
std::sort(vec.begin(), vec.end(), [&max](int lhs, int rhs) {
    return lhs < max;
});

Also, you can capture all the variables out of scope:

// by value
std::sort(vec.begin(), vec.end(), [=](int lhs, int rhs) {
    return lhs < someVar;
});

// by reference
std::sort(vec.begin(), vec.end(), [&](int lhs, int rhs) {
    return lhs < otherVar;
});

Lambda as well as the functors, can be passed to the function and they are easily assigned to variables.

auto square = [](int x) { return x * x; };
std::cout << square(16) << std::endl;

If lambda is created in a certain class method and it is necessary address from it to a certain attribute, then the capture of this attribute does not work. In order to to address to any attribute/method, it is necessary to capture this. At the same time it's not necessary to put this in the lambda body before the attribute/method.

class Foo
{
public:
    Foo(): _x(5) {}

    void doSomething() {
        // если вместо this поставить _x — будет ошибка!
        auto lambda = [this](int x) {
            std::cout << _x * x << std::endl;
        };

        lambda(4);
    }

private:
    int _x;
};