std::bind 与 placeholders

Published: May 02 2014

一直觉得 placeholder 这种设施和奇妙,竟然可以调换参数顺序 = =。

using namespace std;
using namespace std::placeholders

void gao(const string& s, int i) { cout << s << i << endl; }   

int main() {    
    auto f = bind(gao, _2, _1);
    f(1, string("1"));
    return 0; 
}

后来去搜了一下实现,有一个简单的 tut,介绍了 placeholder 实现的基本思路。在此基础上拓展,就是一个可用的 bind 和 placeholder 了。

那真实 stl 中的 bind 和 placeholders 是怎么搞的呢?

来到 include/std/functional bind 有两种,一种是直接 bind 到 Function 上面,另一种是 bind<R>,也就是显式指定返回类型的 bind。其实只要看一种就可以知晓原理了。

  /**
   * @brief Function template for std::bind.
   * @ingroup binders
   */
  template<typename _Func, typename... _BoundArgs>
    inline typename
    _Bind_helper<__is_socketlike<_Func>::value, _Func, _BoundArgs...>::type
    bind(_Func&& __f, _BoundArgs&&... __args)
    {
      typedef _Bind_helper<false, _Func, _BoundArgs...> __helper_type;
      typedef typename __helper_type::__maybe_type __maybe_type;
      typedef typename __helper_type::type __result_type;
      return __result_type(__maybe_type::__do_wrap(std::forward<_Func>(__f)),
                           std::forward<_BoundArgs>(__args)...);
    }

注意看 bind 返回的类型:

_Bind_helper<__is_socketlike<_Func>::value, _Func, _BoundArgs...>::type 

很奇怪诶,这个 __is_socketlike 是做什么的呢?

  // Trait type used to remove std::bind() from overload set via SFINAE
  // when first argument has integer type, so that std::bind() will
  // not be a better match than ::bind() from the BSD Sockets API.
  template<typename _Tp, typename _Tp2 = typename decay<_Tp>::type>
    using __is_socketlike = __or_<is_integral<_Tp2>, is_enum<_Tp2>>;

够人性化吧。这是为了防止和 Socket API 里面的 bind 发生重载冲突。 __is_socketlike 在 _Tp 为 integral 或者 enum 类型的时候为 true。当 bind 传进来的第一个参数是 function 时,__is_socketlike 为 false;当 bind 传进来第一个参数是 int 类型时,__is_socketlike 为 true。

_Bind_helper 在第一个模板参数上做了特化。

  // Partial specialization for is_socketlike == true, does not define
  // nested type so std::bind() will not participate in overload resolution
  // when the first argument might be a socket file descriptor.
  template<typename _Func, typename... _BoundArgs>
    struct _Bind_helper<true, _Func, _BoundArgs...>
    { };

当 __is_socketlike 为 true 时,偏特化的版本里面并没有 ::type,由于 SFINAE,此时 std::bind 不出现在 bind 的重载中。而当 __is_socketlike 为 false 时,std::bind 才是干活的时候~~

在 bind 中要用到 _Bind_helper::__maybe_type 和 __Bind_helper::type。在 _Bind_helper 中

  template<bool _SocketLike, typename _Func, typename... _BoundArgs>
    struct _Bind_helper
    {
      typedef _Maybe_wrap_member_pointer<typename decay<_Func>::type>
        __maybe_type;
      typedef typename __maybe_type::type __func_type;
      typedef _Bind<__func_type(typename decay<_BoundArgs>::type...)> type;
    }; 

