Binding a generic member function

2019-02-20 15:44发布

问题:

Sometimes I need to bind some member functions to its calling object, to treat member functions and non-member functions in the same homogeneous way. For example (The tipical callback example):

#include <vector>
#include <functional>

void f(int){}

struct foo
{
    void f(int){}
};

int main()
{
    using namespace std::placeholders;

    foo my_foo;
    std::vector<std::function<void()>> functions;

    functions.push_back( f );
    functions.push_back([](int){});
    functions.push_back( std::bind( &foo::f , my_foo , _1 ) );


    for( auto& function : functions )
    {
        function(0);
    }
}

As more parameters the member function has, more placeholders we need to put inside the std::bind() call.

Now consider a generic version of that. There shouldn't be problem, isn't?:

#include <vector>
#include <functional>

void f(int){}

struct foo
{
    void f(int){}
};

template<typename FIRST , typename SECOND , typename THIRD>
class callback_list
{
    using callback_t = std::function<void(FIRST,SECOND,THIRD>)>;



    //Overload for non-member handlers:
    void add( const callback_t& handler )
    {
        _handlers.push_back( handler );
    }

    //Overload for member handlers:
    template<typename CLASS>
    void add( CLASS& object_ref , 
                      void(CLASS::*member_function)( FIRST,SECOND,THIRD ) )
    {
        using namespace std::placeholders;

        _handlers.push_back( std::bind( member_function , 
                                        std::ref( object_ref ) , 
                                        _1 , _2 , _3
                                      ) 
                           );
    }

    template<typename... ARGS>
    void operator()( ARGS&&... args )
    {
        for( auto& handler : _handlers )
            handler( std::forward<ARGS>( args )... );
    } 

private:
    std::vector<callback_t> functions;
};


void f(int,int,int){}

struct foo
{
    void f(int,int,int){}
};

int main()
{
    using namespace std::placeholders;

    foo my_foo;
    callback_list<int,int,int> callbacks;

    callbacks.add( f );
    callbacks.add([](int,int,int){});
    callbacks.add( my_foo , &foo::f );

    callbacks(0,0,0);
}

Ok. The add() overload for member callbacks just binds the object to the member function, and because the callbacks are of three parameters, we use three placeholders.

But consider this: What if the callbacks have any number of parameters?.
In other words, what I have to do if the callback_list class template is defined with a variadic template?:

template<typename... ARGS>
class callback_list{ ... };

How can I bind a variadic function with any function parameter known at the point of the std::bind() call, i.e., with an unspecified number of placeholders?

回答1:

To work with std::bind, we need to somehow supply a certain amount of placeholders, depending on the amount of function parameters of the callback. I've described a way here how you can do that, by creating a generator for placeholders:

template<int> struct placeholder_template {};

By partially specializing std::is_placeholder for the above template, std::bind sees the instantiations placeholder_template<N> as placeholder types. With the usual indices trick, we then expand placeholder_template<0>{}, placeholder<1>{}, ...., placeholder<N-1>{}, where N is the number of function parameters.

template<typename... Params>
class callback_list
{
public:
    using callback_t = std::function<void(Params...)>;

    //Overload for non-member handlers:
    void add( const callback_t& handler )
    {
        _handlers.push_back( handler );
    }

private:
    //Overload for member handlers:
    template<typename CLASS, int... Is>
    void add( CLASS& object_ref , 
              void(CLASS::*member_function)( Params... ) ,
              int_sequence<Is...> )
    {
        using namespace std::placeholders;

        _handlers.push_back( std::bind( member_function , 
                                        std::ref( object_ref ) , 
                                        placeholder_template<Is>{}...
                                      ) 
                           );
    }
public:
    template<typename CLASS>
    void add( CLASS& object_ref , 
              void(CLASS::*member_function)( Params... ) )
    {
        add( object_ref, member_function,
             make_int_sequence<sizeof...(Params)>{} );
    }


    template<typename... ARGS>
    void operator()( ARGS&&... args )
    {
        for( auto& handler : _handlers )
            handler( std::forward<ARGS>( args )... );
    } 

private:
    std::vector<callback_t> _handlers;
};

The code for the placeholder generator and the integer sequence, from the other answer:

template<int...> struct int_sequence {};

template<int N, int... Is> struct make_int_sequence
    : make_int_sequence<N-1, N-1, Is...> {};
template<int... Is> struct make_int_sequence<0, Is...>
    : int_sequence<Is...> {};

template<int> // begin with 0 here!
struct placeholder_template
{};

#include <functional>
#include <type_traits>

namespace std
{
    template<int N>
    struct is_placeholder< placeholder_template<N> >
        : integral_constant<int, N+1> // the one is important
    {};
}

Side remark: If you want to accept member functions with cv- and ref-qualifiers, you could use a very general "pattern" like

template<class C, class T>
void add(C& ref, T fun);

and restrict via SFINAE. Here's a trait that lets you deduce the number of parameters from such a function pointer (via tuple_size).