C++11 – wrappers and lambda expressions

Table of Contents

1. Background

2.lambda

1. Meet lambda

2.lambda expression syntax

3.lambda capture list description

3. Function objects and lambda expressions

4. Packager

1.function wrapper

2. Member functions of wrapper classes

5.bind

1.Adjust parameter position

2. Reduce function parameters


一.Background

In C++98, if you want to sort the elements in a data collection, you can use the std::sort method.

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

void Print(vector<int> & amp; arr)
{
for (auto e : arr)
{
cout << e << " ";
}
cout << endl;
}

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

//descending order
sort(arr.begin(), arr.end(),greater<int>());
Print(arr);
//Ascending order
sort(arr.begin(), arr.end());
Print(arr);

return 0;
}

If the elements to be sorted are of a custom type, the user needs to define the comparison rules for sorting:

struct Goods
{
string _name; //name
double _price; //price
int _evaluate; //Evaluation
Goods(const char* str, double price, int evaluate)
:_name(str)
, _price(price)
, _evaluate(evaluate)
{}
};
struct ComparePriceLess
{
bool operator()(const Goods & amp; gl, const Goods & amp; gr)
{
return gl._price < gr._price;
}
};
struct ComparePriceGreater
{
bool operator()(const Goods & amp; gl, const Goods & amp; gr)
{
return gl._price > gr._price;
}
};

void Print(vector<Goods> & amp; v)
{
for (auto e : v)
{
cout << e._name << ":" << e._price << ":" << e._evaluate << endl;
}
cout << endl;
}
int main()
{
vector<Goods> v = { { "apple", 2.1, 5 }, { "banana", 3, 4 }, { "orange", 2.2,3 }, { "pineapple", 1.5, 4 } };
sort(v.begin(), v.end(), ComparePriceLess());
Print(v);
sort(v.begin(), v.end(), ComparePriceGreater());
Print(v);
return 0;
}

With the development of C++ syntax,people began to feel that the above writing method was too complicated. Every time in order to implement an algorithm,
You have to rewrite a class. If the logic of each comparison is different, you have to implement multiple classes, especially the naming of the same class.
These have brought great inconvenience to programmers.
Thus, Lambda expressions appeared in C++11 syntax.

二.lambda

1. Meet lambda

Modify the above code:

struct Goods
{
string _name; //name
double _price; //price
int _evaluate; //Evaluation
Goods(const char* str, double price, int evaluate)
:_name(str)
, _price(price)
, _evaluate(evaluate)
{}
};

void Print(vector<Goods> & amp; v)
{
for (auto e : v)
{
cout << e._name << ":" << e._price << ":" << e._evaluate << endl;
}
cout << endl;
}
int main()
{
vector<Goods> v = { { "apple", 2.1, 5 }, { "banana", 3, 4 }, { "orange", 2.2,3 }, { "pineapple", 1.5, 4 } };
sort(v.begin(), v.end(), [](const Goods g1, const Goods g2)->bool {return g1._price < g2._price; });
Print(v);
sort(v.begin(), v.end(), [](const Goods g1, const Goods g2)->bool {return g1._price > g2._price; });
Print(v);
return 0;
}

The above code is solved using the lambda expression in C++11. It can be seen that the lambda expression is actually an anonymous function.
number.

2.lambda expression syntax

Lambda expression writing format: [capture-list] (parameters) mutable -> return-type { statement }

[capture-list] :

Capture list, this list always appears at the beginning of the lambda function. The compiler uses [] to determine whether the following code is a lambda function. The capture list can capture variables in the context for use by the lambda function.

int main()
{
int a = 100;
int b = 200;
int sum = 0;
auto lam = [a, b]()->int {return a + b; };
sum = lam();
cout << "a + b = "<<sum << endl;
return 0;
}

(parameters):

parameter list. Consistent with the parameter list of an ordinary function, if parameter passing is not required, it can be omitted together with ().

int main()
{
int a = 100;
int b = 200;
int sum = 0;
auto lam = [](int a,int b)->int {return a + b; };
sum = lam(a,b);
cout << "a + b = "<<sum << endl;
return 0;
}

mutable:

By default, a lambda function is always a const function, and mutable can cancel its constness. When using this modifier, the parameter list cannot be omitted (even if the parameter is empty).

By default, captured variables have const attributes:

int main()
{
int a = 100;
int b = 200;
int sum = 0;
auto lam = [a,b]()
{
a = b = 50;
cout << "a:" << a << endl;
cout << "b:" << b << endl;
};
lam();
cout << "a:" << a << endl;
cout << "b:" << b << endl;
\t
return 0;
}

Add mutable keyword:

