Wrote by mutouyun. (http://darkc.at/cxx-bind/)
这篇文的草稿我是在2014年5月11号开始打的,可是拖拖拉拉直到现在才真正动笔写,自己对自己也是醉了。。
之所以写bind而不是什么其他的东西,是因为bind在各种C++的utility里面可以说是最能体现出“利用语言本身来拓展语言功能”这一特征的了。
在C++98/03的时代,人们为了让C++具有把函数和参数打包成闭包(closure)这一能力而发明了bind,从而使C++不仅可以存储算法执行的结果,还可以打包算法和算法的传入参数,存储算法执行的动作,直到调用这个闭包的时候才释放出参数并参与算法计算。有了这个东西之后,再配合可以存储任何可调用体的function对象,不少面向对象里麻烦的调用关系可以被简化到不像话的地步(具体可参看此文:以boost::function和boost:bind取代虚函数)。
在C++98/03下实现一个好用的bind是痛苦的。看看Boost.Bind,还有我自己,为了实现一个完善的bind折腾遍了C++的奇巧淫技……这是很划不来的,平时学习或兴趣玩一玩还可以,真在项目工程里这样做了,如何维护会是一个很头痛的事情。
在C++11里事情变得很不一样了。首先stl里就已经给我们提供了好用的std::bind,然后再就是语言本身的进化,让“写一个bind”之类的事情变得无比简单。于是我去年曾花了点时间,用C++11写了下面这个200多行的“simple bind”,用来让自己被C++98/03腌入味的思维习惯向C++11转换一下:
#include <type_traits> #include <tuple> #include <utility> namespace simple { /* Placeholder */ template <int N> struct placeholder {}; const placeholder<1> _1; const placeholder<6> _6; const placeholder<11> _11; const placeholder<16> _16; const placeholder<2> _2; const placeholder<7> _7; const placeholder<12> _12; const placeholder<17> _17; const placeholder<3> _3; const placeholder<8> _8; const placeholder<13> _13; const placeholder<18> _18; const placeholder<4> _4; const placeholder<9> _9; const placeholder<14> _14; const placeholder<19> _19; const placeholder<5> _5; const placeholder<10> _10; const placeholder<15> _15; const placeholder<20> _20; /* Sequence & Generater */ template <int... N> struct seq { typedef seq<N..., sizeof...(N)> next_type; }; template <typename... P> struct gen; template <> struct gen<> { typedef seq<> seq_type; }; template <typename P1, typename... P> struct gen<P1, P...> { typedef typename gen<P...>::seq_type::next_type seq_type; }; /* Merge the type of tuple */ template <typename T, typename TupleT> struct tuple_insert; template <typename T, typename... TypesT> struct tuple_insert<T, std::tuple<TypesT...>> { typedef std::tuple<T, TypesT...> type; }; template <class TupleT, typename... BindT> struct merge; template <typename... ParsT> struct merge<std::tuple<ParsT...>> { typedef std::tuple<> type; }; template <typename... ParsT, typename B1, typename... BindT> struct merge<std::tuple<ParsT...>, B1, BindT...> { typedef std::tuple<ParsT...> tp_t; typedef typename tuple_insert< B1, typename merge<tp_t, BindT...>::type >::type type; }; template <typename... ParsT, int N, typename... BindT> struct merge<std::tuple<ParsT...>, const placeholder<N>&, BindT...> { typedef std::tuple<ParsT...> tp_t; typedef typename tuple_insert< typename std::tuple_element<N - 1, tp_t>::type, typename merge<tp_t, BindT...>::type >::type type; }; /* Select the value of tuple */ template <typename T, class TupleT> inline auto select(TupleT& /*tp*/, T&& val) -> T&& { return std::forward<T>(val); } template <int N, class TupleT> inline auto select(TupleT& tp, placeholder<N>) -> decltype(std::get<N - 1>(tp)) { return std::get<N - 1>(tp); } /* Return type traits */ template <typename F> struct return_traits : return_traits<decltype(&F::operator())> {}; template <typename T> struct return_traits<T*> : return_traits<T> {}; // check function template <typename R, typename... P> struct return_traits<R(*)(P...)> { typedef R type; }; // check member function #define RESULT_TRAITS__(...) \ template <typename R, typename C, typename... P> \ struct return_traits<R(C::*)(P...) __VA_ARGS__> { typedef R type; }; RESULT_TRAITS__() RESULT_TRAITS__(const) RESULT_TRAITS__(volatile) RESULT_TRAITS__(const volatile) #undef RESULT_TRAITS__ /* Type detect */ template <typename T> struct is_pointer_noref : std::is_pointer<typename std::remove_reference<T>::type> {}; template <typename T> struct is_memfunc_noref : std::is_member_function_pointer<typename std::remove_reference<T>::type> {}; template <typename T> struct is_wrapper : std::false_type {}; template <typename T> struct is_wrapper<std::reference_wrapper<T>> : std::true_type {}; template <typename T> struct is_wrapper_noref : is_wrapper<typename std::remove_reference<typename std::remove_cv<T>::type>::type> {}; /* The invoker for call a callable */ template <typename R, typename F, typename... P> inline typename std::enable_if<is_pointer_noref<F>::value, R>::type invoke(F&& f, P&&... args) { return (*std::forward<F>(f))(std::forward<P>(args)...); } template <typename R, typename F, typename P1, typename... P> inline typename std::enable_if<is_memfunc_noref<F>::value && is_pointer_noref<P1>::value, R>::type invoke(F&& f, P1&& this_ptr, P&&... args) { return (std::forward<P1>(this_ptr)->*std::forward<F>(f))(std::forward<P>(args)...); } template <typename R, typename F, typename P1, typename... P> inline typename std::enable_if<is_memfunc_noref<F>::value && !is_pointer_noref<P1>::value && !is_wrapper_noref<P1>::value, R>::type invoke(F&& f, P1&& this_obj, P&&... args) { return (std::forward<P1>(this_obj).*std::forward<F>(f))(std::forward<P>(args)...); } template <typename R, typename F, typename P1, typename... P> inline typename std::enable_if<is_memfunc_noref<F>::value && !is_pointer_noref<P1>::value && is_wrapper_noref<P1>::value, R>::type invoke(F&& f, P1&& this_wrp, P&&... args) { typedef typename std::remove_reference<P1>::type wrapper_t; typedef typename wrapper_t::type this_t; return (static_cast<this_t&>(std::forward<P1>(this_wrp)).*std::forward<F>(f))(std::forward<P>(args)...); } template <typename R, typename F, typename... P> inline typename std::enable_if<!is_pointer_noref<F>::value && !is_memfunc_noref<F>::value, R>::type invoke(F&& f, P&&... args) { return std::forward<F>(f)(std::forward<P>(args)...); } /* Simple functor for bind callable type and arguments */ template<typename FuncT, typename... ParsT> class fr { typedef std::tuple<typename std::decay<ParsT>::type...> args_type; typedef typename std::decay<FuncT>::type callable_type; typedef typename return_traits<callable_type>::type return_type; callable_type call_; args_type args_; template <class TupleT, int... N> return_type do_call(TupleT&& tp, seq<N...>) { typedef typename merge<TupleT, ParsT...>::type params_t; return invoke<return_type> (call_, static_cast<typename std::tuple_element<N, params_t>::type>(select(tp, std::get<N>(args_)))...); } public: #if defined(_MSC_VER) && (_MSC_VER <= 1800) /* <MSVC 2013> Visual Studio does not support defaulted move constructors or move-assignment operators as the C++11 standard mandates. See: http://stackoverflow.com/questions/24573963/move-constructor-invalid-type-for-defaulted-constructor-vs-2013 */ fr(fr&& rhs) : call_(std::move(rhs.call_)), args_(std::move(rhs.args_)) {} #else fr(fr&&) = default; #endif fr(const fr&) = default; fr(FuncT f, ParsT... args) : call_(std::forward<FuncT>(f)) , args_(std::forward<ParsT>(args)...) {} template <typename... P> return_type operator()(P&&... args) { return do_call(std::forward_as_tuple(std::forward<P>(args)...), typename gen<ParsT...>::seq_type()); } }; /* Bind function arguments */ template <typename F, typename... P> inline fr<F&&, P&&...> bind(F&& f, P&&... args) { return { std::forward<F>(f), std::forward<P>(args)... }; } } // namespace simple
/* Bind function arguments */ template <typename F, typename... P> inline fr<F&&, P&&...> bind(F&& f, P&&... args) { return { std::forward<F>(f), std::forward<P>(args)... }; }
这里有几个小地方需要说明下。
第一,fr是一个仿函数。bind想要实现的功能,目的就是要返回一个可以被施以括号操作符()进行类似函数调用的东西,因此fr是一个仿函数是很自然的事情。
第二,fr是一个类模板,参数是F&&和P&&...。这样做的目的是,fr需要保存bind参数中的f和args,自然需要在编译期获得它们的类型。bind的函数模板参数可以在编译的时候自动帮我们推导出类型,因此把类型传递给fr就可以了。为何是F&&和P&&,而不是F和P呢?这是因为F&&、P&&是通过引用折叠特征推导出来的携带了参数左右值属性的引用类型,这样处理可以让我们在写fr的时候可以直接利用这些引用类型来作为构造时的参数,省下不少类型转换的麻烦。
那么接下来,该看一下类模板fr了。首先,fr需要是一个仿函数,它需要有一个可以接受任意参数的operator();
然后,很自然的,它需要有可以可以接收f和args的构造函数,然后它就成了这个样子:
template<typename FuncT, typename... ParsT> class fr { public: fr(FuncT f, ParsT... args) : call_(std::forward<FuncT>(f)) , args_(std::forward<ParsT>(args)...) {} template <typename... P> return_type operator()(P&&... args) { // ...... } };
template<typename FuncT, typename... ParsT> class fr { typedef std::tuple<typename std::decay<ParsT>::type...> args_type; typedef typename std::decay<FuncT>::type callable_type; callable_type call_; args_type args_; public: fr(fr&&) = default; fr(const fr&) = default; // ...... };
蛋疼的是,这样虽然符合标准,但在VS2013下却是编译不过的,原因见此:Move Constructor - invalid type for defaulted constructor VS 2013,因此我们不得不给默认的移动构造函数加上一个空实现:
// ...... public: #if defined(_MSC_VER) && (_MSC_VER <= 1800) /* <MSVC 2013> Visual Studio does not support defaulted move constructors or move-assignment operators as the C++11 standard mandates. See: http://stackoverflow.com/questions/24573963/move-constructor-invalid-type-for-defaulted-constructor-vs-2013 */ fr(fr&& rhs) : call_(std::move(rhs.call_)), args_(std::move(rhs.args_)) {} #else fr(fr&&) = default; #endif // ......
做好了这些以后,可以说bind的框架已经搭起来了,接下来需要实现最核心的部分,也就是fr的operator()。在写operator()之前,我们必须解决下面几个问题:
首先,我们来想办法从callable_type中把返回值类型return_type萃取出来:
/* Return type traits */ template <typename F> struct return_traits : return_traits<decltype(&F::operator())> {}; template <typename T> struct return_traits<T*> : return_traits<T> {}; // check function template <typename R, typename... P> struct return_traits<R(*)(P...)> { typedef R type; }; // check member function #define RESULT_TRAITS__(...) \ template <typename R, typename C, typename... P> \ struct return_traits<R(C::*)(P...) __VA_ARGS__> { typedef R type; }; RESULT_TRAITS__() RESULT_TRAITS__(const) RESULT_TRAITS__(volatile) RESULT_TRAITS__(const volatile) #undef RESULT_TRAITS__
如上,我们构建了一个traits模板,当它遇到普通函数指针或类成员函数指针时会直接返回函数的返回值类型;当它遇到普通指针时,会取出指针内容的类型再次放入traits里;当遇到的是普通类型,则尝试取出类型的operator()成员函数指针,并把此指针类型放入traits。
于是我们上面的匹配规则覆盖了普通函数指针、类成员函数指针、普通指针和仿函数。
好了,返回值类型的问题解决了,接下来,打包好的args如何解包呢?
首先,打包好的args实际上是一个std::tuple。那么从args中获取第N个元素的方法则是std::get<N>(args_)。在使用bind的时候,我们会为待绑定的function指定它的参数,因此fr的typename... ParsT变参即保存了参数个数。
于是解包的过程就变成了如何在编译期通过ParsT得到一个“0, 1, 2...”的编译期常数数列。
我们可以通过这个模板元gen来完成保存了编译期常数数列seq的计算:
/* Sequence & Generater */ template <int... N> struct seq { typedef seq<N..., sizeof...(N)> next_type; }; template <typename... P> struct gen; template <> struct gen<> { typedef seq<> seq_type; }; template <typename P1, typename... P> struct gen<P1, P...> { typedef typename gen<P...>::seq_type::next_type seq_type; };
template <int... N> return_type do_call(seq<N...>) { typedef std::tuple<ParsT...> params_t; return call_(static_cast<typename std::tuple_element<N, params_t>::type>(std::get<N>(args_))...); } return_type operator()(void) { return do_call(typename gen<ParsT...>::seq_type()); }
上面的写法里,把ParsT...重新打包为一个std::tuple类型,然后使用变参模板的自动展开配合std::tuple_element和std::get把ParsT和args_一个个解出来;接着使用static_cast把使用std::get得到的实参还原为绑定时的类型,放入call_中。
当然,直接通过call_(...)这样调用肯定是错误的,因为call_有可能是一个普通指针,或一个类成员函数指针,必须再引入一个辅助函数invoke来做一次转换。
下面我们来针对各种情况实现invoke函数。首先,很自然的,普通函数指针和仿函数指针都可以使用这个invoke:
template <typename R, typename F, typename... P> inline typename std::enable_if<std::is_pointer<F>::value, R>::type invoke(F&& f, P&&... args) { return (*std::forward<F>(f))(std::forward<P>(args)...); }
template <typename T> struct is_pointer_noref : std::is_pointer<typename std::remove_reference<T>::type> {}; template <typename R, typename F, typename... P> inline typename std::enable_if<is_pointer_noref<F>::value, R>::type invoke(F&& f, P&&... args) { return (*std::forward<F>(f))(std::forward<P>(args)...); }
template <typename T> struct is_memfunc_noref : std::is_member_function_pointer<typename std::remove_reference<T>::type> {}; template <typename R, typename F, typename P1, typename... P> inline typename std::enable_if<is_memfunc_noref<F>::value && is_pointer_noref<P1>::value, R>::type invoke(F&& f, P1&& this_ptr, P&&... args) { return (std::forward<P1>(this_ptr)->*std::forward<F>(f))(std::forward<P>(args)...); }
template <typename T> struct is_wrapper : std::false_type {}; template <typename T> struct is_wrapper<std::reference_wrapper<T>> : std::true_type {}; template <typename T> struct is_wrapper_noref : is_wrapper<typename std::remove_reference<typename std::remove_cv<T>::type>::type> {}; template <typename R, typename F, typename P1, typename... P> inline typename std::enable_if<is_memfunc_noref<F>::value && !is_pointer_noref<P1>::value && !is_wrapper_noref<P1>::value, R>::type invoke(F&& f, P1&& this_obj, P&&... args) { return (std::forward<P1>(this_obj).*std::forward<F>(f))(std::forward<P>(args)...); }
template <typename R, typename F, typename P1, typename... P> inline typename std::enable_if<is_memfunc_noref<F>::value && !is_pointer_noref<P1>::value && is_wrapper_noref<P1>::value, R>::type invoke(F&& f, P1&& this_wrp, P&&... args) { typedef typename std::remove_reference<P1>::type wrapper_t; typedef typename wrapper_t::type this_t; return (static_cast<this_t&>(std::forward<P1>(this_wrp)).*std::forward<F>(f))(std::forward<P>(args)...); }
template <typename R, typename F, typename... P> inline typename std::enable_if<!is_pointer_noref<F>::value && !is_memfunc_noref<F>::value, R>::type invoke(F&& f, P&&... args) { return std::forward<F>(f)(std::forward<P>(args)...); }
template <int... N> return_type do_call(seq<N...>) { typedef std::tuple<ParsT...> params_t; return invoke<return_type> (call_, static_cast<typename std::tuple_element<N, params_t>::type>(std::get<N>(args_))...); }
/* Placeholder */ template <int N> struct placeholder {}; const placeholder<1> _1; const placeholder<6> _6; const placeholder<11> _11; const placeholder<16> _16; const placeholder<2> _2; const placeholder<7> _7; const placeholder<12> _12; const placeholder<17> _17; const placeholder<3> _3; const placeholder<8> _8; const placeholder<13> _13; const placeholder<18> _18; const placeholder<4> _4; const placeholder<9> _9; const placeholder<14> _14; const placeholder<19> _19; const placeholder<5> _5; const placeholder<10> _10; const placeholder<15> _15; const placeholder<20> _20;
上面的写法直接用了const来定义占位符,按照C++标准,这些占位符默认是static的。
当然了,比较好的做法应该是使用extern const,不过这样就需要一个cpp来对这些占位符提供定义了。好在编译器是聪明的,对于static的全局变量,虽然原则上不同的独立编译单元会重新实例化,但如果它们全是一样的,而且又没有在运行时被修改,它们一般会被优化为同一份内存。
占位符要完成“占位”的功能,需要我们从两方面把它筛选出来,并替换为外部实际调用闭包时向operator()传入的参数。
首先,当然是实参本身的替换了。也就是在调用invoke的时候,向invoke传递的参数在传递之时就应当完成替换;
然后,是参数类型的替换。我们在向invoke传递参数的同时,还使用std::tuple_element从params_t中提取出了对应的类型,并对std::get的结果进行了static_cast之后才能正确的invoke。因此params_t必须在std::tuple_element之前,把其中的placeholder换成对应的实参类型。
对于占位符对象的替换,我们可以使用一个select函数来完成。通过C++的函数重载,其实根本不需要祭出“SIFNAE”之类的大招就可以很轻松的把placeholder剃出来:
/* Select the value of tuple */ template <typename T, class TupleT> inline auto select(TupleT& /*tp*/, T&& val) -> T&& { return std::forward<T>(val); } template <int N, class TupleT> inline auto select(TupleT& tp, placeholder<N>) -> decltype(std::get<N - 1>(tp)) { return std::get<N - 1>(tp); }
这里的tp,是调用operator()时传递的所有实参的tuple,而第二个参数则是args_通过std::get解包后得到的参数。偿若args_解包得到的是placeholder,那么select就会使用tp并解包出对应位置的参数传回去;否则就仍然使用args_解包得到的参数。
从params_t中把placeholder的类型置换掉稍微有些麻烦,我们需要用一点模板元的思想去处理这个问题。考虑有这么一个模板merge,它第一个模板参数是调用operator()时传递的实参的类型tuple,后面则是变参列表,传递的是bind时的所有参数类型,那么我们可以先定义出merge的基本样子:
template <class TupleT, typename... BindT> struct merge; template <typename... ParsT> struct merge<std::tuple<ParsT...>> { typedef std::tuple<> type; };
template <typename T, typename TupleT> struct tuple_insert; template <typename T, typename... TypesT> struct tuple_insert<T, std::tuple<TypesT...>> { typedef std::tuple<T, TypesT...> type; }; template <typename... ParsT, typename B1, typename... BindT> struct merge<std::tuple<ParsT...>, B1, BindT...> { typedef std::tuple<ParsT...> tp_t; typedef typename tuple_insert< B1, typename merge<tp_t, BindT...>::type >::type type; }; template <typename... ParsT, int N, typename... BindT> struct merge<std::tuple<ParsT...>, const placeholder<N>&, BindT...> { typedef std::tuple<ParsT...> tp_t; typedef typename tuple_insert< typename std::tuple_element<N - 1, tp_t>::type, typename merge<tp_t, BindT...>::type >::type type; };
template <class TupleT, int... N> return_type do_call(TupleT&& tp, seq<N...>) { typedef typename merge<TupleT, ParsT...>::type params_t; return invoke<return_type> (call_, static_cast<typename std::tuple_element<N, params_t>::type>(select(tp, std::get<N>(args_)))...); } template <typename... P> return_type operator()(P&&... args) { return do_call(std::forward_as_tuple(std::forward<P>(args)...), typename gen<ParsT...>::seq_type()); }
以上,一个简单的bind构建完毕。上面的代码没有考虑类成员的绑定(只考虑了类成员函数),如果读者感兴趣,可自行增加此功能。
友情链接:qicosmos - std::bind技术内幕
Wrote by mutouyun. (http://darkc.at/cxx-bind/)