Lambda expressions in C++

  • 2020-04-02 02:52:45
  • OfStack

I'm in C++

Always remind myself, I do C++; But when C++11 came out for such a long time, I did not follow the team, found that I am sorry for their identity, but also good, found that I have not written C++ code for a while. Today, I saw the Lambda expression in C++. Although I have used C#, I still don't know how to use it. I can't even understand the syntax of Lambda. Ok, here is a simple summary of the Lambda in C++, even if it is an account of their own, I am doing C++, I am a C++ programmer.

A simple Code

I'm not a literary person, and I'm not very familiar with the history of Lambda and its history with C++.


#include<iostream>
using namespace std;
 
int main()
{
    int a = 1;
    int b = 2;
 
    auto func = [=, &b](int c)->int {return b += a + c;};
    return 0;
}

When I first saw this piece of code, I got messy. I couldn't understand it. The above code, if you understand, the following content at that time review; If you don't understand, then summarize with me.

The basic grammar

In short, a Lambda function is a function whose syntax is defined as follows:


[capture](parameters) mutable ->return-type{statement}

1.[capture] : capture lists. The capture list always appears at the beginning of a Lambda function. In fact, [] is the Lambda elicitation. The compiler USES this attractor to determine whether the following code is a Lambda function. Capture lists capture variables in context for use by Lambda functions;

2.(parameters) : list of parameters. Same as the argument list of normal functions. If no parameter is required, the parenthesis "()" can be omitted.

3. The mutable: mutable modifier. By default, the Lambda function is always a const function, mutable can cancel its constants. When the modifier is used, the argument list cannot be omitted (even if the argument is empty);

4. - > Return -type: return type. Declare the return type of a function in the form of a trace return type. We can do this with symbols when we don't need to return a value. "- > "Omitted altogether. In addition, if the return type is explicit, you can omit this part and let the compiler deduce the return type.

5.{statement} : function body. The contents are the same as normal functions, except that you can use all captured variables in addition to arguments.

The big difference from normal functions is that in addition to being able to use arguments, Lambda functions can also access data in some context through capture lists. Specifically, the capture list describes what data in the context can be used by a Lambda, and how it can be used (either by value passing or by reference). Syntactically, included in "[]" is the capture list, which consists of capture items separated by commas. The capture list can take the following forms:

1.[var] means the value transfer mode captures the variable var;
2.[=] means that the value passing method captures all the variables of the parent scope (including this);
3.[&var] means the reference passing capture variable var;
4.[&] means that the reference-passing method captures all variables of the parent scope (including this);
5.[this] means that the current this pointer is captured by the value passing method.

We mentioned a parent scope, which is the block of statement that contains Lambda functions, and in plain English the "{}" block of code that contains Lambda. The above capture list can also be combined, for example:

1.[=,&a,&b] means to capture variables a and b by reference and all other variables by value;
2.[&,a,this] means to capture variables a and this in the way of value passing, and all other variables in the way of reference passing.

It is worth noting, however, that capture lists do not allow variables to be passed repeatedly. The following examples are typical of the duplication that can lead to compile-time errors. Such as:

3.[=,a] all variables have been captured in the way of value transfer, but if a is captured repeatedly, an error will be reported;
4.[&,&this] here & has captured all variables by reference passing, and capturing this again is a repetition.

The use of Lambda

To be honest, I don't have much to say about the use of Lambda. My personal understanding is that before Lambda, we used C++ just as well, and we didn't complain about the lack of Lambda in C++. Now with Lambda expressions, it is just more convenient for us to write code. I don't know if you remember the affine object in the C++ STL library, the affine object, for ordinary functions, the affine object can have an initialization state, and these initializations are specified by parameters when the affine object is declared, and are generally stored in the private variable of the affine object; In C++, we generally use functors to implement functions with state, such as the following code:


#include<iostream>
using namespace std;
 
typedef enum
{
    add = 0,
    sub,
    mul,
    divi
}type;
 
class Calc
{
    public:
        Calc(int x, int y):m_x(x), m_y(y){}
 
