次のタプルを作成しました。
私はそれをどのように反復すべきか知りたいですか? tupl_size()
がありますが、ドキュメントを読んで、その使用方法がわかりませんでした。また、検索SOもありますが、質問はBoost::Tuple
。
auto some = make_Tuple("I am good", 255, 2.1);
これは、タプルの繰り返しをコンポーネント部分に分解する試みです。
まず、一連の操作を順番に実行することを表す関数。多くのコンパイラは、私が知る限り合法的な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::array
sおよびstd::pair
sで動作します。また、そのオブジェクトの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ですが、カスタム作成のファンクターに置き換えることができます。
これは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; });
_
一般的な考え方: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
_を介して別の間接参照を使用する必要があることです。