GCC 有一个错误。该标准使这一点有效。见:
注意这有两个方面
- 一般来说,初始化是如何进行的?
- 在重载解析期间如何使用初始化,它的成本是多少?
第一个问题在8.5 部分回答。第二个问题在13.3 部分回答。例如,引用绑定在8.5.3 和13.3.3.1.4 处理,而列表初始化在8.5.4 和13.3.3.1.5 处理。
8.5/14,16:
表单中发生的初始化
T x = a;
以及在参数传递中、函数返回、抛出异常 (15.1)、处理异常 (15.3) 和聚合成员初始化 (8.5.1) 称为复制初始化。
.
.
初始化器的语义如下[...]:如果初始化器是花括号初始化列表,则对象是列表初始化的(8.5.4)。
在考虑候选function 时,编译器将看到一个初始化列表(它还没有类型——它只是一个语法结构!)作为参数,std::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.4 的8.5 开始从一般方面来处理这一段:
8.5.4/1:
列表初始化可以发生在直接初始化或复制初始化上下文中;直接初始化上下文中的列表初始化称为direct-list-initialization,复制初始化上下文中的列表初始化称为copy-list-initialization。
8.5.4/2:
一个构造函数是一个 initializer-list 构造函数,如果它的第一个参数是 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 这最后一步是如何完成的 :)