跟到 _Maybe_wrap_member_pointer 中去。

  /**
   * Maps member pointers into instances of _Mem_fn but leaves all
   * other function objects untouched. Used by tr1::bind(). The
   * primary template handles the non--member-pointer case.
   */
  template<typename _Tp>
    struct _Maybe_wrap_member_pointer
    {
      typedef _Tp type;
      static const _Tp&
      __do_wrap(const _Tp& __x)
      { return __x; }
      static _Tp&&
      __do_wrap(_Tp&& __x)
      { return static_cast<_Tp&&>(__x); }
    };
  /**
   * Maps member pointers into instances of _Mem_fn but leaves all
   * other function objects untouched. Used by tr1::bind(). This
   * partial specialization handles the member pointer case.
   */
  template<typename _Tp, typename _Class>
    struct _Maybe_wrap_member_pointer<_Tp _Class::*>
    {
      typedef _Mem_fn<_Tp _Class::*> type;
      static type
      __do_wrap(_Tp _Class::* __pm)
      { return type(__pm); }
    };

  // Specialization needed to prevent "forming reference to void" errors when
  // bind<void>() is called, because argument deduction instantiates
  // _Maybe_wrap_member_pointer<void> outside the immediate context where
  // SFINAE applies.
  template<>
    struct _Maybe_wrap_member_pointer<void>
    {
      typedef void type;
    };

这里是对 class member function 的情况做封装,在这种情况下,我们需要 _Mem_fn 的辅助(之前 std::function 篇有看过 _Mem_fn 的实现)。注意到还有 <void> 的特化,来看两种 bind

  template<typename _Func, typename... _BoundArgs>
    inline typename
    _Bind_helper<__is_socketlike<_Func>::value, _Func, _BoundArgs...>::type
    bind(_Func&& __f, _BoundArgs&&... __args)

  template<typename _Result, typename _Func, typename... _BoundArgs>
    inline
    typename _Bindres_helper<_Result, _Func, _BoundArgs...>::type
    bind(_Func&& __f, _BoundArgs&&... __args)

我们显示的使用 std::bind<int(int)> 和 std:bind<int> 的时候,会匹配到哪个函数上呢?如果说 std::bind<int> 匹配到了第一种 bind 上去,根据之前的代码,目前还一切顺利,并没有对 _Func 做具体的类型检查,会继续往下传。

      typedef typename __maybe_type::type __func_type;
      typedef _Bind<__func_type(typename decay<_BoundArgs>::type...)> type;

可以肯定,_Bind 里面一定对 __func_type 做了检查,匹配不上则会 SFINAE,而把匹配的任务交给了 std::bind<R>。 但如果是 std::void 呢? 注意到他会在之前匹配

  template<typename _Tp>
    struct _Maybe_wrap_member_pointer
    {
      typedef _Tp type;
      static const _Tp&
      __do_wrap(const _Tp& __x)
      { return __x; }
      static _Tp&&
      __do_wrap(_Tp&& __x)
      { return static_cast<_Tp&&>(__x); }
    };

int 在这里匹配完全没问题,然而 void ~ Ooops,ref to void,你挂了。这就是 void 特化的原因,将匹配错误延迟给 SFINAE,真妙~

废话了半天,刚才进行到了这里

typedef _Bind<__func_type(typename decay<_BoundArgs>::type...)> type;

跟进到 _Bind 里面。

  /// Type of the function object returned from bind().
  template<typename _Signature>
    struct _Bind;
   template<typename _Functor, typename... _Bound_args>
    class _Bind<_Functor(_Bound_args...)>
    : public _Weak_result_type<_Functor>

发现 _Bind 只有一个模板参数封装函数签名,然后特化时进行展开。这应该是为了写着方便的原因吧,is_bind_expression 什么的可不想知道有这么多模板参数,只要知道你是 _Bind 就好了(酱紫应该也是提高编译速度吧~)。

回头看一眼 _Bind 是在哪构造的?就在 std::bind 里面。

      return __result_type(__maybe_type::__do_wrap(std::forward<_Func>(__f)),
                           std::forward<_BoundArgs>(__args)...);

__result_type 是刚才的 _Bind_helper 搞出的 _Bind<__func_type(typename decay<_BoundArgs>::type…)> (我们要构造这样一个 _Bind),__do_wrap 就是刚才的 maybe_wrap_member 对 class member function 的封装。

