显式复制构造函数和统一初始化

时间:2015-08-02 20:39:04

标签: c++ c++11 language-lawyer copy-constructor uniform-initialization

显式复制构造函数不允许Foo foo = bar;之类的内容,并将复制用法强制为Foo foo(bar);。此外,显式复制构造函数还禁止通过函数的值返回对象。但是,我尝试用大括号替换副本初始化,如此

struct Foo
{
    Foo() = default;
    explicit Foo(const Foo&) = default;
};

int main()
{
    Foo bar;
    Foo foo{bar}; // error here
}

我收到了错误(g ++ 5.2)

  

错误:没有匹配函数来调用'Foo :: Foo(Foo&)'

或(clang ++)

  

错误:struct initializer中的多余元素

删除explicit使代码在g ++下可编译,clang ++仍然失败并出现相同的错误(感谢@Steephen)。这里发生了什么?统一初始化是否被视为初始化列表构造函数(胜过所有其他构建函数)?但如果是这种情况,为什么程序在复制构造函数非显式时编译?

1 个答案:

答案 0 :(得分:24)

在C ++ 14最终确定之后,您遇到了Core issue 1467分辨率解决的案例。

让我们首先注意,类foo是一个聚合。您的代码正在为foo执行 direct-list-initialization 。列表初始化的规则在[8.5.4p3]中。

在C ++ 14中(引自N4140,最接近公布标准的工作草案),上述段落始于:

  

定义了T类型的对象或引用的列表初始化   如下:

     
      
  • 如果T是聚合,则执行聚合初始化(8.5.1)。
  •   
     

[...]

因此,如果您的类是聚合,则编译器会尝试进行聚合初始化,但这会失败。

这被认为是一个问题,并在工作草案中得到修复。从当前版本N4527引用,上述段落现在以:

开头
  

定义了T类型的对象或引用的列表初始化   如下:

     
      
  • 如果T是类类型且初始值设定项列表包含 cv U类型的单个元素,则其中UT或一个派生自T的类,   object从该元素初始化(通过复制初始化)   copy-list-initialization,或者直接初始化   直接一览初始化)。
  •   
  • 否则,如果T是一个字符数组,并且初始化列表的单个元素是一个适当类型的字符串文字   (8.5.2),按照该部分的描述进行初始化。
  •   
  • 否则,如果T是聚合,则执行聚合初始化(8.5.1)。
  •   
     

[...]

您的示例现在属于第一个项目符号点描述的情况,并且foo使用默认的复制构造函数 direct-list-initialized (无论它是否为explicit因为它是直接初始化的。)

即...如果编译器在缺陷报告中实现了解决方案。

  • GCC 5.2.0(和6.0.0 trunk)似乎这样做,但似乎有一个与explicit相关的错误。
  • Clang 3.6.0没有,但3.8.0 trunk没有,并且正确(explicit并不重要)。
  • MSVC 14可以,但IDE中的IntelliSense没有(bar下的波形 - 看起来像IntelliSense使用的EDG编译器也没有更新)。

更新:由于这个答案是写的,工作草案已经进一步修改了几个与问题中的例子和上述解释相关的方式:

  • CWG 2137表示通过将该异常应用于所有类类型,上面引用的段落中的第一个子弹有点过分(问题说明包含相关示例)。子弹的开头现在是:
      
        
    • 如果T是汇总类,则[...]
    •   
  • 论文CWG 1518中包含的P0398R0分辨率表明声明explicit构造函数(甚至是默认值)的类不再是聚合。

这并没有改变以下事实:在实施所有更改之后,问题中的示例旨在工作,无论是否有explicit;值得一提的是,使其发挥作用的潜在机制略有改变。

请注意,所有这些更改都是缺陷报告的解决方案,所以当编译器处于C ++ 14和C ++ 11模式时,它们应该适用。