GCC 有一个错误。该标准使这一点有效。看:
请注意,这有两个方面
- 一般如何以及如何进行初始化?
- 在重载解析期间如何使用初始化,它有什么成本?
第一个问题在第 部分回答8.5
。第二个问题在章节中回答13.3
。例如,引用绑定在8.5.3
and中处理13.3.3.1.4
,而列表初始化在8.5.4
and中处理13.3.3.1.5
。
8.5/14,16
:
表单中发生的初始化
T x = a;
以及在参数传递、函数返回、抛出异常 (15.1)、处理异常 (15.3) 和聚合成员初始化 (8.5.1) 中称为复制初始化。
.
.
初始化器的语义如下[...]:如果初始化器是一个花括号初始化列表,则该对象是列表初始化的(8.5.4)。
在考虑候选function
者时,编译器将看到一个初始化列表(它还没有类型——它只是一个语法结构!)作为参数,而 astd::vector<std::string>
作为function
. 为了弄清楚转换成本是多少,以及我们是否可以在重载的情况下转换这些成本,13.3.3.1/5
说
13.3.3.1.5/1
:
当参数是初始值设定项列表 (8.5.4) 时,它不是表达式,并且适用特殊规则将其转换为参数类型。
13.3.3.1.5/3
:
否则,如果参数是非聚合类 X 并且根据 13.3.1.7 的重载决议选择 X 的单个最佳构造函数来执行参数初始化器列表中 X 类型对象的初始化,则隐式转换序列是用户-定义的转换顺序。用户定义的转换允许将初始化列表元素转换为构造函数参数类型,除非在 13.3.3.1 中注明。
非聚合类X
是std::vector<std::string>
,我将在下面找出最好的构造函数。最后一条规则允许我们在以下情况下使用用户定义的转换:
struct A { A(std::string); A(A const&); };
void f(A);
int main() { f({"hello"}); }
我们可以将字符串文字转换为std::string
,即使这需要用户定义的转换。但是,它指出了另一段的限制。说什么13.3.3.1
?
13.3.3.1/4
,这是负责禁止多个用户定义的转换的段落。我们只会看列表初始化:
但是,当考虑用户定义的转换函数 [(或构造函数)] 的参数时,它是 [...] 13.3.1.7 的候选者,当将初始值设定项列表作为单个参数传递或初始值设定项列表只有一个元素时并且对某个类 X 的转换或对(可能是 cv 限定的)X 的引用被认为是 X 的构造函数的第一个参数,或者 [...],只允许标准转换序列和省略号转换序列。
请注意,这是一个重要的限制:如果不是这样,上面可以使用复制构造函数来建立一个同样好的转换序列,并且初始化将是模棱两可的。(请注意该规则中“A 或 B 和 C”的潜在混淆:它的意思是“(A 或 B)和 C” - 所以我们仅在尝试通过具有参数的 X 的构造函数进行转换时受到限制类型X
)。
我们被委托13.3.1.7
收集可用于进行此转换的构造函数。让我们从8.5
委托我们的一般方面开始处理本段8.5.4
:
8.5.4/1
:
列表初始化可以发生在直接初始化或复制初始化上下文中;直接初始化上下文中的列表初始化称为直接列表初始化,复制初始化上下文中的列表初始化称为复制列表初始化。
8.5.4/2
:
一个构造函数是一个初始化列表构造函数,如果它的第一个参数是类型std::initializer_list<E>
或引用可能为某些类型 E 的 cv 限定的std::initializer_list<E>
,并且要么没有其他参数,要么所有其他参数都有默认参数(8.3.6)。
8.5.4/3
:
T 类型的对象或引用的列表初始化定义如下: [...] 否则,如果 T 是类类型,则考虑构造函数。如果 T 有一个初始化列表构造函数,则参数列表由初始化列表作为单个参数组成;否则,参数列表由初始化列表的元素组成。枚举了适用的构造函数(13.3.1.7),并通过重载决议(13.3)选择最佳构造函数。
此时,T
是类类型std::vector<std::string>
。我们有一个参数(它还没有类型!我们只是在有一个语法初始化列表的上下文中)。构造函数枚举为13.3.1.7
:
[...] 如果 T 有一个初始化列表构造函数(8.5.4),则参数列表由初始化列表作为单个参数组成;否则,参数列表由初始化列表的元素组成。对于复制列表初始化,候选函数是 T 的所有构造函数。但是,如果选择显式构造函数,则初始化格式错误。
我们只会将初始化列表std::vector
视为唯一的候选者,因为我们已经知道其他人不会战胜它或不适合这个论点。它具有以下签名:
vector(initializer_list<std::string>, const Allocator& = Allocator());
现在,将初始化列表转换为std::initializer_list<T>
(以对参数/参数转换的成本进行分类)的规则列举在13.3.3.1.5
:
当参数是初始值设定项列表 (8.5.4) 时,它不是表达式,并且适用特殊规则将其转换为参数类型。[...] 如果参数类型是std::initializer_list<X>
并且初始化列表的所有元素都可以隐式转换为 X,则隐式转换序列是将列表中的元素转换为 X 所需的最差转换。这种转换可以是用户-定义的转换,即使在调用初始化列表构造函数的上下文中也是如此。
现在,初始化列表将被成功转换,并且转换顺序是用户定义的转换(从char const[N]
到std::string
)。再次详细说明这是如何制作的8.5.4
:
否则,如果 T 是 的特std::initializer_list<E>
化,则按如下所述构造一个 initializer_list 对象,并用于根据从相同类型的类 (8.5) 中初始化对象的规则来初始化该对象。(...)
看看8.5.4/4
这最后一步是如何完成的:)