web-dev-qa-db-ja.com

C ++ 11でstd :: Tupleを反復処理する方法

次のタプルを作成しました。

私はそれをどのように反復すべきか知りたいですか? tupl_size()がありますが、ドキュメントを読んで、その使用方法がわかりませんでした。また、検索SOもありますが、質問はBoost::Tuple

auto some = make_Tuple("I am good", 255, 2.1);
19
Mostafa Talebi

これは、タプルの繰り返しをコンポーネント部分に分解する試みです。

まず、一連の操作を順番に実行することを表す関数。多くのコンパイラは、私が知る限り合法的なC++ 11であるにもかかわらず、これを理解するのが難しいことに注意してください。

template<class... Fs>
void do_in_order( Fs&&... fs ) {
  int unused[] = { 0, ( (void)std::forward<Fs>(fs)(), 0 )... }
  (void)unused; // blocks warnings
}

次に、std::Tupleを取り、各要素にアクセスするために必要なインデックスを抽出する関数。そうすることで、後から完璧に進むことができます。

副次的な利点として、私のコードはstd::pairおよびstd::arrayの繰り返しをサポートしています。

template<class T>
constexpr std::make_index_sequence<std::Tuple_size<T>::value>
get_indexes( T const& )
{ return {}; }

肉とジャガイモ:

template<size_t... Is, class Tuple, class F>
void for_each( std::index_sequence<Is...>, Tuple&& tup, F&& f ) {
  using std::get;
  do_in_order( [&]{ f( get<Is>(std::forward<Tuple>(tup)) ); }... );
}

および公開インターフェース:

template<class Tuple, class F>
void for_each( Tuple&& tup, F&& f ) {
  auto indexes = get_indexes(tup);
  for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f) );
}

Tupleと記載されていますが、std::arraysおよびstd::pairsで動作します。また、そのオブジェクトのr/l値カテゴリを、それが呼び出す関数オブジェクトに転送します。また、カスタムタイプにget<N>という無料の関数があり、get_indexesをオーバーライドすると、上記のfor_eachがカスタムタイプで機能することに注意してください。

前述のように、do_in_orderは、多くのコンパイラではサポートされていません。コンパイラは、展開されていないパラメータパックがパラメータパックに展開されるラムダを好まないためです。

その場合、do_in_orderをインライン化できます

