seastar中apply模板的實現

fibercode發表於2018-07-06

我在閱讀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 << `
`;
}

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


相關文章