Lambda expressions in C++11

Lambda comes from the concept of functional programming. C++11 finally adds lambda this time.

Lambda expressions have the following advantages:

1. Declarative programming style: define the target function or function object anonymously in place, without writing an additional named function or function object. Write programs in a more direct way, with good readability and maintainability.

2. Simplicity: There is no need to write an additional function or function object, which avoids code expansion and functional dispersion, allowing developers to focus more on the problem at hand, while also achieving higher productivity.

3. Implement functional closures when and where needed to make the program more flexible.

The concept and basic usage of lambda expressions

A lambda expression defines an anonymous function and can capture a range of variables. The syntactic form of lambda expression can be simply summarized as follows:

[capture](params) opt -> ret { body; };

Among them: capture is the capture list; params is the parameter list; opt is the function option; ret is the return value type; body is the function body.

So a complete lambda expression looks like this:

 auto f = [](int a) -> int { return a + 1;};

        cout << f(1) << endl; //Output: 2

As you can see, the above defines a small closure in one line of code, which is used to add 1 to the input and return it.

In C++11, the return value of a lambda expression is defined through the return value postfix syntax introduced earlier. In fact, many times, the return value of a lambda expression is very obvious, such as the above example. Therefore, running in C++11 omits the return value definition of a lambda expression:

 auto f = [](int a) {return a + 1; };

In this way, the compiler will automatically deduce the return value type based on the return statement.

It should be noted that the initialization list cannot be used for automatic derivation of return values.

 auto x1 = [](int i){ return i; }; //OK: return type is int

        auto x2 = [](){ return {1, 2};}; // error: The return value type cannot be deduced

At this time we need to display the specific return value type.

In addition, when a lambda expression does not have a parameter list, the parameter list can be omitted. Therefore, writing like the following is correct:

 auto f1 = [] () { return 1; };

        auto f2 = [] { return 1; }; //Omit empty parameter list

Lambda expressions can capture a range of variables through a capture list:

1. [] does not capture any variables.

2. [ & amp;] captures all variables in the external scope and uses them as references in the function body (capture by reference).

3. [=] captures all variables in the outer scope and uses them as copies in the function body (capture by value).

4. [=, & foo] captures all variables in the outer scope by value and captures the foo variable by reference.

5. [bar] captures the bar variable by value without capturing other variables.

6. [this] captures the this pointer in the current class, allowing the lambda expression to have the same access rights as the member functions of the current class. This option is added by default if & or = has been used. The purpose of capturing this is to use the member functions and member variables of the current class in the lambda.

Basic usage of lambda expressions

#include <iostream>
using namespace std;

class A
{
public:
    int i_ = 0;

    void func(int x, int y)
    {
        ///auto x1 = [] { return _i; }; ///error, no external variables are captured

        auto x2 = [=] { return i_ + x + y; }; ///OK, capture all external variables

        auto x3 = [ & amp;] { return i_ + x + y; }; ///OK, capture all external variables

        auto x4 = [this] { return i_; }; ///OK, capture this pointer

        ///auto x5 = [this] { return i_ + x + y; }; ///error, x, y not captured

        auto x6 = [this, x, y] { return i_ + x + y; }; ///OK, capture this pointer, x, y

        auto x7 = [this]{ return i_ + + ; }; ///OK, capture this pointer and modify the value of the member

    }

};

int main()
{
    int a = 0, b = 1;

    auto f1 = [] { return a; }; ///error, no external variables are captured

    auto f2 = [ & amp;] { return a + + ; }; ///OK, capture all external variables and perform self-add operation on a

    auto f3 = [=] { return a; }; ///OK, capture all external variables and return a

    auto f4 = [=] { return a + + ; }; ///error, a is captured by copying and cannot be modified

    auto f5 = [a] { return a + b; }; ///error, variable b is not captured

    auto f6 = [a, & amp;b] { return a + (b + + ); }; ///OK, capture the references of a and b, and perform self-add operation on b

    auto f7 = [=, & amp;b] { return a + (b + + ); }; ///OK, capture all external variables and references to b, and perform self-add operation on b

    return 0;
}

