如果我们有带通用引用参数的构造函数,如何声明复制构造函数呢?
http://coliru.stacked-crooked.com/a/4e0355d60297db57
struct Record{
template<class ...Refs>
explicit Record(Refs&&... refs){
cout << "param ctr" << endl;
}
Record(const Record& other){ // never called
cout << "copy ctr" << endl;
}
Record(Record&& other){ // never called
cout << "move ctr" << endl;
}
};
int main() {
Record rec("Hello");
Record rec2(rec); // do "param ctr"
return 0;
}
根据std::tuple
http://en.cppreference.com/w/cpp/utility/tuple/tuple [查看案例3和8]的构造函数列表,这个问题在标准库中以某种方式解决了......但我无法通过stl的代码。
P.S。问题与C++ universal reference in constructor and return value optimization (rvo)
有些相关 P.P.S。现在,我刚刚为EXPLICIT调用添加了额外的第一个参数Record(call_constructor, Refs&&... refs)
。我可以手动检测我们是否只有一个参数,如果它是Record
,而不是重定向调用复制ctr / param ctr,但是......我无法相信没有标准的方法。 ..
答案 0 :(得分:2)
在您的示例中,转发引用与Record&
一起使用。
因此,您可以为Record&
添加额外的重载(转发到复制构造函数):
Record(Record& other) : Record(static_cast<const Record&>(other)) {}
或使用带有转发参考的sfinae。
答案 1 :(得分:1)
在转发引用上重载是一种不好的做法(参见 Effective modern C ++ ,Item 26)。由于重载决策规则,他们倾向于吞噬你传递给他们的一切。
在您的示例中,您正在使用非const Record
对象构造Record
对象,这就是您的副本ctor未执行的原因。如果您将其称为this
Record rec2(const_cast<Record const&>(rec));
然后按预期工作。
解决方案是使用转发引用在构造函数上执行SFINAE,并且禁用应该调用copy ctor的情况;虽然在可变的情况下写它有点难看:
template <
class Ref1, class ...Refs,
typename = typename std::enable_if <
!std::is_same<Ref1, Record&>::value || sizeof...(Refs)
>::type
>
explicit Record(Ref1&& ref, Refs&&... refs)
{
cout << "param ctr" << endl;
}
现在致电
Record rec2(rec); // calls copy ctor
调度到复制构造函数,因为无法为Record&
实例化模板
如果您发现自己这么做(不推荐),您可以通过定义类型特征来执行SFINAE来消除一些混乱
template<class T1, class T2, class... Refs>
using no_copy_ctor = typename std::enable_if <
!std::is_same<T1, T2>::value || sizeof...(Refs)>::type;
因此将上述内容写成
template<class Ref1, class ...Refs, typename = no_copy_ctor<Record&, Ref1, Refs...>>
explicit Record(Ref1&& ref, Refs&&... refs)
{ /*...*/ }
答案 2 :(得分:1)
问题
当你致电Record rec2(rec);
时,你有两个可行的构造函数:你的复制构造函数Record(Record const&)
和带有Refs = {Record&}
的可变参数构造函数,它可以运行到Record(Record&)
。后者是一个更好的候选者,因为它是一个较少 cv 的资格参考,所以它即使不是你想要的也会获胜。
解决方案
你想删除应该调用移动或复制构造函数的任何东西,使其成为可变构造函数的可行候选者。用简单的英语,如果Refs...
由单个类型组成,该类型是从Record
派生的类型的引用或仅仅是普通值 - 我们不想使用可变参数构造函数。包含派生案例也很重要,因为您当然希望SpecialRecord sr; Record r(sr);
调用复制构造函数...
由于出现这种情况,因此将其作为一种类型特征是有用的。基本情况是,它既不是副本也不是移动:
template <typename T, typename... Ts>
struct is_copy_or_move : std::false_type { };
我们只需专注于单一类型:
template <typename T, typename U>
struct is_copy_or_move<T, U>
: std::is_base_of<T, std::decay_t<U>>
{ }
然后我们只需用这个SFINAE&替代替换我们的可变参数构造函数:
template <typename... Refs,
typename = std::enable_if_t<!is_copy_or_move<Record, Refs...>::value>
>
Record(Refs&&...);
现在,如果参数是应该是对副本或移动构造函数的调用,那么可变参数构造函数将不再可行。