我在Visual Studio上测试了以下代码,它编译并打印“A(double)”。
#include <iostream>
#include <initializer_list>
struct A {
A(std::initializer_list<int>) { puts("initializer_list<int>"); } // (1)
A(std::initializer_list<float>) { puts("initializer_list<float>"); } // (2)
A(double) { puts("A(double)"); } // (3)
};
int main() {
A var{ 1.1 };
}
然而IntelliSense和http://ideone.com/ZS1Mcm都不同意,说构造函数“A :: A”的多个实例与参数列表匹配(意味着两个初始化列表构造函数)。请注意,如果删除了(1)或(2),则代码不再编译,因为“从'double'转换为'float'需要缩小转换”。
这是一个错误吗?行为感觉不一致,但我在VS13和VS15中看到了相同的行为,所以可能还有更多呢?
答案 0 :(得分:3)
代码格式不正确。 §8.5.4/(3.6)适用:
否则,如果
T
是类类型,则考虑构造函数。该 列举了适用的构造函数,并选择最佳构造函数 通过重载决议(13.3,13.3.1.7)。
现在,§13.3.3.1.5进行了
当参数是初始化列表(8.5.4)时,它不是表达式,并且特殊规则适用于转换 它是一个参数类型。 [...] 如果参数类型为
std::initializer_list<X>
且全部 初始化列表的元素可以隐式转换为X
, 隐式转换序列是必需的最差转换 将列表元素转换为X
,或者初始化列表没有 元素,身份转换。
将类型为1.1
(!)的double
转换为int
是具有转化排名的浮动积分转换,而从1.1
转换为{ {1}}是浮点转换 - 也具有转换排名。
因此两次转换都同样好,并且因为§13.3.3.2/(3.1)也无法区分它们,所以调用是模糊的。请注意,在完成重载解析之前,缩小不起作用,因此不会影响候选集或选择过程。更准确地说,候选人必须符合13.3.2 / 3中规定的要求:
其次,要使
float
成为可行的函数,每个都应该存在 参数隐式转换序列(13.3.3.1)转换F
的相应参数的参数。
但是,如第二个引用所示,将F
转换为{1.1}
的隐式转换序列是从std::initializer_list<int>
到1.1
的最差转换,这是一个浮点数 - 整体转换 - 以及有效(和现有!)的转换。
<小时/> 如果您通过
int
或将{1.1f}
更改为initializer_list<float>
,则代码格式正确,因为将<double>
转换为1.1f
是身份转换。该标准给出了(3.6)中的相应示例:
[示例:
float
- 结束示例]
更有趣的是,
struct S {
S(std::initializer_list<double>); // #1
S(std::initializer_list<int>); // #2
};
S s1 = { 1.0, 2.0, 3.0 }; // invoke #1
Is also valid - 因为从struct S {
S(std::initializer_list<double>); // #1
S(std::initializer_list<int>); // #2
};
S s1 = { 1.f }; // invoke #1
到1.f
的转换是浮点促销,具有促销排名,优于转化排名。
答案 1 :(得分:2)
Visual Studio接受你的代码是错误的 - 它是不正确的,应该无法编译。 gcc和clang在拒绝它时是正确的。
列表初始化中的关键要点是:
否则,如果T是类类型,则考虑构造函数。列举了适用的构造函数 通过重载决策选择最好的一个(13.3,13.3.1.7)。如果缩小转换(见 如果需要转换任何参数,程序就是格式错误。
我们参考[over.match.list]:
当非聚合类类型T的对象被列表初始化时,8.5.4指定重载决策 根据本节中的规则执行,重载决策分两个阶段选择构造函数:
- 最初,候选函数是类T的初始化列表构造函数(8.5.4)和 参数列表由初始化列表作为单个参数组成。
我们有两个这样的候选函数 - (1)
和(2)
。由于从double
到int
和float
的转化都具有转换排名,因此两者的匹配都不是更好。所以这个电话很模糊。请注意,即使有候选人具有精确等级(A(double )
),我们也会先考虑initializer_list
构造函数 。