在初始化列表中复制构造

Copy Construction in Initializer Lists

我正在探索 std::intializer_list 丑陋 世界。

据我了解的标准:

§ 11.6.4:

  1. An object of type std::initializer_list is constructed from an initializer list as if the implementation generated and materialized (7.4) a prvalue of type “array of N const E”, where N is the number of elements in the initializer list. Each element of that array is copy-initialized with the corresponding element of the initializer list, and the std::initializer_list object is constructed to refer to that array. [ Note: A constructor or conversion function selected for the copy shall be accessible (Clause 14) in the context of the initializer list. — end note ] [...]

因此,如果类型 Eclass,我希望 复制构造函数 被调用。


以下class不允许复制构造:

struct NonCopyable {
  NonCopyable() = default;   
  NonCopyable(const NonCopyable&) = delete;
};

我将尝试用这个 class 实例化一个 std::initializer_list

#include <vector>

void foo() {
  std::vector<NonCopyable>{NonCopyable{}, NonCopyable{}};
}

使用 g++-8.2 -std=c++14 我得到了我期望的结果,编译器错误:

error: use of deleted function 'NonCopyable::NonCopyable(const NonCopyable&)'.

完美!


但是,行为随着新标准而改变。

的确,g++-8.2 -std=c++17 编译。

Compiler Explorer Test


我一开始以为是新标准对copy elision的新要求,

然而,改变标准库实现(保持c++17)错误又回来了:

clang-7 -std=c++17 -stdlib=libc++ 失败:

'NonCopyable' has been explicitly marked deleted here NonCopyable(const NonCopyable&) = delete;

Compiler Explorer Test


那我错过了什么?

1) C++17 是否需要initializer_list的元素的复制构造中进行复制省略?

2) 为什么libc++ 实现不在这里编译?


编辑 请注意,在示例 g++ -std=c++17(编译)中,如果我将 默认构造函数 更改为 "user defined":

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

程序不再编译(不是因为 link 错误)。

Compiler Explorer Example

Does C++17 require copy-elision in the copy construction of elements of initializer_list?

初始化 initializer_list 的元素无法保证 "copy construction" 的使用。它仅仅执行复制初始化。而复制初始化是否调用复制构造函数完全取决于初始化中发生的事情。

如果您有一个可从 int 转换的类型,并且您执行 Type i = 5;,那就是复制初始化。但它不会调用复制构造函数;它将改为调用 Type(int) 构造函数。

是的,initializer_list 引用的数组元素的构造容易受到复制省略的影响。包括 C++17 的保证省略规则。

也就是说, 不受这些规则影响的是 vector 本身 的初始化。 vector 必须从 initializer_list 复制对象,因此它们必须具有可访问的复制构造函数。 compiler/library 实现如何设法解决这个问题尚不得而知,但这绝对是不合规范的行为。

问题是这种类型:

struct NonCopyable {
  NonCopyable() = default;   
  NonCopyable(const NonCopyable&) = delete;
};

可简单复制。因此,作为优化,由于 std::initializer_list 仅由数组支持,libstdc++ 所做的只是将整个内容存储到 vector 中作为优化。请注意,即使此类型具有已删除的复制构造函数,它也是可简单复制的!

这就是为什么当您将默认构造函数设置为用户提供时(只需编写 ; 而不是 = default;),突然不再编译。这使得该类型不再是可复制的,因此 memcpy 路径消失了。

至于这种行为是否正确,我不确定(我怀疑是否有要求此代码 不得 编译?我提交 89164 只是如果)。您当然 想要 libstdc++ 在平凡可复制的情况下采用该路径 - 但也许它需要排除这种情况?在任何情况下,您都可以通过另外删除复制赋值运算符(无论如何您可能都想这样做)来完成相同的操作——这也会导致类型无法被简单地复制。

这在 C++14 中无法编译,因为您无法构造 std::initializer_list - 那里的复制初始化需要复制构造函数。但是在保证复制省略的 C++17 中, std::initializer_list 的构造很好。但是实际构造 vector 的问题与 std::initializer_list 完全不同(事实上,这完全是转移话题)。考虑:

void foo(NonCopyable const* f, NonCopyable const* l) {
  std::vector<NonCopyable>(f, l);
}

在 C++11 中编译得很好...至少从 gcc 4.9 开始是这样。