Callable Object Wrapper, Binder

[From]
Author: Su Fang
Links: https://subingwen.com/cpp/bind/
Source: Big C who likes programming

1. Callable Objects

  • Is a function pointer
int print(int a, double b)
{
    std::cout << a << b << std::endl;
    return 0;
}
// Define Function Pointer
int (*func)(int, double) = &print;
  • Is a class object (parafunction) with operator() member function
#include <iostream>
#include <string>

struct Test
{
    // () Operator overload
    void operator()(string msg)
    {
        std::cout << "msg: " << msg << std::endl;
    }
};

int main(void)
{
    Test t;
    t("I'm the man who's going to be the king of Thieves!!!");	// functor
    return 0;
}
  • Is a class object that can be converted to a function pointer
#include <iostream>
#include <string>

using func_ptr = void(*)(int, string);
struct Test
{
    static void print(int a, string b)
    {
        std::cout << "name: " << b << ", age: " << a << std::endl;
    }

    // Convert class object to function pointer
    operator func_ptr()
    {
        return print;
    }
};

int main(void)
{
    Test t;
    // Object is converted to a function pointer and called
    t(19, "Monkey D. Luffy");

    return 0;
}
  • Is a class member function pointer or class member pointer
#include <iostream>
#include <string>

struct Test
{
    void print(int a, string b)
    {
        std::cout << "name: " << b << ", age: " << a << std::endl;
    }
    int m_num;
};

int main(void)
{
    // Define class member function pointer to class member function
    void (Test::*func_ptr)(int, string) = &Test::print;
    // Class member pointer to class member variable
    int Test::*obj_ptr = &Test::m_num;

    Test t;
    // Calling a class member function through a class member function pointer
    (t.*func_ptr)(19, "Monkey D. Luffy");
    // Initialize class member variables through class member pointers
    t.*obj_ptr = 1;
    std::cout << "number is: " << t.m_num << std::endl;

    return 0;
}

In the example above, the types corresponding to these callable objects that meet the criteria are collectively referred to as callable types. Callable types in C++ have a relatively uniform form of operation, but they are defined in a variety of ways, which can be cumbersome when we try to save them in a uniform way or pass a callable object. C++11 now unifies the operations of callable objects by providing std::function<> and std::bind.

2. Callable Object Wrapper

Std::function<>is a wrapper for callable objects. It is a class template that can hold all callable objects except class member (function) pointers. By specifying its template parameters, it can handle functions, function objects, function pointers in a uniform way and allow them to be saved and deferred.

2.1 Basic Usage

Std::function<>must contain a header file called function, the syntax of the callable object wrapper is as follows:

#include <functional>
std::function<return type(List of parameter types)> diy_name = Callable Object;

The following sample code demonstrates the basic use of a callable object wrapper:

#include <iostream>
#include <functional>

int add(int a, int b)
{
    std::cout << a << " + " << b << " = " << a + b << std::endl;
    return a + b;
}

class T1
{
public:
    static int sub(int a, int b)
    {
        std::cout << a << " - " << b << " = " << a - b << std::endl;
        return a - b;
    }
};

class T2
{
public:
    int operator()(int a, int b)
    {
        std::cout << a << " * " << b << " = " << a * b << std::endl;
        return a * b;
    }
};

int main(void)
{
    // Bind a normal function
    std::function<int(int, int)> f1 = add;
    // Bind to static class member function
    std::function<int(int, int)> f2 = T1::sub;
    // Bind a fake function
    T2 t;
    std::function<int(int, int)> f3 = t;

    // function call
    f1(9, 3);
    f2(9, 3);
    f3(9, 3);

    return 0;
}

The input results are as follows:

9 + 3 = 12
9 - 3 = 6
9 * 3 = 27

By testing the code, you can conclude that std::function<>can wrap the callable object and get a uniform format. The wrapped object is equivalent to a function pointer, which is used in the same way as function pointer. The wrapper object can complete the call to the wrapped function.

2.2 used as callback function

Since callback functions are implemented by function pointers themselves, using object wrappers can replace function pointers. Here are some examples:

#include <iostream>
#include <functional>

class A
{
public:
    // Constructor parameter is a wrapper object
    A(const std::function<void()>& f) : callback(f)
    {
    }

    void notify()
    {
        callback(); // Call function pointer from constructor
    }
private:
    std::function<void()> callback;
};

class B
{
public:
    void operator()()
    {
        std::cout << "I'm the man who's going to be the king of Thieves!!!" << std::endl;
    }
};
int main(void)
{
    B b;
    A a(b); // Fake functions are wrapped by wrapper objects
    a.notify();

    return 0;
}

From the example above, we can see that using the object wrapper std::function<> it is very convenient to convert a function-like function into a function pointer. By passing the function pointer, the wrapped function can be called in the appropriate place for other functions.

In addition, using std::function<> as an incoming parameter of a function, callable objects with different definitions can be passed uniformly, which greatly increases the flexibility of the program.

3. Binders