template<size_t... Is, class Tuple, class F>
void for_each( std::index_sequence<Is...>, Tuple&& tup, F&& f ) {
  using std::get;
  int unused[] = { 0, ( (void)f(get<Is>(std::forward<Tuple>(tup)), 0 )... }
  (void)unused; // blocks warnings
}

これはそれほど冗長ではありませんが、個人的にはそれほど明確ではありません。私の意見では、do_in_orderがどのように機能するかというシャドウマジックは、インラインで行うことで不明瞭になります。

index_sequence(およびサポートテンプレート)は、C++ 11で記述できるC++ 14の機能です。スタックオーバーフローでこのような実装を見つけるのは簡単です。現在のGoogleのトップヒットは a decent O(lg(n)) depth implementation です。コメントを正しく読んだ場合、少なくとも1回の繰り返しの基礎になる可能性があります実際のgcc make_integer_sequenceの(コメントはsizeof...呼び出しの排除を取り巻くコンパイル時のさらなる改善も指摘しています)。

または、次のように記述できます。

template<class F, class...Args>
void for_each_arg(F&&f,Args&&...args){
  using discard=int[];
  (void)discard{0,((void)(
    f(std::forward<Args>(args))
  ),0)...};
}

その後:

template<size_t... Is, class Tuple, class F>
void for_each( std::index_sequence<Is...>, Tuple&& tup, F&& f ) {
  using std::get;
  for_each_arg(
    std::forward<F>(f),
    get<Is>(std::forward<Tuple>(tup))...
  );
}

手動での展開を回避しながら、より多くのコンパイラでコンパイルします。 auto&&iパラメーターを介してIsを渡します。

C++ 1zでは、std::apply関数オブジェクトでfor_each_argを使用して、インデックスの操作をなくすこともできます。

template<class F, class...Ts, std::size_t...Is>
void for_each_in_Tuple(const std::Tuple<Ts...> & Tuple, F func, std::index_sequence<Is...>){
    using expander = int[];
    (void)expander { 0, ((void)func(std::get<Is>(Tuple)), 0)... };
}

template<class F, class...Ts>
void for_each_in_Tuple(const std::Tuple<Ts...> & Tuple, F func){
    for_each_in_Tuple(tuple, func, std::make_index_sequence<sizeof...(Ts)>());
}

使用法:

auto some = std::make_Tuple("I am good", 255, 2.1);
for_each_in_Tuple(some, [](const auto &x) { std::cout << x << std::endl; });

デモ

std::index_sequenceとファミリはC++ 14の機能ですが、C++ 11で簡単に実装できます(SOには多くの機能があります)。多態性ラムダもC++ 14ですが、カスタム作成のファンクターに置き換えることができます。

28
T.C.

これはT.C.が以前に受け入れたものよりも似た、より冗長な解決策であり、少し理解しやすいかもしれません(おそらく、ネットにある他の1000個と同じです):

_template<typename TupleType, typename FunctionType>
void for_each(TupleType&&, FunctionType
            , std::integral_constant<size_t, std::Tuple_size<typename std::remove_reference<TupleType>::type >::value>) {}

template<std::size_t I, typename TupleType, typename FunctionType
       , typename = typename std::enable_if<I!=std::Tuple_size<typename std::remove_reference<TupleType>::type>::value>::type >
void for_each(TupleType&& t, FunctionType f, std::integral_constant<size_t, I>)
{
    f(std::get<I>(std::forward<TupleType>(t)));
    for_each(std::forward<TupleType>(t), f, std::integral_constant<size_t, I + 1>());
}

template<typename TupleType, typename FunctionType>
void for_each(TupleType&& t, FunctionType f)
{
    for_each(std::forward<TupleType>(t), f, std::integral_constant<size_t, 0>());
}
_

使用法(_std::Tuple_を使用):

_auto some = std::make_Tuple("I am good", 255, 2.1);
for_each(some, [](const auto &x) { std::cout << x << std::endl; });
_

使用法(_std::array_を使用):

_std::array<std::string,2> some2 = {"Also good", "Hello world"};
for_each(some2, [](const auto &x) { std::cout << x << std::endl; });
_

[〜#〜] demo [〜#〜]


一般的な考え方:T.C.のソリューションのように、インデックス_I=0_から始めて、タプルのサイズまで上げます。ただし、ここでは変数ごとの拡張ではなく、一度に1つずつ行います。

説明:

  • IがTupleのサイズに等しい場合、_for_each_の最初のオーバーロードが呼び出されます。関数は何もしないので、再帰は終了します。

  • 2番目のオーバーロードは、引数std::get<I>(t)を使用して関数を呼び出し、インデックスを1つ増やします。コンパイル時にIの値を解決するには、クラス_std::integral_constant_が必要です。 _std::enable_if_ SFINAEスタッフは、コンパイラが以前のオーバーロードからこのオーバーロードを分離し、IがTupleサイズより小さい場合にのみこのオーバーロードを呼び出すために使用されます(Coliruではこれが必要ですが、なしで動作するVisual Studio)。

  • 3番目は_I=0_で再帰を開始します。通常、外部から呼び出されるのはオーバーロードです。




EDIT:一般的なテンプレートパラメータを使用して、_std::array_および_std::pair_を追加サポートするためにYakkが言及したアイデアも含めました_std::Tuple<Ts ...>_に特化したものの代わりにTupleType

TupleType型を推測する必要があり、そのような「ユニバーサルリファレンス」であるため、これにはさらに、完全な転送を無料で取得できるという利点があります。欠点は、TupleTypeも参照型として推定されるため、_typename std::remove_reference<TupleType>::type_を介して別の間接参照を使用する必要があることです。

11
davidhigh