带有可变通用引用和复制构造函数的c++11构造函数

c++11 constructor with variadic universal references and copy constructor

本文关键字:构造函数 复制 c++11 引用      更新时间:2023-10-16

如何声明复制构造函数,如果我们有通用引用参数的构造函数,也?

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的代码。


注:关于c++构造函数通用引用和返回值优化(rvo)的问题

P.P.S.现在,我只是为真正的EXPLICIT调用添加了额外的第一个参数Record(call_constructor, Refs&&... refs)。我可以手动检测如果我们只有一个参数,如果它是Record,然后重定向调用复制ctr/参数ctr,但....我不敢相信这没有标准的方法……

在您的示例中,转发引用与Record&一起使用。

所以你可以为Record&添加一个额外的重载(转发到复制构造函数):

Record(Record& other) : Record(static_cast<const Record&>(other)) {}

问题

当你调用Record rec2(rec);时,你有两个可行的构造函数:复制构造函数Record(Record const&)和带Refs = {Record&}的可变构造函数Record(Record&)。后者是一个更好的候选,因为它是一个不那么cv限定的引用,所以即使这不是你想要的,它也会胜出。

解决方案

您希望删除应该调用move或copy构造函数的任何内容,使其不再是可变构造函数的可行候选。简单地说,如果Refs...由一个类型组成,该类型要么是对Record派生类型的引用,要么只是一个普通值——我们不想使用可变的构造函数。包含派生的情况也很重要,因为您肯定希望SpecialRecord sr; Record r(sr);调用复制构造函数…

既然出现了

,那么将它作为类型trait是很有用的。基本情况是它既不是复制也不是移动:

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&&...);
现在,如果参数使得应该是对复制或移动构造函数的调用,则可变构造函数将不再可行。

重载转发引用是一种不好的做法(参见Effective modern c++ , Item 26)。由于重载解析规则,它们倾向于吞噬您传递给它们的所有内容。

在您的示例中,您正在从非const Record对象构造Record对象,这就是为什么您的复制函数没有执行。如果你这样命名

Record rec2(const_cast<Record const&>(rec));

则按预期工作。

一个解决方案是在构造函数上使用转发引用和禁用,如果应该调用复制函数;在可变变量的情况下,它会变得有些难看:

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&实例化


如果你发现自己经常这样做(不推荐),你可以通过定义一个类型trait来完成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)
{ /*...*/ }