C++ – wrapper – bind() function

Wrapper

There may be various callable types in C++, such as function pointers, functors, lambdas, etc. With so many callable types, we will be confused when using them. Can we unify them? How about taking control?

Function wrapper, also called adapter.

Let’s look at an example first:

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 name (function name is equivalent to function pointer)
cout << useF(f, 11.11) << endl;
// Function object (functor)
cout << useF(Functor(), 11.11) << endl;
// lamber expression
cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
return 0;
}

There are three calling methods mentioned above, namely: function pointer, functor and lambda expression.

Each different type of function will instantiate three functions in the same function template, because the variable f receives a callable object, and the incoming of three different callable objects may be Different functions are instantiated, so the addresses output by the static count static variable above are different:

Through the above program verification, we will find that the useF function template is instantiated three times.

In the main function, all three methods can be called, that is to say:

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 cause the template to be inefficient!

So, is there any way to unify the above three methods?

The answer is yes.

Just usewrappers.

Wrapper syntax

std::function In the header file <functional>, in fact, the function wrapper is essentially a class, which stores various functions. There are the above simulations. Functions, lambdas, and function pointers can all be stored in it.

The function class template prototype is as follows:

//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: return type of the called function
  • Args…: formal parameters of the called function

To use a wrapper class, we store the above callable objects in a container (function object), and wrap these callable objects through this wrapper class, so that when we call these callable objects, Can be called in a unified way.

For example, in the above we wrote three functions, the return values are all double types, and the number of parameters of the function is only one:

double f(double i)
{
return i / 2;
}

struct Functor
{
double operator()(double d)
{
return d/3;
}
};

int main()
{
    // Function name (function name is equivalent to function pointer)
cout << useF(f, 11.11) << endl;
// Function object (functor)
cout << useF(Functor(), 11.11) << endl;
// lamber expression
cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;

    return 0;
}

Then the wrapper class declaration should be written like this:

// wrapper function pointer
function<double(double)> f1 = f;

// wrap lambda expression
function<double(double)> f2 = [](double d)->double { return d / 4; };

// Wrap the functor (pass in the anonymous object of the functor class)
function<double(double)> f3 = Functor();

Then, these three functions are packaged at this time.

When we do not use wrapper classes, it is impossible for us to store these callable objects into a container, such as a vector.

But now these three functions have been packaged into an object by the function wrapper class. At this time, we can store each wrapper class object of type function in the vector ( Such as f1 f2 f3 above).

As shown below, we take the vector container and store the above three objects into it:

vector<function<double(double)> v = {f1, f2, f3};

What problem does the packaging class essentially solve?

In the past, we wanted to store the callable object of the functor into a container. Then, the type of the container could only be the type of this functor, and then we could only store this type of functor. Thinking about function pointers, they cannot be stored together.

Furthermore, we can’t control the type of callable objects like lambda expressions at all. They are named in the form of lambda + uuid. Basically, the type of callable objects of every lambda expression is different. of. Then lambda expressions with the same return value type and the same parameter list cannot be stored together.

Like the above, after packaging through the function packaging class, these callable objects are packaged into a function object of this type function , then only need to be stored in the vector Each callable object is wrapped into a function object, and different types of callable objects can be stored together:

vector<function<double(double)> v = {f1, f2, f3};

double n = 1.1;
for(auto f : v)
{
    cout << f(n + + ) << endl;
}

Output:

Moreover, we don’t need to write in the above way. Instead of defining more function objects such as f1 and f2, we can directly pass in the callable objects of the function:

vector<function<double(double)> v = {
                                f
                                , [](double d)->double { return d / 4; }
                                ,Functor()
                                };

The above vector is just an example, which means that a function can be packaged into a function object using a packaging class, and the callable object of the function can be stored in a container.

After using the wrapping class to wrap two or three functions, the three callable objects are all wrapped into objects of the same type. Then we will pass in the three wrapped objects, because these three The types of the objects are the same, so an instantiated function is used, which means that these three functions use an instantiation function of useF:

