2011年12月21日水曜日

forward_as_tuple() からの復元

C++11 の <tuple> には forward_as_tuple() という関数があります。

template<class... Types>
std::tuple<Types&&...> forward_as_tuple(Types&&... t) noexcept
{
 return std::tuple<Types&&...>(std::forward<Types>(t)...);
}
与えられた引数を(転送用に) tuple としてまとめる関数ですがまとめた後にどう使えばいいのか悩んでいました。標準ライブラリの中にはこれと逆の変換、tuple から引数の列に戻すものがないように思えたからです。作ろうにも単純に考えると 0,1,...,N-1 のような parameter pack を作る必要がありその方法が思いつきませんでした。std::tuple の Fusion アダプタを書いて Boost.Fusion の invoke() に回してみたこともあります。ところが Boost 勉強会 #7 の配信を見ていたところ、ボレロ村上さん(@bolero_MURAKAMI)による 中3女子でもわかるconstexpr で index_tuple イディオムが紹介されていました。まさしく 0,1,...,N-1 のような parameter pack を作成する方法です。ということで tuple を引数に展開して関数を呼び出すヘルパを書いてみました。g++ 4.6/4.7 でコンパイルできます。
#include <tuple>
#include <iostream>

// index_tuple 用クラス

template<std::size_t ... args>
struct indices
{
};

template<std::size_t val, typename T>
struct make_indices_impl
{
// I'm not sure that this (dependent value postpones static_assert) is correct
 static_assert(!std::is_same<T, T>::value, "internal error: make_indices_impl instansiated with invalid arguments");
};

template<std::size_t val, std::size_t ... args>
struct make_indices_impl<val, indices<args...>>
{
 typedef indices<val, args...> type;
};

template<std::size_t beg, std::size_t end>
struct make_indices
{
 static_assert(beg < end, "invalid range");
 typedef typename make_indices_impl<beg, typename make_indices<beg+1, end>::type>::type type;
};

template<std::size_t beg>
struct make_indices<beg, beg>
{
 typedef indices<> type;
};

// tuple を展開して関数を呼び出すヘルパ関数

template<typename F, typename T, std::size_t ... args>
constexpr auto call_imp(F f, T t, indices<args...> i) -> decltype(f(std::get<args>(t)...))
{
 return f(std::get<args>(t)...);
}

template<typename F, typename T>
constexpr auto call(F f, T t) -> decltype(call_imp(f, t, std::declval<typename make_indices<0, std::tuple_size<T>::value>::type>()))
{
 typedef typename make_indices<0, std::tuple_size<T>::value>::type indices_type;
 return call_imp(f, t, indices_type());
}

// テスト用関数

constexpr int sum(int n1, int n2, int n3)
{
 return n1 + n2 + n3;
}

constexpr int func() { return 2; }

int main(void)
{
 int n = 0;
 std::cout << call(sum, std::forward_as_tuple(n, 1, func())) << std::endl;
 std::cout << call(sum, std::forward_as_tuple(1, 2, 3)) << std::endl;
 return 0;
}
gcc 4.6/4.7 の libstdc++ では拡張として tuple も constexpr 化されているため↑コードの関数テンプレートに constexpr をつけることができます。なお、-O2 ではなく -O でも call() の呼び出しが定数(それぞれ 3, 6)に展開されていました。

0 件のコメント:

コメントを投稿