我在阅读seastar的源码时(这并不代表我熟悉seastar),偶然发现了seastar把tuple里的每一个element传给一个函数作为参数,这让我很好奇它是如何实现的,让我不自量力地分析一下。
seastar有个头文件apply.hh包含如下代码:
namespace seastar {
template <typename Func, typename Args, typename IndexList>
struct apply_helper;
template <typename Func, typename Tuple, size_t... I>
struct apply_helper<Func, Tuple, std::index_sequence<I...>> {
static auto apply(Func&& func, Tuple args) {
return func(std::get<I>(std::forward<Tuple>(args))...);
}
};
template <typename Func, typename... T>
inline
auto apply(Func&& func, std::tuple<T...>&& args) {
using helper = apply_helper<Func, std::tuple<T...>&&, std::index_sequence_for<T...>>;
return helper::apply(std::forward<Func>(func), std::move(args));
}
template <typename Func, typename... T>
inline
auto apply(Func&& func, std::tuple<T...>& args) {
using helper = apply_helper<Func, std::tuple<T...>&, std::index_sequence_for<T...>>;
return helper::apply(std::forward<Func>(func), args);
}
template <typename Func, typename... T>
inline
auto apply(Func&& func, const std::tuple<T...>& args) {
using helper = apply_helper<Func, const std::tuple<T...>&, std::index_sequence_for<T...>>;
return helper::apply(std::forward<Func>(func), args);
}
以上这段代码实现的功能是把通过tuple打包的各个element数据拆开,并作为一个个参数去调用其他函数,下面展示如何使用它:
void print(int i, double f)
{
std::cout << "i:" << i << " f:" << f << '\n';
}
int main()
{
std::tuple<int, double> values(1, 2.3); //对数据1和2.3打包
apply(print, values); //调用 print, 把1和2.3作为调用参数
}
以上就是把值1和2.3打包,然后通过apply把这些打包的参数通过apply来调用函数print。
上面seastar中apply_helper的主模板没有定义,只是写了一个声明:
template <typename Func, typename Args, typename IndexList>
struct apply_helper;
然后就是准对第三个模板参数做了部分特化,这第三个参数用的到就是index_sequence模板实例类型:
template <typename Func, typename Tuple, size_t... I>
struct apply_helper<Func, Tuple, std::index_sequence<I...>> {
static auto apply(Func&& func, Tuple args) {
return func(std::get<I>(std::forward<Tuple>(args))...);
}
};
这个apply静态函数的秘密就是利用index_sequence模板实例化时携带的非类型可变参数列表"I...",利用编译器自动对可变参数列表的展开,把这些参数列表里的一个个参数去调用std::get,而这个get则是获取tuple里由Index指定的element。Index是一个整形值。注意调用的方式,get后面跟随一个...操作符:
std::get<I>(std::forward<Tuple>(args))...
它把index_sequence实例化的参数列表I的每一个参数去分别调用get,由编译器自动重复这个过程。
例如,如果你有index_sequence<1,2,3>这个模板实例定义的类型,那么
func(std::get<I>(std::forward<Tuple>(args))...)
的意思就是:
func(std::get<1>(std::forward(args)), std::get<2>(std::forward(args)), std::get<3>(std::forward(args)))
STL中的index_sequence_for模板的定义
template<typename... _Types>
using index_sequence_for = make_index_sequence<sizeof...(_Types)>;
具体细节我不多讲了,有兴趣可以去看libstdc++头文件的实现,位于utility头文件。
它的作用就是为给定的一个参数列表,实例化模板index_sequence得到一个新的类型,该类型的模板参数是一系列索引号列表:
例如 index_sequence_for生成一个实例化类型:
index_sequence<0,1,2>。
这样0,1,2这个参数列表就可以用在apply一系列的模板中。
由于在apply代码中,模板演绎时tuple始终不丢弃,所以实际上我们在展开这些tuple element作为函数调用的参数时,只需要一系列序号让编译器去重复调用get模板,这就是index_sequence的作用所在。