有时我需要将一些成员函数绑定到其调用对象,以相同的同构方式处理成员函数和非成员函数。例如(典型的回调示例):
#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_placeholder
,std::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
)推断参数的数量。