std::bind is used to bind callable objects with their parameters. The result of the binding can be saved using std::function<>and delayed until any time we need it. Generally speaking, it has two main functions:

  1. Bind the callable object with its parameters to a fake function.
  2. Converts a multivariate (parameter number n, n>1) callable object to a unary or (n-1) metacallable object, that is, binds only some parameters.

The syntax format for the binder function is as follows:

// Bind nonclass member functions/variables
auto f = std::bind(Callable Object Address, Binding parameters/placeholder);
// Binding class member letter/variable
auto f = std::bind(Class Functions/Member Address, Class Instance Object Address, Binding parameters/placeholder);

Here's an example of the actual use of the binder:

#include <iostream>
#include <functional>

void callFunc(int x, const std::function<void(int)>& f)
{
    if (x % 2 == 0)
    {
        f(x);
    }
}

void output(int x)
{
    std::cout << x << " ";
}

void output_add(int x)
{
    std::cout << x + 10 << " ";
}

int main(void)
{
    // Binding callable objects and parameters with a binder
    auto f1 = std::bind(output, std::placeholders::_1);
    for (int i = 0; i < 10; ++i)
    {
        callFunc(i, f1);
    }
    std::cout << std::endl;

    auto f2 = std::bind(output_add, std::placeholders::_1);
    for (int i = 0; i < 10; ++i)
    {
        callFunc(i, f2);
    }
    std::cout << std::endl;

    return 0;
}

Results of the test code output:

0 2 4 6 8
10 12 14 16 18

In the above program, std::bind binder is used to control the result of the last execution by binding different functions outside the function. Std::bind binder returns a function-like type, the return value can be directly assigned to a std::function<>, when using we do not need to care about the return value type of the binder, use auto for automatic type derivation.

std::placeholders::_1 is a placeholder, meaning that this location will be replaced by the first parameter passed in when the function is called. There are also other placeholders std::placeholders::_2. std::placeholders::_3. std::placeholders::_4. std::placeholders::_5 etc...

With the concept of placeholders, the use of std::bind becomes very flexible:

#include <iostream>
#include <functional>

void output(int x, int y)
{
    std::cout << x << " " << y << std::endl;
}

int main(void)
{
    // Use the binder to bind callable objects and parameters and call the resulting function-like
    std::bind(output, 1, 2)();
    std::bind(output, std::placeholders::_1, 2)(10);
    std::bind(output, 2, std::placeholders::_1)(10);

    // error, called without a second parameter
    std::bind(output, 2, std::placeholders::_2)(10);

    // The first parameter 10 was swallowed and not used during the call
    std::bind(output, 2, std::placeholders::_2)(10, 20);

    std::bind(output, std::placeholders::_1, std::placeholders::_2)(10, 20);
    std::bind(output, std::placeholders::_2, std::placeholders::_1)(10, 20);

    return 0;
}

Result of sample code execution:

1  2		// std::bind(output, 1, 2)();
10 2		// std::bind(output, std::placeholders::_1, 2)(10);
2 10		// std::bind(output, 2, std::placeholders::_1)(10);
2 20		// std::bind(output, 2, std::placeholders::_2)(10, 20);
10 20		// std::bind(output, std::placeholders::_1, std::placeholders::_2)(10, 20);
20 10		// std::bind(output, std::placeholders::_2, std::placeholders::_1)(10, 20);

As you can see from the test, std::bind can bind all the parameters of a function directly or only some of them. When binding part of the parameter, use std::placeholders to determine which empty parameter will belong to the parameter at the time of the call.

The callable object wrapper std::function<>is not capable of wrapping class member function pointers or class member pointers, but this can be perfectly solved by the combination of the binder std::bind. Let's take another example and then explain the details:

#include <iostream>
#include <functional>

class Test
{
public:
    void output(int x, int y)
    {
        std::cout << "x: " << x << ", y: " << y << std::endl;
    }
    int m_number = 100;
};

int main(void)
{
    Test t;
    // Binding class member function
    std::function<void(int, int)> f1 = std::bind(&Test::output, &t, std::placeholders::_1, std::placeholders::_2);
    // Binding class member variables (public)
    std::function<int&(void)> f2 = std::bind(&Test::m_number, &t);

    // call
    f1(520, 1314);
    f2() = 2333;
    std::cout << "t.m_number: " << t.m_number << std::endl;

    return 0;
}

Output from sample code:

x: 520, y: 1314
t.m_number: 2333

When binding class member functions or member variables with a binder, you need to pass the instance objects they belong to inside the binder function. The type of F1 is std::function<void(int,int)>, which binds the address of output of the member function of Test to the object t by using std::bind, converts it into a fake function, and stores it in object f1.

Class member variable m_bound with binder Number's fake function is stored in wrapper object f2 of type std:: function <int& (void)>, and this member can be modified as needed. Where int is the type of class member bound and allows modification of the bound variable, it needs to be specified as a reference to the variable, and because there are no parameters, the parameter list is specified as void.

The sample program uses the std::function<>wrapper to save the parody function returned by std::bind. If you do not know how the template type (function pointer) of the wrapper is specified, you can use auto directly to derive the type automatically, which makes it easier to use.

Keywords: C++

Added by perrohunter on Sat, 27 Nov 2021 00:04:28 +0200