2
#include <iostream>
#include <initializer_list>
using namespace std;

struct CL
{
    CL(){}
    CL (std::initializer_list<CL>){cout<<1;}
    CL (const CL&){cout<<2;}
};

int main()
{
    CL cl1;
    CL cl2 {cl1}; //prints 21
}

这是带有复制构造函数和初始化列表构造函数的CL结构。我认为这里必须调用复制构造函数,因为根据 C++ 14 标准,8.5.4/3

类型 T 的对象或引用的列表初始化定义如下:
— 如果 T 是类类型并且初始化器列表具有 cv U 类型的单个元素,其中 U 是 T 或从 T 派生的类,则对象是从该元素初始化(通过复制初始化进行复制列表初始化,或通过直接初始化进行直接列表初始化)。
- 除此以外, ...

换句话说,cl2的初始化必须从cl1元素执行,而不是从初始化列表{cl1}执行。Clang 和 gcc 都打印“21”,只有 Visual Studio 打印“2”,我认为它是正确的。
有两个候选构造函数用于获取 CL 类型的参数cl1

  1. 构造函数std::initializer_list<CL>(通过,因为没有从 CL 到 的这种转换std::initializer_list<CL>
  2. 使用 const CL& 复制构造函数(仅与限定转换 non-const->const 完全匹配)

谁是对的?谁的行为是正确的?

4

1 回答 1

3

tl;dr:已发布的 C++14 文本指定输出21。但是,此代码的行为已被CWG 问题 1467更改,该问题于 2014 年 11 月获得缺陷状态。

缺陷报告被视为追溯适用。clang 3.7 和 VS2015 已应用此缺陷报告建议的解决方案,该报告从 N4296 开始出现在 C++17 草案中。


在此缺陷报告之前,该行为已被 N4140 [over.match.list] 中的此文本所涵盖:

非聚合类类型的对象T被列表初始化(8.5.4)时,重载决议分两个阶段选择构造函数:

  • 最初,候选函数是类 T 的初始化列表构造函数(8.5.4),参数列表由初始化列表作为单个参数组成。
  • 如果找不到可行的初始化列表构造函数,则再次执行重载决议,其中候选函数是类 T 的所有构造函数,参数列表由初始化列表的元素组成。

如果初始化列表没有元素并且 T 有默认构造函数,则省略第一阶段。在复制列表初始化中,如果选择了显式构造函数,则初始化格式错误。[注意:这与其他情况(13.3.1.3、13.3.1.4)不同,其中仅考虑转换构造函数进行复制初始化。仅当此初始化是重载决议的最终结果的一部分时,此限制才适用。——尾注]

您的类不是聚合,因为它具有用户提供的构造函数。

上述文本由 [dcl.init.list]/3 中的以下项目符号指向:

  • 否则,如果 T 是类类型,则考虑构造函数。枚举适用的构造函数,并通过重载决议(13.3、13.3.1.7)选择最佳构造函数。

因此,在已发布的 C++14 中,初始化列表构造函数实际上应该优先于复制构造函数,如果它匹配的话。C++11 有相同的文本。


在您的问题中,您说 C++14 包含:

IfT是一个类类型并且初始化列表有一个类型为 [...]

该文本不在 C++14 中,但后来通过缺陷报告应用。在应用了缺陷报告的更新标准 (N4296) 中,这显示为 [dcl.init.list]/3 中的项目符号列表中较高的项目符号;所以现在复制构造函数在这个过程中被选择了,我们没有达到上面的 [over.match.list] 步骤。

请注意,尽管该缺陷的标题为List-initialization of aggregate from same-type object,但该解决方案实际上会影响聚合和非聚合的初始化。

于 2015-11-29T01:32:42.883 回答