绑定泛型成员函数

Binding a generic member function

本文关键字:函数 成员 泛型 绑定      更新时间:2023-10-16

有时我需要将一些成员函数绑定到其调用对象,以相同的同构方式处理成员函数和非成员函数。例如(典型的回调示例):

#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);
    }
}

随着成员函数具有的参数越多,我们需要在std::bind()调用中放置更多的占位符。

现在考虑一个通用版本。应该没有问题,不是吗?

#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);
}

还行。成员回调的add()重载只是将对象绑定到成员函数,并且由于回调是三个参数,因此我们使用三个占位符。

但是考虑一下:如果回调有任意数量的参数怎么办?
换句话说,如果callback_list类模板是用可变参数模板定义的,我该怎么办?

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

如何将可变参数函数与std::bind()调用点已知的任何函数参数绑定,即使用未指定数量的占位符?

为了使用 std::bind,我们需要以某种方式提供一定数量的占位符,具体取决于回调的函数参数数量。我在这里描述了一种方法,通过为占位符创建一个生成器来做到这一点:

template<int> struct placeholder_template {};

通过对上述模板进行部分专用std::is_placeholderstd::bind将实例化placeholder_template<N>视为占位符类型。使用通常的索引技巧,然后我们扩展placeholder_template<0>{}, placeholder<1>{}, ...., placeholder<N-1>{},其中N是函数参数的数量。

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;
};

占位符生成器和整数序列的代码,来自另一个答案:

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
    {};
}

使用 C++14,可以使用 std::index_sequence_for<Params...> 而不是自定义make_int_sequence<sizeof...(Params)> .

<小时 />

附带评论:如果你想接受带有 cv 和 ref 限定符的成员函数,你可以使用一个非常通用的"模式",比如

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

并通过SFINAE进行限制。这是一个特征,可让您从此类函数指针(通过 tuple_size)推断参数的数量。