C + + lambda expressions and Function objects
Lambda expressions are a new technique introduced in c++11, which allows you to write an embedded anonymous function that replaces a stand-alone function or function object and makes the code more readable. But essentially, a lambda expression is just a syntactic sugar, because all the work that it can do can be done with other slightly more complex code. But its simple grammar has a profound effect on C + +. In a broad sense, a LAMDBA expression produces a function object. In a class, you can overload the function call operator (), at which point the object of the class can behave like a function object, which we refer to as function objects or functor (functor). Compared to lambda expressions, function objects have their own unique advantages. Now let's start with a concrete explanation of these two black technologies. lambda expression
Let's start with a simple example, where we define a lambda expression that can output a string, usually starting with square brackets [] and ending with curly braces {}, which contains the LAMDBA expression body in the curly braces as defined functions:
Defines a simple lambda expression of
auto Basiclambda = [] {cout << "Hello, world!" << Endl;};
Call
Basiclambda (); Output: Hello, world!
Above is the simplest lambda expression, with no arguments. If you need parameters, you should put them in parentheses like a function, and if there is a return value, the return type is placed behind the->, the trailing return type, and of course you can ignore the return type, the lambda will help you automatically infer the return type:
Indicates return type
auto add = [] (int a, int b)-> int {Returns a + B;};
Auto inference return type
auto multiply = [] (int a, int b) {Returns a * b;};
int sum = Add (2, 5); Output: 7
int product = Multiply (2, 5); Output: 10
You might think about the meaning of the square brackets at the front of the lambda expression. In fact, this is a lambda expression of a very important function, that is, closures. Let's start with the general principle of lambda expressions: Each time you define a lambda expression, the compiler automatically generates an anonymous class (which, of course, overloads the () operator), which we call the closure type (closure type). At runtime, the lambda expression returns an anonymous closure instance, actually a right value. So, the result of our lambda expression above is a closed package. One of the strengths of closures is that it can capture variables in its encapsulated scope by passing values or references, which are used to define capture patterns and variables, and we call them lambda capture blocks. Look at the following example:
int main ()
{
int x = ten;
Auto add_x = [x] (int a) {return a + x;}; Copy capture x
auto multiply_x = [&x] (int a) {return a * x;}; Reference capture x
cout << add_x (Ten) << "" << multiply_x (a) << Endl;
Output:
0;
When a lambda snap block is empty, it means that no variables are captured. However, the above add_x captures the variable x in the form of a copy, while multiply captures X in a reference way. As we've said before, lambda expressions are the result of a closure class, so capturing is a matter of time. For a copy-by-value capture method, a non-static data member of the corresponding type is added to the class accordingly. At run time, these member variables are initialized with the copied values, resulting in a closure. As mentioned earlier, the closure class also implements the overload of the function call operator, which is typically:
Class Closuretype
{public
:
//...
ReturnType operator (params) const {body};
}
This means that a lambda expression cannot modify a variable that is caught by copying, because the overloaded method of the function call operator is a const property. Sometimes, you want to change the value captured by the value, then use the mutable, as the following example:
int main ()
{
int x = ten;
Auto add_x = [x] (int a) mutable {x *= 2; return a + x;}; Copy capture x
cout << add_x (Ten) << Endl;//output
0;
}
This is why. Because once you mark the lambda expression as mutable, the implementation of the function call operator is not a const attribute:
Class Closuretype
{public
:
//...
ReturnType operator (params) {body};
}
For a reference capture, you can modify the captured value in a lambda expression, regardless of whether you mark mutable or not. As to whether there are corresponding members in the closure class, the answer given in the C + + standard is: not clear, it seems to be related to the implementation of the specific. As far as the depths are concerned, there is one more point to note: Lambda expressions cannot be assigned values:
Auto A = [] {cout << "a" << Endl;};
Auto B = [] {cout << "B" << Endl;};
A = b; Illegal, lambda cannot be assigned
auto C = A; Valid, generating a copy
You might think that a is the same type of function as B (the compiler also shows the same type: lambda [] void ()-> void), so why not assign values to each other? Because the assignment operator is disabled:
closuretype& operator= (const closuretype&) = delete;
But the copy constructor is not disabled, so you can still use a lambda expression to initialize another lambda expression and produce a copy. And the lambda expression can also be assigned to the corresponding function pointer, which allows you to think of a lambda expression as a pointer to the corresponding function type.
Gossip less, to the point, capture the way can be a reference can also be copied, but specifically, there are several situations in which you can capture variables in their scope: []: By default, no variables are captured; [=]: By default, all variables are captured by value; [;]: By default, all variables are captured by reference; [x] : X is captured by value only, other variables are not captured; [&x]: X is captured by reference only, other variables are not captured; [=, &x]: By default, all variables are captured by value, but X is the exception, captured by reference; [;, X]: By default, all variables are captured by reference, but X is the exception, Capture by value; [This]: captures the current object (in fact, the copy pointer) by reference; [*this]: captures the current object by means of a value;
In the above capture mode, note that it is best not to use [=] and [n] to catch all variables by default. First of all, the default reference captures all the variables, and you are likely to have a hanging reference (dangling references) because the reference capture does not extend the declaration cycle of the referenced variable:
Std::function<int (int) > add_x (int x)
{return
[ampersand] (int a) {return x + A;};
}
Because the parameter x is only a temporary variable, the function call is destroyed, but the returned lambda expression references the variable, but when the expression is invoked, the reference is a garbage value, so there is no meaningful result. You might think that you can solve the problem by passing a value:
Std::function<int (int) > add_x (int x)
{return
[=] (int a) {return x + A;};
}
Yes, you can avoid hanging reference problems by using the default method of passing values. However, it is still risky to capture all variables with default values, see the following example:
Class Filter
{public
:
filter (int divisorval):
divisor{divisorval}
{}
std::function <bool (int) > GetFilter ()
{return
[=] (int value) {return value% divisor = 0;};
}
Private:
int divisor;
};
There is a member method in this class that can return a lambda expression that uses the data member divisor of the class. And the default value is used to catch all variables. You might think that this lambda expression also captures a copy of divisor, but it's actually a big mistake. Where did the problem arise? Because the data member divisor is not visible to the lambda expression, you can use the following code to verify that:
Class, the following cannot be compiled because divisor does not
std::function<bool (int) > GetFilter ()
{return
[divisor] () in the scope of the lambda capture ( int value) {return value% divisor = 0;};
}
So why is the original code able to capture it. Think carefully that each non-static method has a this pointer variable, and with this pointer, you can approach any member variable, so the lambda expression actually captures a copy of this pointer, so the original code is equivalent to:
Std::function<bool (int) > GetFilter ()
{return
[this] (int value) {return value% This->divisor = 0;};
}
Although it is still captured as a value, the pointer is captured, in fact, it is the equivalent of capturing the current class object in a reference way, so the closure of the lambda expression is bound to a class object, which is also dangerous because you still have the possibility of using the lambda expression after the class object is destructor, so it is similar to " The question of hanging references will also arise. Therefore, it is still unsafe to catch all variables with the default value, mainly because of the copy of the pointer variable, and actually the value is passed by reference.
In the previous example, you can also see that the lambda expression can be returned as a value. We know that a lambda expression can be assigned to a function pointer of the corresponding type. But using a function pointer is not as convenient as it seems. So STL definition in <functional> header file provides a Polymorphic function object encapsulation Std::function, which is similar to function pointer. It can bind any class function object as long as the parameter is the same as the return type. Like the following function wrapper that returns a bool and receives two int:
Std::function<bool (int, int) > wrapper = [] (int x, int y) {return x < y;};
A more important application of a lambda expression is the argument it can use for a function, in which case the callback function can be implemented. In fact, the most common is in the STL algorithm, for example, you want to count the number of elements in an array to meet certain conditions, the lambda expression given the condition, passed to the COUNT_IF function:
int value = 3;
Vector<int> v {1, 3, 5, 2, 6, ten};
int count = std::count_if (V.beigin (), V.end (), [value] (int x) {return x > value;});
For example, if you want to generate the Fibonacci sequence and then save it in an array, you can use the Generate function and assist the lambda expression:
Vector<int> V (a);
int a = 0;
int b = 1;
Std::generate (V.begin (), V.end (), [&a, &b] {int value = b; b = B + A; a = value; return value;});
At this time v {1, 1, 2, 3, 5, 8, 13, 21, 34, 55}
In addition, lambda expressions are used for sorting criteria for objects:
Class person
{public
: Person
(const string&-I, const string& last):
Firstname{first}, Lastname{last}
{} person
() = default;
String A () const {return firstName;}
String last () const {return lastName;}
Private:
string firstName;
string lastName;
};
int main ()
{
vector<person> vp;
// ... Add person information
//Sort by name
Std::sort (Vp.begin (), Vp.end (), [] (const person& p1, const person& p2)
{ Return P1.last () < P2.last () | | (P1.last () = = P2.last () && P1.first () < P2.first ()); });
// ...
return 0;
}
In short, for most STL algorithms, it is very flexible to match lambda expressions to achieve desired results.
The basic use of lambda expressions is described, and the complete syntax for lambda expressions is given:
Full syntax
[capture-list] (params) mutable (optional) constexpr (optional) (c++17) exception attribute-> ret {body }
//optional simplified Syntax
[capture-list] (params)-> ret {body}
[capture-list] (params) {body}
The first one is the complete syntax, followed by the 3 optional syntax. This means that the lambda expression is quite flexible, but there are certain limitations, such as you use the trailing return type, so you cannot omit the argument list, although it may be empty. For the complete syntax, we have a description of the various parts: capture-list: Capture list, this needless to say, the previous said, remember that it can not be omitted; params: Parameter list, can be omitted (but must be followed by the function body); mutable: Optionally, after the lambda expression is marked as mutable, the function body can modify the variables captured by the value-passing method; constexpr: Optional, c++17, you can specify that a lambda expression is a constant function; exception: Optionally, specify the exception that the lambda expression can throw; attribute: Optionally, specify the attributes of the lambda expression; ret: Optional, return value type; Body: function executor.
If you want to learn more, you can refer to the cppreference lambda. new characteristics of Lambda
In c++14, the lambda is enhanced, one is a generic lambda expression, and one is a lambda that captures the expression. Here we have a brief introduction to these two new features. Lambda capture Expression
As mentioned earlier, a lambda expression can be copied or referenced to a variable that is captured within its scope. And sometimes we want to catch variables that are not in their scope, and most importantly we want to capture the right values. Therefore, an expression capture is introduced in c++14, which allows the captured variable to be initialized with any type of expression. Look at the following example:
With an expression capture, you can more flexibly handle variables
int x = 4 within the scope;
Auto y = [&r = x, x = x + 1] {R = 2; return x * x;} ();
At this point x is updated to 6,y
///literal initialization variable
auto z = [str = ' string ']{return str;} ();
At this point z is the const char* type, storing string strings
You can see that the capture expression expands the ability to capture lambda expressions, and sometimes you can initialize variables with Std::move. This is important to not replicate objects that can only be moved, such as STD::UNIQUE_PTR, because it does not support replication, and you cannot capture it in a value way. But by using a lambda capture expression, you can capture it by moving it:
Auto Mypi = std::make_unique<double> (3.1415);
Auto Circle_area = [PI = Std::move (mypi)] (double R) {return *PI * R * r;};
cout << Circle_area (1.0) << Endl; 3.1415
In fact, using an expression to initialize a catch variable is similar to the mechanism for declaring a variable with auto. generic lambda expression
Starting with c++14, a lambda expression supports generics: its arguments can use the ability to automatically infer types, without having to declare specific types. This is like a function template, which uses the Type auto inference feature, with only the type specified as auto, and the type inference rule as the function template. Here's a simple example:
Auto add = [] (auto x, auto y) {return x + y;};
int x = Add (2, 3); 5
Double y = Add (2.5, 3.5); 6.0
Function Object
A function object is a broad concept, because all objects with function behavior can be called function objects. This is a high-level abstraction, and we don't care what the object is, as long as it has functional behavior. The so-called function behavior refers to the use of () to invoke and pass parameters:
function (Arg1, arg2, ...); Function call
In this case, a lambda expression is also a function object. But what we're talking about here is a special kind of function object, which is actually an instance of a class, except that the class implements the function call character ():
Class X
{public
:
//define function Invoker
returntype operator () (params) const;
// ...
};
In this way, we can use the object of this class and use it as a function:
X F;
// ...
F (arg1, arg2); Equivalent to F.operator () (Arg1, arg2);
As an example, here we define a function object that prints an integer:
T needs to support output flow operator
Template <typename t>
class Print
{public
:
void Operator () (T elem) const
{
cout << elem << ';
}
};
int main ()
{
vector<int> v);
int init = 0;
Std::generate (V.begin (), V.end (), [&init] {return init++;});
Use For_each to output individual elements (fed a Print instance)
Std::for_each (V.begin (), V.end (), print<int>{});
Lambda expressions are used: Std::for_each (V.begin (), V.end (), [] (int x) {cout << x << ';});
Output: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 return
0;
}
You can see that an instance of print<int> can pass in Std::for_each, which behaves like a function, so we call this instance a function object. You may wonder why For_each can receive both lambda expressions and function objects, but the STL algorithm is a generic implementation and does not care what type of object it receives, but it must support function invocation operations:
For_each Similar implementation
namespace STD
{
template <typename iterator, TypeName
operation> Operation for _each (Iterator Act, iterator end, Operation op)
{while
(act!= end)
{
op (*act);
++act;
}
return op;
}
Generics provide a high-level abstraction that can be passed into the for_each algorithm, whether it be a lambda expression, a function object, or a function pointer.
In essence, a function object is a class object, which makes the function object have its own unique advantages over ordinary functions: The function object has a state : The function object is "intelligent function" relative to the ordinary function, which is like the smart pointer compared to the traditional pointer. Because function objects can have other methods and data members in addition to the function invocation method. So the function object has a state. This is not possible for ordinary functions, even if the different function objects instantiated by the same class are not of the same state. Also, function objects can be created at run time. each function object has its own type : For ordinary functions, the type is the same as long as the signature is consistent. This does not apply to function objects, however, because the type of the function object is the type of its class. In this way, the function object has its own type, which means that the function object can be used for template parameters, which greatly improves the generic programming. function objects are generally faster than normal functions : Because function objects are typically used for template parameters, templates typically do some optimizations at compile time.
Here we look at a function object that can have a state, which is used to generate sequential sequences:
Class Intsequence
{public
:
intsequence (int initval): value{initval} {}
int operator () () {return ++va Lue
private:
int value;
};
int main ()
{
vector<int> v);
Std::generate (V.begin (), V.end (), intsequence{0}); /* Lambda implements same effect
int init = 0;
Std::generate (V.begin (), V.end (), [&init] {return ++init;});
*/
Std::for_each (V.begin (), V.end (), [] (int x) {cout << x << ';});
Output: 1, 2, 3, 4, 5, 6, 7, 8, 9, ten return
0;
}
As you can see, a function object can have a private data member that increments every time it is invoked, resulting in a sequential sequence. Similarly, you can implement a similar effect with a lambda expression, but you must use a reference capture method. However, function objects can implement more complex functionality, while lambda expressions require complex reference captures. Consider a function object that can calculate the mean value:
Class Meanvalue
{public
:
meanvalue (): num{0}, sum{0} {}
void operator () (int e)
{
++num;
sum + = num;
}
Double value ()
{return static_cast<double> (sum)/static_cast<double> (num);}
Private:
int num;
int sum;
};
int main ()
{
vector<int> v{1, 3, 5, 7};
Meanvalue mv = Std::for_each (V.begin (), V.end (), meanvalue{});
cout << mv.value () << Endl; output:2.5 return
0;
}
You can see that the Meanvalue object holds two private variables, num and sum respectively, recording the number and sum, and finally the mean value can be computed by both. Lambda expressions can also be used to implement similar functions with reference capture, but it can be a bit cumbersome. This is also considered a unique advantage of the function object.
Header files <functional> predefined function objects, such as arithmetic function objects, Comparison function objects, logical operational function objects, and bitwise function objects, which we can use when needed. For example, less<> is the default comparison function object in the STL sort algorithm, so the default sort result is ascending, but if you want to arrange in descending order, you can use the Greater<> function object:
Vector<int> v{3, 4, 2, 9, 5};
Ascending sort
Std::sort (V.begin (), V.end ()); Output:2, 3, 4, 5, 9
/descending order
Std::sort (V.begin (), V.end (), std::greater<int>{});//Output:9, 5, 4, 3, 2
For more information about function objects, you can refer to this. function Adapters
In design mode, a function adapter is a special function object that combines a function object with another function object, or a particular value, or a particular function. Because of the combination of features, function adapters can meet specific requirements, header file <functional> defines several function adapters: Std::bind (OP, args ...) : Bind the parameters of the Function object op to a specific value args std::mem_fn (OP): Converts a member function of a class to a Function Object Std::not1 (OP), Std::not2 (OP): unary-accessor and two-dollar fetch-counter binder (Binder)
The binder Std::bind is the most commonly used function adapter that binds the parameters of a function object to a specific value. For parameters that are not bound, you can use std::p laceholers::_1, std::p laceholers::_2, and so on. Let's start with a simple example where you want a function object minus a fixed tree:
Auto Minus10 = Std::bind (std::minus<int>{}, std::p laceholders::_1);
cout << Minus10 (m) << Endl; Output 10
Sometimes you can rearrange the order of the parameters by using the binder, and the following binding switches the position of two parameters:
Reversing parameter Order
Auto Vminus = Std::bind (std::minus<int>{}, std::p laceholders::_2, std::p laceholders::_1);
cout << Vminus (a) << Endl; Output-10
The bindings can also be nested with each other to achieve a combination of function objects:
Defines a function object that receives a parameter and then multiplies the parameter by 10 times 2 to
auto Plus10times2 = Std::bind (std::multiplies<int>{},
std::bind (std: :p lus<int>{}, std::p laceholders::_1, 2);
cout << Plus10times2 (4) << Endl; Output:
pow3//define 3-second-order function object
Auto = Std::bind (std::multiplies<int>{},
Std::bind (std::multiplies< int>{}, std::p laceholders::_1, std::p laceholders::_1),
std::p laceholders::_1);
cout << pow3 (3) << Endl; Output: 27
Using a combination of different function objects, a function adapter can call global functions, and the following example is case-insensitive to determine whether a string contains a specific substring:
//uppercase conversion function char Mytoupper (char c) {if (C >= ' a ' && c <= ' z ') return STA
Tic_cast<char> (C-' a ' + ' a ');
return C;
int main () {string s{"internationalization"};
String sub{"Nation"}; Auto pos = Std::search (S.begin (), S.end (), Sub.begin (), Sub.end (), Std::bind (Std::equal_to<char >{}, Std::bind (Mytoupper, std::p laceholders::_1), Std::bind (my
Toupper, std::p laceholders::_2));
if (POS!= s.end ()) {cout << sub << "is part of" << s << endl; //output: Nation is part of internationalization R