恩,我们可以看 _Bind 的构造函数了。

     public:
      template<typename... _Args>
        explicit _Bind(const _Functor& __f, _Args&&... __args)
        : _M_f(__f), _M_bound_args(std::forward<_Args>(__args)...)
        { }
      template<typename... _Args>
        explicit _Bind(_Functor&& __f, _Args&&... __args)
        : _M_f(std::move(__f)), _M_bound_args(std::forward<_Args>(__args)...)
        { }
      _Bind(const _Bind&) = default;
      _Bind(_Bind&& __b)
      : _M_f(std::move(__b._M_f)), _M_bound_args(std::move(__b._M_bound_args))
      { }

_M_f 应该是存函数指针的,关键在 _M_bound_args。

      _Functor _M_f;
      tuple<_Bound_args...> _M_bound_args;

原来是用 tuple 做的。诶,这么做真的大丈夫? bind 的时候并没有做 args 和 function 签名的检查,也就是说咱们想怎么干就怎么干,只有在实际 call 的时候做匹配才会出现错误。

那 call 的时候是怎么跟这些 args 关联起来呢。我们来看下 call 的过程吧,也就是 operator() 。 call 有四小类,分别是 unqualified, const, volatile, const volatile。 来看最基础的。

      // Call unqualified
      template<typename... _Args, typename _Result
        = decltype( std::declval<_Functor>()(
              _Mu<_Bound_args>()( std::declval<_Bound_args&>(),
                                  std::declval<tuple<_Args...>&>() )... ) )>
        _Result
        operator()(_Args&&... __args)
        {
          return this->__call<_Result>(
              std::forward_as_tuple(std::forward<_Args>(__args)...),
              _Bound_indexes());
        }

用之前 function 里面的方法,得到了 返回类型 _Result。令人疑惑的是 _Mu,他似乎起到了拼接 _Bound_args 和刚传入的 args 的作用。

_Mu<_Bound_args>()( std::declval<_Bound_args&>(), std::declval<tuple<_Args...>&>() )... 

按 _Bound_args 展开,也就是对于每一个 _Bound_args 中的 type 来说,按

_Mu<type>()( std::declval<type&>(), std::declval<tuple<_Args...>&>() )

来看 _Mu

  template<typename _Arg,
           bool _IsBindExp = is_bind_expression<_Arg>::value,
           bool _IsPlaceholder = (is_placeholder<_Arg>::value > 0)>
    class _Mu;

is_bind_expression 显而易见

  template<typename _Signature>
    struct is_bind_expression<_Bind<_Signature> >
    : public true_type { };

对了,还没看过 placeholders 是什么样的

  template<int _Num> struct _Placeholder { };

    extern const _Placeholder<1> _1;
    extern const _Placeholder<2> _2;

没用的占位符你们好。我们继续看 _Mu。刚才 _Mu 上调了 operator()。

我们先看最基础的情况。

  template<typename _Arg>
    class _Mu<_Arg, false, false>
    {
    public:
      template<typename _Signature> struct result;
      template<typename _CVMu, typename _CVArg, typename _Tuple>
        struct result<_CVMu(_CVArg, _Tuple)>
        {
          typedef typename add_lvalue_reference<_CVArg>::type type;
        };
      // Pick up the cv-qualifiers of the argument
      template<typename _CVArg, typename _Tuple>
        _CVArg&&
        operator()(_CVArg&& __arg, _Tuple&) const volatile
        { return std::forward<_CVArg>(__arg); }
    };

此时 _Mu 没有起到任何作用,只是转发而已。(相应的还有一个 reference_wrapper 的特化)

如果 is_bind_expression 是 true 呢? 诶。。什么时候会出现这种情况,传给 _Mu 的不都是 _Bound_args 么? 卧槽。难道我们可以 f = std::bind(funA, std::bind(funB, 1), 2),然后再 f(“abc”, “def”) 简直。。不忍直视啊。

int a(int en) {
    return en + 1;
}
int b(int en) {
    return en + 1;
}

