直接用于基类初始化的复制省略?

时间:2018-12-21 11:02:31

标签: c++ language-lawyer c++17

由于B构造函数内部的A基类子对象的复制构造,因此以下代码无法同时用Gcc和Clang编译:

struct B{
  B();
  B(const B&) =delete;
  };

struct A:B{
  A():B(B()){} //=> error: use of deleted function...
  };

尽管如此,根据[class.base.init]/7

  

mem-initializer 中的 expression-list braced-init-list 用于初始化指定的子对象(或者,在如果是委派的构造函数,则为完整的类对象),并根据[dcl.init]的初始化规则进行直接初始化。

因此,成员或直接基数的初始化规则相同。对于成员子对象,Gcc和Clang不使用删除的副本构造函数:

struct A2:B{
  B b;
  A2():b(B()){} //=> OK the result object of B() is b
  };

这不是Clang和Gcc的编译器错误吗? B的副本构造函数是否不应该在A()中消失?


有趣的是,即使gcc检查副本结构是否格式正确,它也会忽略此副本构造函数调用,请参见assembly here

1 个答案:

答案 0 :(得分:3)

这确实很奇怪。首先,与是否应在此处调用复制构造函数无关,因为[class.base.init]/7在初始化基数和初始化成员之间没有区别,因此两种情况下的行为应相同。我在标准中找不到任何其他措辞,它们会以某种方式在基础初始化与成员初始化之间引入不对称性。仅基于此,我可以说我们可以得出这样的结论:这里有一种编译器错误。 icc和MSVC在初始化基础与初始化成员方面似乎至少表现一致。但是,icc和MSVC在是否应接受该代码方面存在分歧。

我相信,如果我们再次查看[class.base.init]/7,将会找到是否应调用复制构造函数的答案:

  

mem-initializer中的expression-list或braced-init-list用于初始化指定的子对象(如果是委托的构造函数,则初始化完整的类对象) [dcl.init]用于直接初始化。 […]

强调我的。我相信[dcl.init]的相对位应该是[dcl.init]/17.6,在这里我们可以找到:

  
      
  • 如果目标类型是(可能是cv限定的)类类型:

         
        
    • 如果初始化器表达式是prvalue,并且源类型的cv不合格版本与目标的类相同,则使用初始化器表达式初始化目标对象。 […]

    •   
    • 否则,如果初始化是直接初始化,或者如果它是复制初始化,则源类型的cv不合格版本与该类是同一类,或者是其派生类目的地类别中的构造函数被考虑。   枚举适用的构造函数([over.match.ctor]),并通过重载分辨率([over.match])选择最佳的构造函数。 […]

    •   
  •   
     

[…]

如果要应用17.6.2,则意味着应调用copy-constructor,这将使MSVC成为此示例中行为正确的唯一主要编译器。但是,我的解释是17.6.1一般适用,并且icc是正确的,即您的代码应该编译。这意味着您在这里可能甚至会在GCC和clang中出现两个错误(基本初始化与成员初始化的行为有所不同,+ mem-initializer 调用了copy-ctor,尽管它不应该这样做),还有一个在MSVC中...