同じ参照が基本クラスに 2 回転送され、そこでタプルを構築するために使用される次のコードを考えてみましょう。
template<typename ... Ts>
struct Base
{
template<typename ... _Ts>
Base(_Ts&& ... ts) : tup{std::forward<_Ts>(ts) ...} {}
std::tuple <Ts ...> tup;
};
template<typename T>
struct Derived : public Base<T, T>
{
template<typename _T>
Derived(_T&& t) : Base<T, T>{t, std::forward<_T>(t)} {}
};
Derived
最初にasで基本クラス コンストラクターを呼び出しBase<T, T>{t, std::forward<_T>(t)}
、その後でタプル コンストラクターを使用することtup{std::forward<Ts>(ts)...}
には、次の理由があります。
が右辺値参照の場合t
、最初のタプル引数は に左辺値参照を渡されるt
ため、 のコピーを介して構築されますがt
、2 番目のタプル要素は右辺値参照を取得する必要があるため、可能であれば構築に move を使用します。
このアプローチは、ブレース初期化リストがその引数の左から右への評価を実行すると述べているSO に関するいくつかの質問と回答 (たとえば、 here、here、およびhere ) によってサポートされているようです。
ただし、単純な例で上記のコードを使用すると、実際の動作は (一貫して) 予想とは逆になります。
struct A
{
A() = default;
A(A const& other) : vec(other.vec) { std::cout<<"copy"<<std::endl; }
A(A && other) : vec(std::move(other.vec)) { std::cout<<"move"<<std::endl; }
std::vector<int> vec = std::vector<int>(100);
};
int main()
{
Derived<A> d(A{}); //prints first "move", then "copy"
std::cout<<std::get<0>(d.tup).vec.size()<<std::endl; //prints 0
std::cout<<std::get<1>(d.tup).vec.size()<<std::endl; //prints 100
}
Coliruで gcc を使用した例を次に示します。(gcc コンパイラには、これに関連して 1 回バグがあったようですが、それから約 2 年が経過しており、もはや問題にはなっていないはずです。)
質問:
- ここでの実装または仮定のどこが間違っていますか?
- 上記のコードを修正して、期待どおりに動作させるにはどうすればよいでしょうか: 最初にコピーしてから移動しますか?