auto c = std::bind(b, std::bind(a, 1)); 
std:: cout << c();
  /**
   * If the argument is a bind expression, we invoke the underlying
   * function object with the same cv-qualifiers as we are given and
   * pass along all of our arguments (unwrapped). [TR1 3.6.3/5 bullet 2]
   */
  template<typename _Arg>
    class _Mu<_Arg, true, false>
    {
    public:
      template<typename _CVArg, typename... _Args>
        auto
        operator()(_CVArg& __arg,
                   tuple<_Args...>& __tuple) const volatile
        -> decltype(__arg(declval<_Args>()...))
        {
          // Construct an index tuple and forward to __call
          typedef typename _Build_index_tuple<sizeof...(_Args)>::__type
            _Indexes;
          return this->__call(__arg, __tuple, _Indexes());
        }
    private:
      // Invokes the underlying function object __arg by unpacking all
      // of the arguments in the tuple.
      template<typename _CVArg, typename... _Args, std::size_t... _Indexes>
        auto
        __call(_CVArg& __arg, tuple<_Args...>& __tuple,
               const _Index_tuple<_Indexes...>&) const volatile
        -> decltype(__arg(declval<_Args>()...))
        {
          return __arg(std::forward<_Args>(get<_Indexes>(__tuple))...);
        }
    };

果然。。。继续看下去,placeholder 是怎么搞的。

  /**
   * If the argument is a placeholder for the Nth argument, returns
   * a reference to the Nth argument to the bind function object.
   * [TR1 3.6.3/5 bullet 3]
   */
  template<typename _Arg>
    class _Mu<_Arg, false, true>
    {
    public:
      template<typename _Signature> class result;
      template<typename _CVMu, typename _CVArg, typename _Tuple>
        class result<_CVMu(_CVArg, _Tuple)>
        {
          // Add a reference, if it hasn't already been done for us.
          // This allows us to be a little bit sloppy in constructing
          // the tuple that we pass to result_of<...>.
          typedef typename _Safe_tuple_element<(is_placeholder<_Arg>::value
                                                - 1), _Tuple>::type
            __base_type;
        public:
          typedef typename add_rvalue_reference<__base_type>::type type;
        };
      template<typename _Tuple>
        typename result<_Mu(_Arg, _Tuple)>::type
        operator()(const volatile _Arg&, _Tuple& __tuple) const volatile
        {
          return std::forward<typename result<_Mu(_Arg, _Tuple)>::type>(
              ::std::get<(is_placeholder<_Arg>::value - 1)>(__tuple));
        }
    };

从 placeholder 里面拿到标号,再从 args 的 tuple 里面 get,就拿到了对应的 arg。这就是 placeholder 干活的原理啊。 这里有一个 _Safe_tuple_element,不过感觉用处并不大,只是想出错的时候少爆点 error 。。(4K 吓哭你)。

_Mu 已经看完,虽然刚才只是从模板参数上引进去的,不过 _Mu 似乎已经做完了绝大多数事情啊。我们回到 _Bind::operator()。

      // Call unqualified
      template<typename... _Args, typename _Result
        = decltype( std::declval<_Functor>()(
              _Mu<_Bound_args>()( std::declval<_Bound_args&>(),
                                  std::declval<tuple<_Args...>&>() )... ) )>
        _Result
        operator()(_Args&&... __args)
        {
          return this->__call<_Result>(
              std::forward_as_tuple(std::forward<_Args>(__args)...),
              _Bound_indexes());
        }

在看 __call 之前,还有一个奇怪的 _Bound_indexes()。刚才 _Mu 不是已经可以 work 么,为什么要这个。 看一下 __call

      // Call unqualified
      template<typename _Result, typename... _Args, std::size_t... _Indexes>
        _Result
        __call(tuple<_Args...>&& __args, _Index_tuple<_Indexes...>)
        {
          return _M_f(_Mu<_Bound_args>()
                      (get<_Indexes>(_M_bound_args), __args)...);
        }