Notice:

  1. This causes the variables captured by lambda to lose the const attribute, but does not say that the scope of variable modification is limited to the interior of the lambda expression.
  2. The external data in the lambda is still the external value.

->returntype:

  1. Return value type.
  2. Use the tracking return type form to declare the return value type of the function. This part can be omitted if there is no return value.
  3. If the return value type is clear, it can also be omitted, and the compiler will deduce the return type.

{statement}:

The function body, in which all captured variables can be used in addition to its parameters.

As can be seen from the above examples, lambda expressions can actually be understood as unnamed functions, which cannot be directly called.
If you want to call it directly, you can use auto to assign it to a variable.

3.lambda capture list description

The capture list describes which data in the context can be used by the lambda, and whether it is passed by value or by reference.

[var]: Indicates that the value transfer method captures the variable var.

int main()
{
int a = 100;
int b = 200;
int sum = 0;
auto lam1 = [a]()
{
cout << a << endl;
};
lam1();

return 0;
}

[=]: Indicates that the value passing method captures all variables in the parent scope (including this).

int main()
{
int a = 100;
int b = 200;
int sum = 0;
auto lam1 = [=]()
{
cout << a << endl;
cout << b << endl;
};
lam1();
return 0;
}

[ & var]: Indicates that the capture variable var is passed by reference.

int main()
{
int a = 100;
int b = 200;
int sum = 0;
auto lam1 = [ & amp;a]()
{
a = 500;
};
lam1();
cout << "a:" << a;
return 0;
}

[ & amp;]: Indicates that reference transfer captures all variables in the parent scope (including this).

int main()
{
int a = 100;
int b = 200;
int sum = 0;
auto lam1 = [ & amp;]()
{
a = b = 50;
};
lam1();
cout << "a:" << a << endl;
cout << "b:" << b << endl;
\t
return 0;
}

[this]: Indicates that the value passing method captures the current this pointer.

class Test
{
public:
Test(int a)
:_a(a)
{
}

void operator()()
{
//After capturing this pointer, you can directly access class members without using this class to specify members.
auto Prin_a = [this]() {cout <<"class Teat::_a:" << _a << endl; };
Prin_a();
}

private:
int _a;
\t
};

int main()
{
Test T(100);
T();
return 0;
}

Note: After capturing the this pointer, you can directly access the class members without using the this class to specify members.

3. Function objects and lambda expressions

Function object, also known as functor, is an object that can be used like a function. It is an object that overloads the operator() operator in a class.
class object.

class Rate
{
public:
Rate(double rate) : _rate(rate)
{}
double operator()(double money, int year)
{
return money * _rate * year;
}
private:
double_rate;
};
int main()
{
// function object
double rate = 0.49;
Rate r1(rate);
r1(10000, 2);
// lamber
auto r2 = [=](double monty, int year)->double {return monty * rate * year;
};
r2(10000, 2);
return 0;
}

In terms of usage, function objects are exactly the same as lambda expressions.
The function object uses rate as its member variable. Just give the initial value when defining the object. The lambda expression can be used through the capture list.
to capture the variable directly.

UUID is the abbreviation of Universally Unique Identifier. It is a standard for software construction and a part of the Open Software Foundation in the field of distributed computing environments. Its purpose is to enable all elements in the distributed system to have unique identification information, without the need for the central control terminal to specify the identification information.

In fact, the underlying compiler processes lambda expressions exactly as function objects, that is: such as
If a lambda expression is defined, the compiler will automatically generate a class in which operator() is overloaded.

Four. Wrapper

1.function wrapper

Function wrappers are also called adapters. Function in C++ is essentially a class template and a wrapper. So let’s take a look, why do we need function?

ret = func(x);

What might the func above be? So func might be the function name? Function pointer? Function object (functor object)? Also a possibility
Is it a lamber expression object? So these are all callable types! Such a rich type may lead to inefficiency of the template! why? Let’s continue reading:

template<class F, class T>
T useF(F f, T x)
{
static int count = 0;
cout << "count:" << + + count << endl;
cout << "count:" << & amp;count << endl;
return f(x);
}
double f(double i)
{
return i / 2;
}
struct Functor
{
double operator()(double d)
{
return d/3;
}
};
int main()
{
\t// Function name
cout << useF(f, 11.11) << endl;
// function object
cout << useF(Functor(), 11.11) << endl;
// lamber expression
cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
return 0;
}

Through the above program verification, we will find that the useF function template is instantiated three times. The reason for this is that even though the functions of these three callable objects are exactly the same, because of the three callable objects, one It is a function, one is a class object, and the other is a lambda expression. They are three different types, so when the template is instantiated, three useF functions will be instantiated.

Wrapper can solve the above problem very well:

//The class template prototype is as follows
template <class T> function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
Template parameter description:
Ret: the return type of the called function
Args...: formal parameters of the called function

Wrapping the above callable:

#include<functional>
template<class F, class T>
T useF(F f, T x)
{
static int count = 0;
cout << "count:" << + + count << endl;
cout << "count:" << & amp;count << endl;
return f(x);
}
double f(double i)
{
return i / 2;
}
struct Functor
{
double operator()(double d)
{
return d/3;
}
};

int main()
{
function<double(double)> func1 = f;
function<double(double)> func2 = Functor();
function<double(double)> func3 = [](double d) {return d / 1; };
\t
useF<function<double(double)>, double>(func1, 11.11);
useF<function<double(double)>, double>(func2, 11.11);
useF<function<double(double)>, double>(func3, 11.11);

return 0;
}

2. Member functions of the packaging class

For class member functions, there is almost no difference between static member functions and ordinary functions. For ordinary member functions of a class, we need to pass a class object or a pointer to a class object when packaging.

class Add
{
public:
Add(int a)
:_a(a)
{}
int add(int b)
{
return _a + b;
}
static int s_add(int a, int b)
{
return a + b;
}
private:
int _a;
};

int main()
{
//Wrap non-static member functions and build them with objects
function<int(Add, int)> fun1 = & amp;Add::add;
cout << fun1(Add(10), 30) << endl;
\t
//Wrap non-static member functions and build them with object pointers
Add add(10);
function<int(Add*, int)> fun3 = & amp;Add::add;
cout << fun3( & amp;add, 30) << endl;

//Wrap static member function
function<int(int,int)> fun2 = Add::s_add;
cout << fun2(10, 30) << endl;

return 0;
}

Note: If a non-static member function of a wrapper class is wrapped using a pointer to the class object, you cannot use an anonymous object to pass parameters when calling, because only lvalues can take addresses.

五.bind

The std::bind function is defined in the header file . is a function template. It is like a function wrapper (adapter), accepting a callable object and generating a new callable object. to “adapt” the parameter list of the original object. Generally speaking, we can use it to convert a function fn that originally received N parameters, and by binding some parameters, return a new function that receives M (M can be greater than N, but this makes no sense) parameters. At the same time, the std::bind function can also be used to adjust the parameter order and other operations.

//The prototype is as follows:
template <class Fn, class... Args>
/* unspecified */ bind (Fn & amp; & amp; fn, Args & amp; & amp;... args);
// with return type (2)
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn & amp; & amp; fn, Args & amp; & amp;... args);

The bind function can be regarded as a general function adapter, which accepts a callable object and generates a new callable object.
The object is used to “adapt” the parameter list of the original object.
The general form of calling bind:

auto newCallable = bind(callable,arg_list);

Among them, newCallable itself is a callable object, which can also be received using a function object. arg_list is a comma-separated parameter list corresponding to the parameters of the given callable. When we call newCallable, newCallable will call callable and pass it the parameters in arg_list.

Parameters in arg_list may contain names of the form _n, where n is an integer. These parameters are “placeholders”, representing
newCallable’s parameters, they occupy the “position” of the parameters passed to newCallable. The value n represents the generated callable pair
The position of the parameters in the image: _1 is the first parameter of newCallable, _2 is the second parameter, and so on.

1.Adjust parameter position

void Plus(int a, int b)
{
cout << a << " " << b << endl;
}

int main()
{
//Indicates that the binding function plus parameters are specified by the first and second parameters of calling func1.
function<void(int, int)> func1 = std::bind(Plus, placeholders::_1, placeholders::_2);
func1(1, 2);
\t
//The position of the parameters represented by _2,_1
function<void(int, int)> func2 = std::bind(Plus, placeholders::_2, placeholders::_1);
func2(1, 2);

return 0;
}

2. Reduce function parameters

By specifying a positional parameter, one less parameter is passed when calling.

 class Add
{
public:
Add(int c)
:_c(c)
{}
int add(int a,int b)
{
return (a + b) * _c;
}
private:
int _c;
};

int main()
{
//Wrap non-static member functions and build them with objects
function<int(Add, int,int)> fun1 = & amp;Add::add;
cout << fun1(Add(10), 3,2) << endl;

//Wrap non-static member functions and build them with objects
//Specify a parameter Add(10) as a parameter, and the remaining parameters are passed as normal
function<int(int,int)> fun2 = bind( & amp;Add::add, Add(10), placeholders::_1, placeholders::_2);
cout << fun2(3,2) << endl;
\t
return 0;
}

The knowledge points of the article match the official knowledge files, and you can further learn related knowledge. Algorithm skill tree Home page Overview 56812 people are learning the system