//Function name (function pointer)
std::function<int(int, int)> func1 = f;
cout << func1(1, 2) << endl;

// function object
std::function<int(int, int)> func2 = Functor();
cout << func2(1, 2) << endl;

// lamber expression
std::function<int(int, int)> func3 = [](const int a, const int b){return a + b; };
cout << func3(1, 2) << endl;

Output:

As mentioned above, we found that the addresses of count are all the same, and the modification of count can be extended among the three functions.

It is proved that the types of the three callable objects are the same.

For example, the following reference scenario:

150. Evaluating reverse Polish expression - LeetCode

You are given a string array tokens that represents an arithmetic expression in reverse Polish notation.

Please evaluate this expression. Returns an integer representing the value of the expression.

Note:

  • Valid operators are ' + ', '-', '*' and '/ '.
  • Each operand (operand) can be an integer or another expression.
  • Division between two integers is always truncated toward zero .
  • There is no division by zero operation in the expression.
  • The input is an arithmetic expression expressed in reverse Polish notation.
  • The answer and all intermediate calculation results can be represented as 32-bit integers.

If the above wrapper is not used, we use if - else or switch to manually determine which operator is currently, and then call the algorithm corresponding to this operator:

It can be found that it is very troublesome.

We can use key-value pairs to match the above operator with the algorithm corresponding to this operator. However, an algorithm is a line of code. If we want to store the code in a container, we need to write it as a function as above, and then wrap the callable object of this function into a function object. Map can store this object. .

Then, it can be implemented in map. An operator corresponds to an algorithm of a function (a function object wrapped by a callable object):

As above,we use the lambda expression, which seems to be the simplest, and do not use functors, which are more cumbersome than lambda. But because the above codes are relatively small, if the amount of code is relatively large, writing functors separately will also improve readability.

bind

std::bind

is a function template that, like a function adapter (wrapper), accepts a callable object and generates a new callable object to "adapt" the parameter list of the original object.

Generally speaking, we can use the bind function to pass in a certain function FN. This FN function has N parameters. By binding some parameters, it returns a function that accepts M (M > N is okay, but No need) new function with parameters (this new function is returned as a function object), we can use the bind function to adjust the order of parameters in this new function:

int func(int a, int b)
{
    return a - b;
}

Use the bind function to exchange the parameter positions of a and b in the above func function. In the function, it is equivalent to a becoming b, and b becoming a.

 function<int(int, int)> rSub2 = bind(func, placeholders::_2, placeholders::_1);
cout << rSub2(10, 5) << endl;

Output:

-5

We see that if 10 and 5 are passed in, the return result of the func() function is 5, but the return result of the rSub object modified by bind is -5.

Moreover, the bind() function only creates a new function based on the original func() function. Modifying this new function will not affect the original func() function:

 function<int(int, int)> rSub1 = bind(func, placeholders::_1, placeholders::_2);
cout << func(10, 5) << endl;

Output:

5

bind syntax

We see that placeholders are used above, which is actually a namespace. This namespace declares an unspecified number of objects: _1, _2, _3, ..., which is used to specify placeholders when calling the bind function:

Let’s take the above example to illustrate the syntax:

_1 represents the first parameter of the func function, _2 represents the second parameter of func... and so on.

It is equivalent to placeholders::_1 only accepting the first parameter passed in when calling the function, as shown in 10 above; placeholders::_2 only accepting the second parameter passed in when calling the function, as shown above 5·······

If we use a lot of function parameters, or if we are not used to writing functions written by others in a numerical definition order, we can choose to change the parameter positions to conform to our calling habits.

How to write the default parameters of bind() function

If we don’t want to pass a parameter, we can use the bind function to give a default value.

Such as the following example:

void func(int a, int b, double rate)
{
    return (a - b) * rate;
}