As you can see from the above example, the capture list of a lambda expression finely controls the external variables that the lambda expression can access and how to access these variables.

It should be noted that by default, lambda expressions cannot modify external variables captured through copying. If we want to modify these variables, we need to capture them by reference.

 int a = 0;
    auto f = [=] { return a; };

    a + = 1;

    cout << f() << endl;

In this example, the lambda expression captures all external variables by value. At the moment of capture, the value of a has been copied to f. After that, a is modified, but at this time, a stored in f is still the value at the time of capture, so the final output result is 0.

If we want the lambda expression to be able to access external variables in time when it is called, we should use reference capture.

If you want to modify the external variable captured by value, you need to explicitly specify the lambda expression as mutable:

int main()
{
    int a = 0;

    ///auto f1 = [=](){ return a + + ; }; ///error, modify external variables captured by value
    auto f2 = [=]()mutable { return a + + ; }; ///OK, mutable

    return 0;
}

One thing to note is that the lambda expression modified by mutable must specify the parameter list even if it has no parameters.

The types of lambda expressions are called “closure types” in C++11. It is a special, anonymous non-nunion type. Therefore, we can think of it as a class with operator(), a functor. Therefore, we can use std::function and std::bind to store and manipulate lambda expressions:

std::function<int(int)> f1 = [] (int a) { return a; };
std::function<int(void)> f2 = std::bind( [] (int a) { return a; }, 123);

In addition, a lambda expression that does not capture any variables can also be converted into an ordinary function pointer:

using func_t = int(*)(int);
func_t f = [] (int a) { return a; };
f(123);

Lambda expressions can be said to be “syntactic sugar” for defining functor closures in place. Any external variables captured by its capture list will eventually become member variables of the closure type. If the operator() of a class that uses member variables can be directly converted into an ordinary function pointer, the this pointer of the lambda expression itself will be lost. Lambda expressions that do not capture any external variables do not have this problem.

This can also naturally explain why capture by value cannot modify the captured external variables. Because according to the C++ flag, the operator() of a lambda expression is const by default. A const member function cannot modify the value of a member variable. The role of mutable is to cancel the const of operator().

Declarative programming style, concise code

Defining anonymous functions in place eliminates the need to define function objects, greatly simplifying the calling of standard library algorithms. Before C++11, we had to call the for_each function to print out the even numbers in the vector. The code is as follows:

#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
using namespace std;

classCountEven
{
public:
    CountEven(int & c):count(c){}

    void operator()(int val)
    {
        if (!(val & amp; 1))
        {
             + + count;
        }
    }

private:
    int & count;
};

int main()
{
    int count = 0;
    vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8};

    for_each(v.begin(), v.end(), CountEven(count));

    cout << "The number of even is " << count << endl;

    return 0;
}

Writing this way is tedious and error-prone. With the lambda expression, we can use the real closure concept to replace the functor here. The code is as follows:

#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
using namespace std;

classCountEven
{
public:
    CountEven(int & c):count(c){}

    void operator()(int val)
    {
        if (!(val & amp; 1))
        {
             + + count;
        }
    }

private:
    int & count;
};

int main()
{
    int count = 0;
    vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8};

    for_each(v.begin(), v.end(), CountEven(count));

    cout << "The number of even is " << count << endl;

    count = 0;
    for_each(v.begin(), v.end(), [ & amp;count](int val)
            {
                if (!(val & amp; 1))
                {
                     + + count;
                }
            }
            );

    cout << "The number of even is " << count << endl;

    return 0;
}

The value of lambda expressions is that by encapsulating short functional closures in place, it is extremely convenient to express the specific operations we want to perform, and to integrate the context more closely.