        int operator()(type i)
        {
            switch (i)
            {
                case add:
                    return m_x + m_y;
                case sub:
                    return m_x - m_y;
                case mul:
                    return m_x * m_y;
                case divi:
                    return m_x / m_y;
            }
        }
 
    private:
        int m_x;
        int m_y;
};
 
int main()
{
    Calc addObj(10, 20);
    cout<<addObj(add)<<endl; //Found that in C++11, the use of enum types also changed and became "stronger" & NBSP; & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent < br / >     return 0;
}

Now that we have Lambda, can we override the above implementation? Look at the code:


#include<iostream>
using namespace std;
     
typedef enum
{    
    add = 0,
    sub,
    mul,
    divi
}type;
     
int main()
{    
    int a = 10;
    int b = 20;
     
    auto func = [=](type i)->int {
        switch (i)
        {
            case add:
                return a + b;
            case sub:
                return a - b;
            case mul:
                return a * b;
            case divi:
                return a / b;
        }
    };
     
    cout<<func(add)<<endl;
}

Obviously, the code is simpler and you write less code. Try Lambda expressions in C++.

All that crazy stuff about Lambda

See the following code:


#include<iostream>        
using namespace std;      
                          
int main()                
{                         
    int j = 10;           
    auto by_val_lambda = [=]{ return j + 1; };
    auto by_ref_lambda = [&]{ return j + 1; };
    cout<<"by_val_lambda: "<<by_val_lambda()<<endl;
    cout<<"by_ref_lambda: "<<by_ref_lambda()<<endl;
                          
    ++j;                  
    cout<<"by_val_lambda: "<<by_val_lambda()<<endl;
    cout<<"by_ref_lambda: "<<by_ref_lambda()<<endl;
                          
    return 0;             
}

The output result of the program is as follows:


by_val_lambda: 11
by_ref_lambda: 11
by_val_lambda: 11
by_ref_lambda: 12

Did you think of that?? So why is that? Why isn't the third output 12?

In by_val_lambda, j is treated as a constant that does not change once initialized (think of it as just a constant with the same name as j in the parent scope), while in by_ref_lambda, j still USES the value in the parent scope. Therefore, when using Lambda functions, if the value to be captured becomes a constant of Lambda function, we will usually capture it by passing it by value. Conversely, if the value you want to capture becomes a variable in the Lambda runtime, you should capture it by reference.

A more confusing code:


#include<iostream>                 
using namespace std;               
                                   
int main()                         
{                                  
    int val = 0;                                   
    // auto const_val_lambda = [=](){ val = 3; }; wrong!!!
                                   
    auto mutable_val_lambda = [=]() mutable{ val = 3; };
    mutable_val_lambda();          
    cout<<val<<endl; // 0
                                   
    auto const_ref_lambda = [&]() { val = 4; };
    const_ref_lambda();            
    cout<<val<<endl; // 4
                                   
    auto mutable_ref_lambda = [&]() mutable{ val = 5; };
    mutable_ref_lambda();          
    cout<<val<<endl; // 5
                                   
    return 0;     
}

This code is used to understand the mutable Lambda expressions keyword. By default, the Lambda function is always a const function, mutable can cancel its constants. As a rule, a const member function cannot modify the value of a non-static member variable in the body of the function. For example, the Lambda expression above can be viewed as the following functor code:


class const_val_lambda
{
public:
    const_val_lambda(int v) : val(v) {}
    void operator()() const { val = 3; } //Constant member function
 
private:
    int val;
};

For the const member function, modify the non-static member variable, so an error occurs. The way the reference is passed does not change the reference itself, but only the value of the reference, so no error is reported. It's all a tangle of rules. Take your time.

conclusion

With Lambda, some people are happy with it, while others are not happy with it. Different people have different views on the same thing. Anyway, as a programmer, you will. The purpose of this article is to make up for my lack of knowledge of Lambda expressions in C++, so as not to see Lambda in someone else's code in the future.


Related articles: