考虑代码
#include <iostream>
class Foo
{
int val_;
public:
Foo(std::initializer_list<Foo> il)
{
std::cout << "initializer_list ctor" << std::endl;
}
/* explicit */ Foo(int val): val_(val)
{
std::cout << "ctor" << std::endl;
};
};
int main(int argc, char const *argv[])
{
// why is the initializer_list ctor invoked?
Foo foo {10};
}
输出
ctor
initializer_list ctor
据我所知,值10
被隐式转换为Foo
(第一个ctor
输出),然后初始化构造函数启动(第二个initializer_list ctor
输出)。我的问题是为什么会发生这种情况?标准构造函数Foo(int)
不是更好的匹配吗?即,我希望此代码段的输出只是ctor
。
PS:如果我将构造函数Foo(int)
标记为explicit
,则Foo(int)
是唯一调用的构造函数,因为整数10
现在无法隐式转换为{ {1}}。
答案 0 :(得分:24)
§13.3.1.7[over.match.list] / p1:
当非聚合类类型
T
的对象被列表初始化时 (8.5.4),重载决策分两个阶段选择构造函数:
- 最初,候选函数是类
T
的初始化列表构造函数(8.5.4),参数列表包含 初始化列表作为单个参数。- 如果找不到可行的初始化列表构造函数,则再次执行重载解析,其中候选函数都是 类
T
的构造函数和参数列表包含 初始化列表的元素。如果初始化列表没有元素,
T
有默认值 构造函数,第一阶段被省略。在复制列表初始化中, 如果选择explicit
构造函数,则初始化为 不良形成。
只要有一个可行的初始化列表构造函数,当使用列表初始化并且初始化列表至少有一个元素时,它将胜过所有非初始化列表构造函数。
答案 1 :(得分:14)
初始化列表的n2100提议非常详细地说明了使序列构造函数(他们称之为std::initializer_lists
的构造函数)优先于常规构造函数的决策。有关详细讨论,请参阅附录B.结论中简明扼要地总结了这一点:
11.4结论那么,我们如何决定剩下的两个选择(“模糊”和“序列构造器”优先 超过普通的建设者)?我们的建议给出了序列构建器 优先,因为
- 寻找所有建设者之间的含糊不清导致太多“误报”;也就是说,两者之间的冲突显然是无关紧要的 构造函数。见下面的例子。
- 消歧本身容易出错(以及冗长)。参见§11.3中的示例。
- 对同类列表的每个元素使用完全相同的语法很重要 - 应该进行消歧 普通的构造函数(没有常规的模式 参数)。参见§11.3中的示例。虚假的最简单的例子 positive是默认构造函数:
误报的最简单示例是默认构造函数:
vector<int> v; vector<int> v { }; // potentially ambiguous void f(vector<int>&); // ... f({ }); // potentially ambiguous
可以想到没有初始化的类 成员在语义上与默认初始化不同,但我们 不会使语言复杂化以提供更好的支持 案例比它们在语义上更常见的情况 相同。
优先使用序列构造函数将参数检查分解为 更容易理解的块,并提供更好的局部性。
void f(const vector<double>&); // ... struct X { X(int); /* ... */ }; void f(X); // ... f(1); // call f(X); vector’s constructor is explicit f({1}); // potentially ambiguous: X or vector? f({1,2}); // potentially ambiguous: 1 or 2 elements of vector
在这里,优先考虑序列构造函数会消除 来自X的干扰。对于f(1)挑选X是该问题的变体 明确显示在§3.3中。
答案 2 :(得分:5)
整个初始化列表的东西是为了启用列表初始化,如下所示:
std::vector<int> v { 0, 1, 2 };
考虑案例
std::vector<int> v { 123 };
这会使用一个值为123的元素初始化向量,而不是123个值为零的元素。
要访问其他构造函数,请使用旧语法
Foo foo(10);