Variable parameter template+lambda+function wrapper (adapter)+bind

Table of Contents

variadic template

introduce

introduce

How to expand parameter pack

recursion

comma expression

overall use

emplace

introduce

?edit

use

Simulation implementation

code

Example

lambda

introduce

introduce

Format

use

Passing on parameters

capture

principle

Example

function wrapper (adapter)

introduce

introduce

Format

use

bind

introduce

std::placeholders

use

Modify parameter position

Pass fixed values for parameters


Variable parameter template

Introduction

  • Do you still remember printf in C language? You can pass in any number of variables for printing, which is very convenient.
  • And he used the variable parameter list in c, but it is different from the principle of the variable parameter template we are going to introduce now.
  • All we need to know is that variable parameter templates are easy to use and flexible, and they make generic programming more complete.
  • However, the syntax in C++ is very abstract, and learning the basics is enough.

Introduction

template <class ...Args>
void ShowList(Args... args)
{}
  • Parameters with… are called “Parameter Pack” and contain 0-n template parameters.
  • Args is the template parameter package passed in, and args is the function parameter parameter package
  • But we can’t do it like printf. printf gets its parameters through the va_list object and macros.
  • It is also impossible to use args[i] to get parameters
  • Here, the parameters can only be obtained by expanding the parameter package

Method to expand parameter package

recursive

// Recursive termination function
template <class T>
void ShowList(const T & amp; t) //When there is only one parameter
{
     cout << t << endl;
}

//Expand function
template <class T, class ...Args>
void ShowList(T value, Args... args) //When there are multiple parameters
{
     cout << value <<" ";
     ShowList(args...);
}

Function overloading is used here, when calling the showlist function:

  • If multiple parameters are passed in, it will match the second function and continuously call itself to get its current first template parameter. In this way, the parameters passed in the parameter package will continue to be reduced by one. , until there is only one template parameter left, call the first function
  • If only one parameter is passed in, it will directly match the first function (the best matching one will be found)
  • If you want to support no-parameter calls, you can change the first function to no parameters (because args can be 0 parameters)

comma expression

It does not require recursion to obtain, but uses the characteristics of multiple syntaxes to directly expand the parameter package during the array construction process.

template <class T>
void PrintArg(T t)
{
     cout << t << " ";
}

//Expand function
template <class ...Args>
void ShowList(Args... args)
{
     int arr[] = { (PrintArg(args), 0)... };
     cout << endl;
}
  • Here, an array is initialized using initialization list, and the elements inside are comma expression + parameter pack
  • So, actually the elements inside it after expansion are — ((printarg(arg1),0), (printarg(arg2),0),(printarg(arg3),0)…)
  • Therefore, during the construction process, the comma expressions will be executed one by one.
  • First, execute the expression before the comma, that is, call the function and print the parameters.
  • Then use the int value after the comma-that is, the 0 here, as the return value of the comma expression to initialize the array.
  • In this way, an array is constructed and all the parameters in the parameter package are printed out.

Overall use

However, the above two methods can only take out the parameters one by one, which is of little use. The most they can do is print them out.

However, if we can use it as a whole, it can be used to pass parameters!

class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
,_month(month)
,_day(day)
{
cout << "Date construction" << endl;
}

Date(const Date & d)
:_year(d._year)
, _month(d._month)
, _day(d._day)
{
cout << "Date copy construction" << endl;
}

private:
int _year;
int _month;
int _day;
};


template <class ...Args>
Date* Create(Args... args)
{
Date* ret = new Date(args...);

return ret;
}
  • In the create function, package the multiple parameters passed in into a parameter package, which is directly used to construct the date object
  • The parameters in the parameter package can be used to match the parameters of the function. If they match, the function can be called directly.
  • If it does not match, an error will be reported
  • And this feature is the basis of emplace
emplace
Introduction
< /h6>

  • These two functions of the emplace series are set in multiple stl containers, and he is using the variable parameter template introduced above to insert any number of elements.
