【问题标题】:Ambiguous overload resolution with initializer_list带有 initializer_list 的模棱两可的重载解决方案
【发布时间】:2015-07-30 15:59:16
【问题描述】:

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

【问题讨论】:

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


【解决方案1】:

代码格式不正确。 §8.5.4/(3.6) 适用:

否则,如果T 是类类型,则考虑构造函数。这 枚举适用的构造函数并选择最好的构造函数 通过重载决议(13.3、13.3.1.7)。

现在,§13.3.3.1.5 开始

当参数是初始值设定项列表 (8.5.4) 时,它不是表达式,并且特殊规则适用于转换 它到一个参数类型。 […] 如果参数类型是std::initializer_list&lt;X&gt; 并且所有 初始化列表的元素可以隐式转换为X隐式转换序列是最差的转换序列 将列表的元素转换为X,或者如果初始化列表没有 元素,身份转换。

1.1(类型为double (!))转换为int 是具有转换等级的浮点整数转换,而从1.1 转换为float 是浮点转换-也有转化排名。

因此,两种转换都一样好,并且由于 §13.3.3.2/(3.1) 也无法区分它们,因此调用是模棱两可的。请注意,在完成重载解决之前,缩小不会发挥作用,因此不会影响候选集或选择过程。更准确地说,候选人必须满足 13.3.2/3 中设定的要求:

其次,要使F 成为可行的功能,每个功能都应存在 参数一个隐式转换序列(13.3.3.1),它转换 F 的相应参数的那个参数。

但是,如第二个引用所示,将{1.1} 转换为std::initializer_list&lt;int&gt; 的隐式转换序列是从1.1int 的最差转换,这是一个浮点整数转换 - 并且是一个有效的 (和现有的!)一个。


相反,如果您传递{1.1f} 或将initializer_list&lt;float&gt; 更改为&lt;double&gt;,则代码格式正确,因为将1.1f 转换为float 是一种身份转换。标准在(3.6)中给出了相应的例子:

[例子

struct S {
    S(std::initializer_list<double>); // #1
    S(std::initializer_list<int>);    // #2

};
S s1 = { 1.0, 2.0, 3.0 }; // invoke #1

结束示例 ]

更有趣的是,

struct S {
    S(std::initializer_list<double>); // #1
    S(std::initializer_list<int>);    // #2

};
S s1 = { 1.f }; // invoke #1 

Is also valid - 因为从1.fdouble 的转换是浮点促销,具有提升等级,优于转化等级。

【讨论】:

  • 似乎 list-init 序列不是 ICS [over.best.ics]p3。我很困惑。我们是否应用 [over.ics.list]p4 来发现存在 ICS?
  • 嗯,很明显,我们必须查看 [over.ics.list] 才能继续 :) 但是,我不太清楚 [over.match.viable] 的步骤到 [over.ics.list]。后者几乎似乎是事后才想到的。
  • @dyp 很抱歉这个无意义的评论。是的,我想这种联系并没有想象中那么清晰。一个 DR 是合适的(我可以找到一个对应的,但是)。
【解决方案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 的转换都具有转换排名。所以这个称呼是模棱两可的。请注意,即使存在具有 Exact Rank (A(double )) 的候选者,我们也会考虑 initializer_list 构造函数first

【讨论】:

  • 但是(1)不是一个可行的函数,因为需要缩小转换。为什么这是一个硬错误?
  • @dyp 虽然它被视为一个可行的函数(可能的缺陷?)在[over.ics.list]中,有“否则,如果参数类型是std::initializer_list&lt;X&gt;并且所有元素初始化列表可以隐式转换为X,隐式转换序列是将列表元素转换为 X 所需的最差转换,或者如果初始化列表没有元素,则进行恒等转换。”没有提到缩小,double可以隐式转换为int
  • 哇。我被以下措辞欺骗了:“当参数是初始化列表时”,但我读到“当参数是initializer_list”。但是,我认为参数类型是not initializer_list&lt;..&gt;,它是一个引用,所以我们需要考虑[over.ics.ref]而不是[over.ics.list](也许我们'第二步会到达 [over.ics.list] 吗?)
  • 另外,我(还)不相信 [over.ics.list] 定义了转换的格式良好。它似乎指定了排名的ICS。 [over.match.list] 定义了 candidates,据我所知,[over.match.viable] 应该删除不可行的initializer_list&lt;int&gt; 候选人。
  • @dyp 我认为缩小转换范围仍然是可行的转换。我没有看到任何建议 struct A { A(int ); A(float ); }; A{1.1}; 不应该模棱两可。
猜你喜欢
  • 1970-01-01
  • 2012-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多