注:下面的代码段单击即可显示

 

在之前 对std::bind生命周期的探究 中,提到了 std::bind 调用过程中,会使用 std::tuple<class… Args> 将需要的参数保存起来,而这种情况下,传引用则会失去本来的意义. 对于需要传引用的场合,需要显式地使用 std::ref<T> 对传引用的对象进行包裹,如下所示

 

那么, std::ref 是如何实现引用的传递的呢?

可以发现, std::ref 产生了一个储存 _Val 指针的对象,当需要的时候再转换回 _Ty& 引用类型(小Tip:这里可以发现它使用了 std::addressof 函数,而不是通常的取地址运算符&,这是因为运算符 & 可以被重载,需要考虑到这种情况.而 std::addressof 的一种可行方式是,把输入的对象转换为 char& ,进行取址,再转换回 T* 类型,就不会调用运算符重载了.). 可以发现, std::ref 返回的类型,是存在 _Ty& 的隐式转换的,那么可以直接尝试传入 std::ref 返回的对象,使其自动转换:

上面的代码是可以通过编译的,也就意味着直接传参是可行的.不过在 std::bind 中,并没有采用这种方法,而是采用模板识别来进行特殊处理.让我们来梳理一下 std::bind 的实现:

std::bind 函数会返回一个 _Binder 对象,在其运算符()的重载中会利用 _Call_binder 去调用原函数.需要注意的是上面定义的 _Seq 类型,那定义了一个特化的 std::make_integer_sequence() 函数,这个函数会返回 std::integer_sequence 类型,通常用于产生一个类型为T的参数序列(T…).上面的代码通过 sizeof…() 获取了函数参数的个数,利用 _Seq 产生参数序列,以便后面对函数的参数进行填入.
接下来我们进一步看看 _Call_binder 里面是怎么回事:

在_Call_binder的代码中,就根据之前产生的 integer_sequence 进行展开,对之前 std::tuple 中保存的每个参数调用 _Fix_arg进行额外处理(此处出现的 std::get<I,class… Types>,是用于获取 std::tuple 中第 I 个元素的函数)

_Fix_arg 是对几种特殊情况进行了处理:

  1.  std::ref 的情况
  2. 内部参数也是 std::bind 的情况
  3. 是占位符的情况(从调用时的参数列表取相应的参数)

需要注意的是 _Select_Fixer 的定义,在定义处使用模板参数的默认值,将几种特殊情况的检测,与下面的模板特化,联系在了一起.以 std::ref 的检测为例, _Select_fixer 的第二个模板参数的值,是 _Unrefwarp 检测是否为  std::ref 类型的结果,对应着后面 _Select_fixer<_Cv_TiD, true, false, 0> 的特化.在 std::ref 的处理中,手动转换回了 _Cv_TiD::type& 引用.

PS:模板特化博大精深……

PS2:扩展阅读:Parameter Pack