use

Here we use the node of list as pair to demonstrate the role of variable parameter list

void test1()
{
    std::list<std::pair<int, bit::mystring>> l;
    l.push_back(make_pair(1, "22"));
    l.push_back({1, "1434"});
    cout << endl;
    l.emplace_back(1, "111");
}
  • Previously, we could use make_pair/multi-parameter implicit type conversion to pass in the pair object
  • The emplace series can directly pass parameters

  • Because the parameters of push_back can only be element types, which is the pair type here
  • Therefore, any non-pair type parameters passed in must construct a temporary pair object before entering the function.
  • so —
  • The first half of the result here first constructs a string and then constructs a pair
  • Then inside push_back, use pair to call the constructor of the node
  • In the constructor, the copy constructor of the first member and second member of the pair object is called respectively to copy a pair object to the node.
  • But because of the existence of move copy, the process of copying to the node is completed by move operation.
  • The lower half of the result is received using a variable parameter template. Just like the overall use described above, the parameter package is always passed to the copy constructor that calls pair.
  • Then, the number and type of parameters passed in can just match the constructor of pair.
  • Therefore, we directly called the string constructor and initialized the second member of the pair with the passed in string.
  • This bypasses the copy operation and directly constructs the lowest-level call

In fact, if you use a custom type here, there will be little difference in efficiency, because with the existence of mobile operations, it can directly exchange resources.

However, if it involves classes that occupy a large amount of memory but are all built-in types, using the emplace series of functions will greatly improve efficiency (similar to the date class above)

Simulation Implementation

We can also add the emplace function to the list we implement

code
 template <class Data>
        ListNode(Data & amp; & amp;val) //Previously implemented move structure
            : _ppre(nullptr), _pnext(nullptr), _val(forward<Data>(val)){};

        template <class...Args>
        ListNode(Args... args)
            : _ppre(nullptr), _pnext(nullptr), _val(args...)
        //In this way, the variable parameter template is passed directly into the constructor of val. If it matches, it is constructed directly.
        {
        }


        template <class...Args>
        void emplace_back (Args & amp; & amp;... args){
            insert(end(),args...); //Pass parameter package to insert
        }


        template <class...Args>
        iterator insert(iterator pos, Args & amp; & amp;... args)
        {
            PNode cur = pos._pNode;
            PNode pre = cur->_ppre;
            PNode newnode = new Node(args...); //The parameter package is passed to the node constructor
 
            newnode->_pnext = cur;
            pre->_pnext = newnode;

            cur->_ppre = newnode;
            newnode->_ppre = pre;

            _size + + ;

            return newnode;
        }
example

Here we have two more lines than Curry’s in the upper half.

The reason is that when we construct the head node here, we also need to construct string, but it is default construction; while the library uses a memory pool

lambda

Introduction

We have learned many ways to call functions before, such as: function pointers, functors (actually class overloading ())

  • However, this means that every time we use a function, we have to write the function body
  • What if we need to use functions with similar but different functions multiple times? Define many copies?
  • This will involve the issue of function naming
  • It’s really hard to name something with similar functions. If you don’t name it well, the readability will be very poor.

The lambda expression here can also be used like a function, which can solve this problem

Introduction

is an anonymous function or closure expression introduced in C++11 that allows you to define functions inline in your code without explicitly naming the function

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 [] todetermine whether the following code is a lambda function
  • Capture lists can capture variables in the context for use by lambda functions
  • By default, what is captured is a copy of the variable, and it is of const type.

(parameters)

  • The parameter list is consistent with the use of parameter lists of ordinary functions.
  • If parameter passing is not required, you can omit it together with ()

mutable

  • As mentioned before, by default, the variable captured by the lambda function is of const type, and mutable can cancel its constness (but note that the constness is no longer here, but it is still Copy of variable)
  • When using this modifier, the parameter list cannot be omitted (even if the parameter is empty)

