seastar中apply模板的实现

我在阅读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的作用所在。

上一篇:Token的使用


下一篇:python设计模式(十四):模板方法模式