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

vso*_*tco 25 c++ copy-constructor language-lawyer uniform-initialization c++11

显式复制构造函数不允许使用类似的东西Foo foo = bar;,并强制执行复制用法Foo foo(bar);.此外,显式复制构造函数还禁止通过函数的值返回对象.但是,我尝试用大括号替换副本初始化,就像这样

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

int main()
{
    Foo bar;
    Foo foo{bar}; // error here
}
Run Code Online (Sandbox Code Playgroud)

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

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

或(clang ++)

错误:struct initializer中的多余元素

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

bog*_*dan 24

在C++ 14最终确定之后,您遇到了一个由Core issue 1467的解决方案解决的案例.

让我们首先注意,类foo是一个聚合.您的代码是做直接列表初始化foo.列表初始化的规则在[8.5.4p3]中.

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

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

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

[...]

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

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

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

  • 如果T是类类型且初始化列表具有cv 类型的单个元素U,其中Uis T或派生自的类T,则从该元素初始化对象(通过复制初始化进行复制列表初始化,或通过直接初始化直接列表初始化).
  • 否则,如果T是字符数组且初始化列表具有单个元素,该元素是适当类型的字符串文字(8.5.2),则按照该部分中的描述执行初始化.
  • 否则,如果T是聚合,则执行聚合初始化(8.5.1).

[...]

你比如现在落在通过第一点所描述的情况中,并且foo直接列表初始化使用默认的拷贝构造函数(无论它是explicit,因为它是直接初始化).

那就是......如果编译器在缺陷报告中实现了解决方案.

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

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

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

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

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

  • @Christophe因为`Foo`在这种情况下不再是聚合 - 它有一个用户提供的构造函数. (4认同)