->returntype

  • Return type,Declare the return type of a function using the trace return type form
  • There is no return value/it can be omitted when the return value is clear (because the compiler can infer the return type, just like auto)

{statement}

  • function body
  • In the body of this function, you can use parameters/capture functions of the object/global domain, and others cannot be used.

use

Pass parameters

You can pass by value or by reference.

auto goodsPriceLess = [](const Goods & amp; x, const Goods & amp; y){return x._price < y._price; };

capture

By default, a copy of const type is captured.

  • You can use mutable to change the const type, but it is still a copy
  • You can also directly pass a reference (& variable name), so that x can be modified and the modification can be obtained externally:
  • int main() {
    int x = 0;
    auto a = [ & amp;x]() { + + x; };
    a();
    cout << x << endl;
    return 0;
    }
  • If you want to capture all variables in the scope, you can use [ = ] / [ & amp; ]

  • [ = ] — Capture all variables in this scope by value

  • [ & amp; ] — capture all variables in the scope by reference

  • Note! In lambda expressions outside block scope, the capture list must be empty

Principle

  • In fact, our lambda expressions cannot be assigned to each other, even if they are exactly the same expressions
  • The reason is because its bottom layer is functor, each expression is a different class object, and the () operator is overloaded in the class
  • Therefore, each lambda expression is of a different type and cannot be assigned a value.
  • However, itsupports copying
  • Lambda expressions are all anonymous class objects, and their names and types are automatically generated by the compiler.
  • The name is lambda + uuid (uuid is a string generated using a certain algorithm with a very small probability of repetition)
  • The type is an anonymous function object type
  • Using lambda expression is to call the operator() function in the class
  • If the type of a function pointer is the same as the lambda expression type, the lambda expression can be assigned to the function pointer (possibly because the operator() function has a similar signature (parameter list and return type) to the function pointer)

Example

sort(v.begin(), v.end(), [](const Goods & amp; x, const Goods & amp; y) {return x._price > y._price;});</pre >
 <p>It can be passed to an algorithm, such as sorting in sort, so that you don’t have to write a special functor yourself.</p>
</blockquote>
<h2 id="function wrapper (adapter)">function wrapper (adapter) </h2>
<blockquote>
 <h3>Introduction</h3>
 <p>Now, we already know that there are three ways to call functions - function pointers, functors, and lambda expressions</p>
 <p>Then when using a class template to pass parameters, if three different methods of functions are used, three copies will be instantiated, but in fact we do not need so many copies.</p>
 <p></p>
 <p>And what if we want to store these callable types in a container? How should we write the type when instantiating the container? We can write function pointer and functor types, but what about lambda?</p>
 <p></p>
 <p>Therefore, the function wrapper can solve this problem, it can adapt to all callable types</p>
</blockquote>
<blockquote>
 <h3>Introduction</h3>
 <p>Header file:<functional></p>
 <p></p>
 <p>Instead of specifying the exact type at compile time, a callable of a different type is assigned to it at runtime</p>
 <p><img alt="" height="287" src="//i2.wp.com/img-blog.csdnimg.cn/badbb8e8409f4c038078acbafbd2ab4b.png" width="1054"></p>
 <p></p>
 <p>We can use the object defined by function to receive different types of callable objects, as long as the return value and parameter list are the same:</p>
 <p><img alt="" height="362" src="//i2.wp.com/img-blog.csdnimg.cn/399dfe3bed1346c9bb7f9dde7f29ce56.png" width="989"></p>
 <h4>Format</h4>
 <p>functioni<return value (parameter list)></p>
