来自相同类型对象的列表初始化优先级

List-initialization priority from object of same type

#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

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, ...

换句话说,cl2的初始化必须从cl1元素执行,而不是从initializer-list {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)

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

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

缺陷报告被认为具有追溯力。 clang 3.7 和 VS2015 已应用此缺陷报告建议的解决方案,该缺陷报告出现在 N4296 的 C++17 草案中。


在此缺陷报告之前,该行为已包含在 N4140 [over.match.list]:

的这段文本中

When objects of non-aggregate class type T are list-initialized (8.5.4), overload resolution selects the constructor in two phases:

  • Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T and the argument list consists of the initializer list as a single argument.
  • If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.

If the initializer list has no elements and T has a default constructor, the first phase is omitted. In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed. [Note: This differs from other situations (13.3.1.3, 13.3.1.4), where only converting constructors are considered for copy-initialization. This restriction only applies if this initialization is part of the final result of overload resolution. —end note ]

您的 class 不是聚合,因为它有一个 user-provided 构造函数。

上面的文字是 directed-to [dcl.init.list]/3 中的以下要点:

  • Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7).

因此,在已发布的 C++14 中,initializer-list 构造函数实际上应该优先于 copy-constructor,如果它匹配的话。 C++11 有相同的文本。


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

If T is a class type and the initializer list has a single element of type [...]

此文本不在 C++14 中,但后来通过缺陷报告应用。在应用了缺陷报告的更新标准 (N4296) 中,这在 [dcl.init.list]/3 中的要点列表中显示为更高的要点;所以现在 copy-constructor 在这个过程中被更早地选择了,我们还没有达到上面的 [over.match.list] 步骤。

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