C++ 從 std::tuple 取出 Parameter Pack

將 Parameter Pack 建成 std::tuple

很 trivial,因為 std::tuple 就是直接 accept parameter pack XD

template<typename ... Args>
void create_tuple(const Args&... args)
{
    std::tuple<Args...> t(args...);    
}

包成 make style function

template<typename ... Args>
auto make_tuple(Args&&... args) @1
{
    return std::tuple< typename std::decay<Args>::type...>( @2
        std::forward<Args> @3 (args)... @4
    );
}
  1. 使用帶有 &&Args type: 實行 Universal Refernce,為了不要遺失掉 T&& 的 type 資訊。

  2. std::decay: 為了不要讓 tuple 含有像 T& 的值,將 Passing 進來的 Args... 去掉可能的 reference type,只存放 value-only 的 std::tuple

  3. std::forward: 為了防止 T&& 因為帶有名字(named variable) 而退化成 lvalue-reference。施以 perfect forwarding (做 static_cast<Args&&>(args)...)。

  4. 使用 pattern 展開 args... 這個 parameter pack。將 ... 放在 std::forward<Args> 外部,會連同 std::forward<Args> 也一起被展開。

Universal Reference

Argument Argument Type Signature Args Type
v & Args& &
std::move(v) && Args& &
v & Args&& &
std::move(v) && Args&& &&

從 std::tuple 取出 Parameter Pack

  1. 來源目標: std::tuple<Args...> t;
  2. 已知的取 std::tuple 元素的 function: std::get<Index>(t);
  3. 用來當 one-by-one 取值的 Index sequence: std::integer_sequence<T, T ... integers> seq;
  4. Function 的自動樣版參數推導
  5. Parameter Pack Pattern 展開

我們需要利用 4. 來取得 3. 的 integers...,再使用 5. 的方法展開 integers... 到 2. 的 function 上,就可以取出 1. 的各個元素。

template<typename F, typename TupleT>
auto invoke(F&& f, TupleT&& t)
{
    constexpr tuple_size = std::tuple_size @1< typename std::decay<TupleT>::type @2>::value;
    constexpr index_seq = std::make_index_sequence<tuple_size>(); @3
    return expand(std::forward<F>(f), std::forward<TupleT>(t), index_seq);
}

template<typename F, typename TupleT, std::size_t ... Index>  @4-1
auto expand(F&& f, TupleT&& t, std::index_sequence<Index...>) @4-2
{
    return f(std::get<Index>(t)...); @5    
}
usage example
int add3(int v1, int v2, int v3) { return v1 + v2 + v3; }
int main(){
  auto t = make_tuple(1, 2, 3);
  invoke(f, std::move(t));
  // tuple_size => 3
  // index_seq => std::index_sequence<0, 1, 2>
  // f(std::get<Index>(t)...) => f( std::get<0>(t), std::get<1>(t), std::get<2>(t))
}
  1. std::tuple_size<T>::value: 要先取出 std::tuple 的大小。

  2. typename std::decay<TupleT>::type: 我發現 std::tuple_size 似乎是無法接受 reference type 的 std::tuple。故使用 std::decay 取出其原始 type。

  3. std::make_index_sequence<N>: 做出一個含有 0, 1, 2, ..., N - 1 的遞增序列。其 type 是 std::integer_sequence<std::size_t, 0, 1, 2, ... , N - 1>,也是 std::index_sequence<0, 1, 2, ... , N - 1>
    目的是為了使用其序列,展開後可以當作是 std::get 的 Index 參數。

  4. 傳入 index_sequence<0, 1, ..., N-1>expand function,其序列會被自動推導到 std::size_t ... Index 的 template 參數。

  5. 展開 Index... 這個 parameter pack。將 ... 放在 std::get 外部,讓 std::get<index> 變成一個 pattern,會連同被一起展開。

Live: Wandbox

可以幹麻?

  • 將參數 cache 起來,並延遅呼叫。似乎可以做 thread pool 的 task store。 之後再開一篇描述基於此技術的 thread pool。

p.s. std::index_sequence 是 C++ 14 的東西,所以會需要 -std=14 的 compiler option。

comments powered by Disqus