</blockquote>
<h3>Use </h3>
<blockquote>
 <pre>int func(int x, int y)
{
return x - y;
}
struct Function
{
int operator()(int x, int y)
{
return x - y;
}
};
void test2() {
function<int(int, int)> sub1 = [](int x, int y) {return x - y; };
function<int(int, int)> sub2 = func;
function<int(int, int)> sub3 = Function();
cout << sub1(2, 1) << endl;
cout << sub2(3, 1) << endl;
cout << sub3(4, 1) << endl;
}

Use them in the same way as before, except that the receiver is a function type

Store in container:

void test3() {
vector< function<int(int, int)>> f;
f.push_back(func);
f.push_back(Function());
f.push_back([](int x, int y) {return x - y; });
cout << f[0](1, 2) << endl;
}

After we save it, we can get the callable object through the subscript, and then use it to execute the function function

In addition, it can be used to specify a command to execute a certain function

For example: when we already have a suffix expression, we can directly perform calculation, but there are many situations. The more operators, the more situations there are.

  • If you use judgment directly, the efficiency is too low
  • We can use function and map to directly get the operation method of the incoming operator, and then pass the parameters.
  • int evalRPN(vector<string> & amp; tokens) {
      stack<int> st;
      map<string, function<int(int, int)>> opFuncMap =
     {
     { " + ", [](int i, int j){return i + j; } },
     { "-", [](int i, int j){return i - j; } },
     { "*", [](int i, int j){return i * j; } },
     { "/", [](int i, int j){return i / j; } }
     };
    
      for(auto & str : tokens)
     {
             if(opFuncMap.find(str) != opFuncMap.end())
             {
                 int right = st.top();
                 st.pop();
                 int left = st.top();
                 st.pop();
                 st.push(opFuncMap[str](left, right));
         }
             else
             {
                 // 1. atoi itoa
                 // 2. sprintf scanf
                 // 3. stoi to_string C++11
                 st.push(stoi(str));
             }
         }
         return st.top();
    }

bind

Introduction

Bindings used to create function objects (which can be of the three mentioned previously), allowing you to
Pre-bind some parameters or change the order of parameters

It provides a convenient way to partially apply functions, change the order of arguments, or create function objects
(That is, you can freely control the parameters)

std::placeholders

In addition, the use of bind also needs to be coordinated with a set of placeholder objects defined in the scope of std::placeholders, which is used to specify the parameter locations when binding functions< /strong>

Use

Modify parameter position

In other words, there will be two levels of calls here

  • The first level — the first parameter in the new calling object is passed to the position of the _1 object in bind
  • Second level – then pass the parameter to the source object function parameter at the corresponding position (that is, a here)
  • When used here, the positions of the two parameters are not exchanged, but if we exchange the positions of _1 and _2 in bind, the modification of the parameter positions can be completed.
Pass fixed value for parameter

In fact, we don’t use much of the first case, but more of freely passing different fixed values to the formal parameters

double PPlus(int a, double rate, int b) { //Here we set rate as a fixed parameter
return (a + b) * 1.0 * rate;
}

void test4() {
function<double(int, int)> new_pplus = bind(PPlus, std::placeholders::_1, 2.3, std::placeholders::_2);
new_pplus(1, 1);
}

Of course, we can use bind to generate variables with different fixed parameters without having to modify the function code ourselves.

When we want to implement this function for the member function of the class:

class SubType
{
public:
static int sub(int a, int b)
{
return a - b;
}

int ssub(int a, int b, int rate)
{
return (a - b) * rate;
}
};

Since thebind function receives a function object + a series of parameters, you need to rely on the class name to get the function pointers of these two functions.

  • Since static member functions of a class can be called directly through the class field, the function pointer is subtype::sub
  • Ordinary member functions must get the address, that is & amp;subtype::ssub
  • But in actual use, it can be preceded by + & amp;, so that there is no need to distinguish it.
  • The most important point!!! The member function of the class will implicitly pass in the this pointer, and this pointer is not passed in manually by us, but through the callable object , call the function, and then use the pointer of the object as this pointer
syntaxbug.com © 2021 All Rights Reserved.