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

Explicit copy constructor and 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)

error: no matching function for call to 'Foo::Foo(Foo&)'

或 (clang++)

error: excess elements in struct initializer

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

您遇到了一个案例,在 C++14 完成后立即通过 Core issue 1467 的决议解决。

首先请注意,class foo 是一个聚合。您的代码正在为 foo 执行 直接列表初始化 。列表初始化的规则在 [8.5.4p3] 中。

在 C++14 中(引用自 N4140,最接近已发布标准的工作草案),上面的段落开头为:

List-initialization of an object or reference of type T is defined as follows:

  • If T is an aggregate, aggregate initialization is performed (8.5.1).

[...]

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

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

List-initialization of an object or reference of type T is defined as follows:

  • If T is a class type and the initializer list has a single element of type cv U, where U is T or a class derived from T, the object is initialized from that element (by copy-initialization for copy-list-initialization, or by direct-initialization for direct-list-initialization).
  • Otherwise, if T is a character array and the initializer list has a single element that is an appropriately typed string literal (8.5.2), initialization is performed as described in that section.
  • Otherwise, if T is an aggregate, aggregate initialization is performed (8.5.1).

[...]

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

也就是说...如果编译器实现了缺陷报告中的解决方案。

  • GCC 5.2.0(和 6.0.0 主干)似乎这样做,但似乎有一个与此相关的错误 explicit
  • Clang 3.6.0 没有,但 3.8.0 trunk 有,而且正确(explicit 无所谓)。
  • MSVC 14 有,但 IDE 中的 IntelliSense 没有(bar 下的波浪线 - 看起来 IntelliSense 使用的 EDG 编译器也没有更新)。

更新:由于写了这个答案,工作草案在与问题中的例子和上面的解释相关的几个方面得到了进一步的修改:

  • CWG 2137 表示上面引用的段落中的第一个项目符号将该例外应用于所有 class 类型(问题说明包含相关示例)有点过头了。项目符号的开头现在是:
    • If T is an aggregate class and [...]
  • CWG 1518 contained in paper P0398R0 的决议表明 class 声明 explicit 构造函数(甚至默认)的 es 不再是聚合。

这不会改变这样一个事实,即在实施所有更改之后,问题中的示例旨在工作,有或没有 explicit;值得一提的是,使其发挥作用的底层机制发生了细微变化。

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