Consider the following code, where the same reference is forwarded two times to a base class and used there to construct a tuple:
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)} {}
};
Calling first the base class constructor in Derived
as Base<T, T>{t, std::forward<_T>(t)}
and thereafter also the tuple constructor using tup{std::forward<Ts>(ts)...}
has the following reason:
When t
is an rvalue reference, the first tuple argument shall be passed an lvalue-ref to t
and thus be constructed through a copy of t
, whereas the second tuple element should get an rvalue-ref and therefore, if possible, use a move for construction.
This approach seems to be supported by several questions and answers on SO (e.g. here, here and here) which state that the braced-init list performs to a left-to-right evaluation of its arguments.
However, when I use the above code in a simple example, the actual behavior is (consistently) the opposite of what I expected:
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
}
Here is the example using gcc on Coliru. (The gcc compiler apparently had a bug once in this connection, but it's about two years since and should be no issue anymore.)
Questions:
- Where am I wrong on the implementation or the assumptions here?
- How can the above code be fixed to behave as expected: first copy -- then move?