考虑以下代码,其中相同的引用被转发两次到一个基类并在那里构造一个元组:
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)}
然后调用元组构造函数 usingtup{std::forward<Ts>(ts)...}
具有以下原因:
当t
是一个右值引用时,第一个元组参数应该传递一个左值引用t
,因此通过一个副本来构造t
,而第二个元组元素应该得到一个右值引用,因此,如果可能的话,使用移动进行构造。
这种方法似乎得到了关于 SO(例如, here、here和here)的几个问题和答案的支持,这些问题和答案表明braced-init 列表对其参数进行从左到右的评估。
但是,当我在一个简单的示例中使用上述代码时,实际行为(始终)与我的预期相反:
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 编译器在这方面显然有过一次错误,但距今已有大约两年了,应该不再有问题了。)
问题:
- 我在这里的实施或假设哪里错了?
- 如何修复上述代码以使其行为符合预期:首先复制——然后移动?