使用initializer_list进行不明确的重载决策

时间:2015-07-30 15:59:16

标签: c++ visual-studio c++11

我在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中看到了相同的行为,所以可能还有更多呢?

2 个答案:

答案 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}}是浮点转换 - 也具有转换排名。

enter image description here

因此两次转换都同样好,并且因为§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)。由于从doubleintfloat的转化都具有转换排名,因此两者的匹配都不是更好。所以这个电话很模糊。请注意,即使有候选人具有精确等级(A(double )),我们也会先考虑initializer_list构造函数