int main()
{
    // Do not write the type of the default parameter in the template parameter in the type of the function object.
    function<double(int, int) > plus1 = bind(func, placeholders::_1, placeholders::_2, 4.0);
    function<double(int, int) > plus2 = bind(func, placeholders::_1, placeholders::_2, 4.2);
    function<double(int, int) > plus3 = bind(func, placeholders::_1, placeholders::_2, 4.4);

    // There is no need to write the default parameter type when calling
    cout << plus1(10, 5) << endl;
    cout << plus2(10, 5) << endl;
    cout << plus3(10, 5) << endl;

    return 0;
}

Output:

20
twenty one
22

The above 4.0 4.2 4.4 positions are the default parameter values we set in the bind function. The set default parameters correspond to the rate parameter in func.

Of course, if the default parameter position is to be changed, does the corresponding placeholders::_? also need to be changed?

As follows:

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

function<double(int, int) > plus1 = bind(func, 4.0, placeholders::_2, placeholders::_3); // Compilation error
function<double(int, int) > plus2 = bind(func, 4.2, placeholders::_1, placeholders::_2); // Compilation passed

The correct way is to write _1 and _2 in post order, although at this time placeholders::_1, and placeholders::_2 correspond to the second parameter and the third parameter respectively. In fact, it can be understood that the parameters after the default in the bind function are not counted in the parameter list.

In fact, the above understanding should be understood from calling plus1 and plus2:

 cout << plus1(10, 5) << endl;
    cout << plus2(10, 5) << endl;

From the perspective of calling the function, isn’t it just the first parameter and the second parameter?

Moreover, if we want the default parameter to be in the middle of the parameter list, with parameters on both sides, it is better from the perspective of calling the function:

To sum up:If the new function coming out of bind() has no default parameters for bind, what about placeholders::_? Which parameter to specify depends on how many parameters are used during the call. Then the corresponding _? It is the corresponding ?th parameter when calling the function.

Seeing this, you should understand how the bind function uses default parameters. In fact, the default parameters implemented by the bind () function are generally better than writing default values directly in the function parameter list.

Because, the default parameters of the new function generated by the bind() function are passed directly in the original function, that is, the parameters in the original function that are modified by bind() into default parameters are not default parameters themselves. Save parameters.

And we can use multiple bind functions () to default different parameters; we can also write different default values for the same parameter; thereby defining multiple function objects.

From the above point of view, the default parameters of bind() are more flexible.

If it is an overloaded function and you want to use the bind () function, to distinguish the overloaded function, use the template parameter of the function:

If it is a public member function in the class, such as a static function, then it can be called outside the class through "class name::function name ()" , then, the static member functions in the class are similar, "class name::function name".

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

    function<int(int, int) > plus2 = bind(SubType::Sub, placeholders::_1,placeholders::_2);

If it is a public non-static member function, it cannot be accessed like "Class name::Function name". You need to add a " & amp;" before it, which becomes: " & amp;Class name::Function name":

Moreover,The difference between non-static member functions and static member functions is that although we write N parameters in the parameter list of the function, it is actually N + 1 parameters, because non-static members The first parameter in the function parameter list is not the first parameter we wrote in the function parameter list, but is the this pointer pointing to the object in it.

In fact, it is consistent with the previous one. If we want to call non-static member methods in a class, we need an object as a medium. Therefore,to call a member function, we need to create an object and pass in the pointer of the object. .

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

int main()
{
    SubType st;
    function<int(int, int) > plus2 = bind( & amp;SubType::Sub, & amp;st, placeholders::_1,placeholders::_2);

    return 0;
}

Or you can pass in an anonymous object; instead of passing in an object pointer, you can pass in an object.

The reason why such an operation is supported is that whether it is lambda or bind(), the bottom layer is actually a functor. In the functor class, the operator() function must be rewritten. Whether it is &st, st, or anonymous Objects are all passed to this operator() function.

 function<int(int, int) > plus2 = bind( & amp;SubType::Sub, Subtype() , placeholders::_1,placeholders::_2);

    function<int(int, int) > plus2 = bind( & amp;SubType::Sub, st , placeholders::_1,placeholders::_2);

Static does not need to be added, but " & amp;" can also be added.