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

时间:2015-08-29 14:39:54

标签: c++ templates c++11 constructor c++14

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

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,但是......我无法相信没有标准的方法。 ..

3 个答案:

答案 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&实例化模板

Demo

如果您发现自己这么做(不推荐),您可以通过定义类型特征来执行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&&...);

现在,如果参数是应该是对副本或移动构造函数的调用,那么可变参数构造函数将不再可行。