2

std::vector コンテナーを連結するために、次の構文の使用を提案するこの post variadic テンプレート関数に出くわしました。

template<typename T>
void append_to_vector(std::vector<T>& v1, const std::vector<T>& v2) {
  std::cout << v2[0] << std::endl;
  for (auto& e : v2) v1.push_back(e);
}


template<typename T, typename... A>
std::vector<T> concat_version3(std::vector<T> v1, const A&... vr) {
    int unpack[] { (append_to_vector(v1, vr), 1)... };
    (void(unpack));
    return v1;
}

私はこれを見たことがないので、それがどのように機能するかを理解するためにそれをいじり始めました:

int unpack[] { (append_to_vector(v1, vr), 0)... };
(void(unpack));

これは、副作用もある動的に生成された初期化リストのようなものですか? 0上記が問題ではないという事実にも困惑しています。-1 と 5 を代入すると、これらの値もそれぞれ問題なく機能しました。

それで、誰かがこのテクニック/構文の名前と、上の2行で正確に何が起こっているのか教えてもらえますか? 関連するSOの投稿を見逃していた場合は、ご指摘いただきありがとうございます。

4

1 に答える 1

5
int unpack[] { (append_to_vector(v1, vr), 1)... };
//        ^^ |                          |   ||| |    array of ints
//           ^                          |    |  ^    array initializer
//                                      ^    |       comma operator
//                                          ^^^      pack expansion

intこれは、パラメータ pack のサイズと同じ数の要素を含むの配列を作成していますvr。配列内の各要素は です1。これは、両方の引数を評価した後にコンマ演算子が返すものです。最後の省略記号は、パラメーター パックのパック展開vrが行われていることを示します。

concat_version3(v1, v2, v3)したがって、すべての引数がvectors である関数を呼び出す場合、上記の式は次のようになります。

int unpack[]{ (append_to_vector(v1, v2), 1), (append_to_vector(v1, v3), 1) };

ブレース初期化リスト内の式を評価することの良い点は、評価の順序が固定されており、左から右に行われることです。

§8.5.4/4 [dcl.init.list]

braced-init-listinitializer-list内で、pack展開 (14.5.3) の結果を含むinitializer-clausesは、出現順に評価されます

したがって、 beforeにv2追加されることが保証されます。これは、あなたが望むものです。v1v3


(void(unpack));

これは、コンパイラからの未使用変数の警告を回避するための方法にすぎません。


ここで、unpack初期化を少し異なる方法で記述します。

int unpack[] { 1, (append_to_vector(v1, vr), 1)... };
//             ^^

オリジナルでは、関数を as として呼び出した場合concat_version3(v1)、つまり空のパラメーター パックを使用して、サイズがゼロの配列を作成しようとするため、コードはコンパイルされませんでした。余分な要素を追加すると、その問題が修正されます。

さらに、戻り値の型がわからないより一般的なコードで上記の式を使用している場合はappend_to_vector、カンマ演算子をオーバーロードする型を返す可能性を防ぐ必要もあります。その場合、あなたは書くでしょう

int unpack[] { 1, (append_to_vector(v1, vr), void(), 1)... };

間に式を追加void()することで、オーバーロードされたカンマ演算子が選択されないようにし、組み込みの演算子が常に呼び出されるようにします。


最後に、 fold 式を理解するコンパイラを使用している場合は、配列のトリック全体を廃止して、単純に次のように記述できます。

template<typename T, typename... A>
std::vector<T> concat_version3(std::vector<T> v1, const A&... vr)
{
    (void)(((append_to_vector(v1, vr), void()), ...));
    return v1;
}

ライブデモ

注: キャストの後の余分な括弧は、 clang のバグvoidにより必要です。

于 2015-11-10T01:01:16.273 に答える