回调中的占位符_1是如何工作的

How placeholders as _1 in a callback works?

本文关键字:何工作 工作 占位符 回调      更新时间:2023-10-16

编译器如何解释符号_1,以及绑定是如何发生的?考虑以下示例:

class A {
public:
boost::function<void (int x)> g;
};
class B {
public:
B() {}
static void foo(int i) { cout << "Hack: " << i <<endl;  }
};
int main() {
A a;
a.g = boost::bind(B::foo,_1);
a.g(2);
return 0;
}

boost::bind(B::foo,_1);行内部发生了什么魔法?

_1是如何映射到下一行a.g(2);中传递的参数的?

输出:
破解:2

我将尽我所能进行解释。首先,_1只是一个全局变量。在这方面,它没有什么特别的,它也可以被命名为其他任何东西——placeholder1SergeyA。然而,像_1这样的名称很短,有很好的理解意义,并且以_开头,这降低了它与程序中其他全局名称冲突的可能性。

神奇之处在于这个变量的类型。它有一种特殊的类型,反映在生成的bind*对象中。稍后,当调用operator()时,该类型被识别为从operator()参数中获取一个参数。

以下是一些类似C++的伪代码,虽然不正确,但很有说明性:

template<class F, class... ARG>
struct bound {
bound(F f, ARGS&&... args) : bound_args(args...), functor(f) { }
std::tuple<ARG...> bound_args;
template<class... T>  
void operator()(T&&... args);
F f;
};
template<class F, class... T>
auto bind(F f, T&& args) {
return bound<std::remove_reference_t<T>...>(f, args...);
}

现在,让我们介绍一种占位符类型。

template<size_t N>
struct placeholder {
enum { position = N; };
template<class...T>
auto operator()(T&&... args) {
return std::get<position>(std::make_tuple(arg...));
}
};
placeholder<0> _1;
placeholder<1> _2;

到目前为止还不错。现在,让我们看看operator()是如何实际处理绑定对象的:

template<class... BOUND_ARGS>
template<class... CALL_ARGS>
void bound_object<BOUND_ARGS...>::operator() (CALL_ARGS&&... args) {
call_impl(args..., make_index_sequence<sizeof...(BOUND_ARGS)>{});
}

这里需要make_index_sequence来将元组值提取到函数参数中,所以不要太关注它。这里是call_impl;

template<class... BOUND_ARGS>
template<class... CALL_ARGS, size_t... ix>
void  bound_object<BOUND_ARGS...>::call_impl(CALL_ARGS&&... args, std::index_sequence<ix...>) {
f(to_arg().(std::get<ix>(bound_args), args...)...);
}

最后一块拼图是to_arg:

template<class B, class... ARGS>
auto to_arg(B&& b, ARGS... args) {
return b;
}
template<class... ARGS>
auto to_arg(placeholder<0> p, ARGS&&... args) {
return p(args);
}
template<class... ARGS>
auto to_arg(placeholder<1> p, ARGS&&... args) {
return p(args);
}

这里的整个to_arg是根据绑定参数类型为您提供绑定参数或提供的参数之一。在上面的例子中,我使用了3个重载,因为你可以部分专门化一个函数,但当然,把它放在一个类中并部分专门化这个类会更有意义。