【问题标题】:c++11 constructor with variadic universal references and copy constructor具有可变参数通用引用和复制构造函数的 c++11 构造函数
【发布时间】:2015-11-24 01:41:46
【问题描述】:

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

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::tuplehttp://en.cppreference.com/w/cpp/utility/tuple/tuple的构造函数列表[看案例3和8]这个问题在标准库中以某种方式解决了......但我无法通过stl的代码。


附:与C++ universal reference in constructor and return value optimization (rvo)有些相关的问题

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

【问题讨论】:

  • 我无法写出完整的答案,但这应该对您有所帮助akrzemi1.wordpress.com/2013/10/10/too-perfect-forwarding 基本思想是使用 SFINAE。
  • @DanielJour 这有点类似于我的 P.P.S.部分,但是谢谢,我很高兴知道这是已知问题...
  • 为了将来验证这个问题,需要指出的是,universal reference 将正式称为forwarding reference。可以在here找到提案。

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


【解决方案1】:

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

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

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

或者在带有转发参考的那个上使用 sfinae。

【讨论】:

  • 有没有缺点(有手动const_cast)?那么移动 ctr 呢?
  • @tower120:删除const 可能很危险,我在添加const 时看到的唯一副作用是使用其他重载(如此处)。
  • @tower120:移动构造函数可能已经被调用,但如果需要,您可以为const Record&amp;&amp; 添加额外的重载。
  • “顺便说一句,您可能更喜欢使用 static_cast 添加 const” - 为什么?
  • @tower120:见const-cast-vs-static-cast
【解决方案2】:

问题

当您调用Record rec2(rec); 时,您有两个可行的构造函数:您的复制构造函数Record(Record const&amp;) 和带有Refs = {Record&amp;} 的可变参数构造函数,它适用于Record(Record&amp;)。后者是更好的候选者,因为它的 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&&...);

现在如果参数是这样的 应该 是对复制或移动构造函数的调用,可变参数构造函数将不再可行。

【讨论】:

    【解决方案3】:

    在转发引用上重载是一种不好的做法(请参阅Effective modern C++,第 26 条)。由于重载解决规则,它们倾向于吞噬您传递给它们的所有内容。

    在您的示例中,您正在从非常量 Record 对象构造一个 Record 对象,这就是您的复制 ctor 没有被执行的原因。如果你称它为this

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

    然后它按预期工作。

    解决方案是使用转发引用在构造函数上执行 SFINAE,并在应该调用复制 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&amp; 实例化模板,因此分派给复制构造函数

    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)
    { /*...*/ }
    

    【讨论】:

    • 您还必须使用您的 SFINAE 解决方案(重新)实现默认构造函数。
    • 而且条件其实更复杂,你忘了查sizeof...(Refs) == 0
    • @Jarod42 您假设 OP 想要使用可变参数构造函数作为零参数构造函数。
    • @Jarod42 在Record rec2(rec) 中,Refs 的大小实际上为零。你能详细说明一下吗?
    • 对于尺寸,我的意思是你也禁止Record rec2(rec, foo);
    猜你喜欢
    • 2012-06-17
    • 2016-01-02
    • 2013-12-24
    • 1970-01-01
    • 1970-01-01
    • 2023-03-22
    • 1970-01-01
    • 2013-10-13
    • 2020-05-14
    相关资源
    最近更新 更多