是一个很悲催的原因,之前 _M_bound_args 可以通过 … 做类型展开,然而对于一个 bind 后的 tuple,我们必须用借助辅助工具,让其中的 element 跟着一起展开。get 是选择的方法,而确定 arg 和 element 对应关系的就是这个 _Index_tuple,上面的 _Indexes 就是展开的模板参数。(其实展开就是 0,1,2,3 …..)

来看一下 index 是怎么搞出来的。

      typedef typename _Build_index_tuple<sizeof...(_Bound_args)>::__type
        _Bound_indexes;

那啥,这里的 sizeof… 是 args 的个数。

吃惊的是 _Build_index_tuple 是在 utility 里面

  // Stores a tuple of indices. Used by tuple and pair, and by bind() to
  // extract the elements in a tuple.
  template<size_t... _Indexes>
    struct _Index_tuple
    {
      typedef _Index_tuple<_Indexes..., sizeof...(_Indexes)> __next;
    };
  // Builds an _Index_tuple<0, 1, 2, ..., _Num-1>.
  template<size_t _Num>
    struct _Build_index_tuple
    {
      typedef typename _Build_index_tuple<_Num - 1>::__type::__next __type;
    };
  template<>
    struct _Build_index_tuple<0>
    {
      typedef _Index_tuple<> __type;
    };

_Build_index_tuple 又一次展现递归的强大属性。我们来看那一下 _Build_index_tuple::type 是怎么解析出来的,以 _Num == 3 为例。

_Build_index_tuple<3>
typedef typename _Build_index_tuple<2>::__type::__next __type;

_Build_index_tuple<2>
typedef typename _Build_index_tuple<1>::__type::__next __type;

_Build_index_tuple<1>
typedef typename _Build_index_tuple<0>::__type::__next __type;

_Build_index_tuple<0>
typedef _Index_tuple<> __type;

反向递推回去

_Build_index_tuple<1>
typedef _Index_tuple<0> __type;

_Build_index_tuple<2>
typedef _Index_tuple<0, 1> __type;

_Build_index_tuple<2>
typedef _Index_tuple<0, 1, 2> __type;

于是我们构造出来了 _Index_tuple<0, 1, 2, … _Num - 1> 简直叹为观止。。。。。

总结一下

  1. _Build_index_tuple 简直是神作,强大的模板。。。
  2. bind 通过 _Mu 模板特化以及模板参数展开实现 placeholder 替换和参数的选择。bind 必须 bind 函数所有的参数,空缺用 placeholder 代替(C++ 木有 partial fun)。
  3. _Mu 做 trait 的时候可不管你 call 的时候给了多少参数(只要给 placeholder 够用了就可以),所以 call bind 的函数如果多传了参数一样编译通过。
  4. bind 后的结果 _Bind 可以转换成 function 么?可以,然而 Bind 并没有提供返回类型和参数类型(_Weak_result 这个基类现在还没有被用到),[del]function 在接受 _Functor 时只做 callable 检查,所以任何类型的 _Bind 都可以被转换成任何类型的 function,take care。[/del] 不过在模板实例化的时候,callable 检查会生效,所以跟第三条一样,返回值要 convertable,参数不能少(但可以多)。 然而 lambda 对参数的要求则是严格的,因为 lambda 是通过 struct operator() 实现的喔。

顺便给一下例子~~

int b(int en, const char* c) {
    return en + 1;
}
auto d = std::bind(b, std::placeholders::_1, std::placeholders::_2);
std::cout << d(1, "asdf") << std::endl;
// std::cout << d(1) << std::endl; error !
// std::cout << d("asdf", "asdf") << std::endl; error !
std::cout << d(1, "asdf", 13) << std::endl;
std::function<int(int, const char*)> d1 = std::bind(b, std::placeholders::_1, std::placeholders::_2);
std::function<int(int, const char*, int)> d2 = std::bind(b, std::placeholders::_1, std::placeholders::_2);
// std::function<int(int, int)> d3 = std::bind(b, std::placeholders::_1, std::placeholders::_2); error !
> 本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 <
blog comments powered by Disqus