【问题标题】:What's the rank of implicitly conversion for copy-list-initialization复制列表初始化的隐式转换等级是多少
【发布时间】:2020-02-26 03:18:48
【问题描述】:
#include <iostream>
struct A{
  A(int){

  }
};
struct B{
  B() = default;
  B(A){

  }
  B(B const&){}
  B(B&&){}
};

int main(){
  B b({0});
}

对于给定的代码,候选函数是:

 #1  B::B(A)   
 #2  B::B(const B&)  
 #3  B::B(B&&)  

根据标准,对于#1,类型A的对象被{0}复制列表初始化为A a = {0}A::A(int)被考虑用于初始化,所以只有#1内的标准转换。对于#2,它是引用表单braced-init-list 的初始化,这是[dcl.init.list] 的原因

否则,如果 T 是引用类型,则生成 T 所引用类型的纯右值。 prvalue 通过复制列表初始化或直接列表初始化来初始化其结果对象,具体取决于引用的初始化类型。然后使用纯右值直接初始化引用。 [ 注意:与往常一样,如果引用类型是对非 const 类型的左值引用,则绑定将失败并且程序格式错误。 — 尾注 ]

所以等于const B&amp; = {0},在这个初始化中,转换函数是B::B(A),参数是0,所以B tmp = {0}和'B::B(A)'被认为是参数被初始化通过参数0,作为A parameter = 0

否则(即,对于剩余的复制初始化情况),用户定义的转换序列可以从源类型转换为目标类型或(当使用转换函数时)转换为其派生类如[over.match.copy]中所述进行枚举,并通过重载决议选择最佳的...

所以#2里面有一个用户自定义的转换,#3的情况和#2的情况一样,根据[over.ics.rank]

标准转换序列是比用户定义转换序列或省略号转换序列更好的转换序列,并且...

标准转换比自定义转换好,所以#1应该比#2和#3好,但实际上,g++报告调用不明确,为什么?错误信息是:

main.cpp: In function ‘int main()’:
main.cpp:12:10: error: call of overloaded ‘B(<brace-enclosed initializer list>)’ is ambiguous
   B b({0});
          ^
main.cpp:8:3: note: candidate: B::B(A)
   B(A){
   ^
main.cpp:6:8: note: candidate: constexpr B::B(const B&)
 struct B{
        ^
main.cpp:6:8: note: candidate: constexpr B::B(B&&)

【问题讨论】:

  • {0} -&gt; A{0} -&gt; const B&amp;{0} -&gt; B&amp;&amp;这三个转化都是用户自定义的转化。为什么你认为{0} -&gt; A是一个标准的转换序列?
  • @xskxzr 因为标准[dcl.init.list],构造函数被认为是直接初始化reuslt对象,隐蔽0int是A::A(整数)
  • @xskxzr 或者换句话说,你认为A a{0} 是转换吗?

标签: c++ language-lawyer overload-resolution


【解决方案1】:

{0} -&gt; A{0} -&gt; const B&amp;{0} -&gt; B&amp;&amp; 这三个转换都是用户定义的转换。

要将{0} 转换为A另一个重载决议发生,这一次你面对三个构造函数A(int)A(const A&amp;)A(A&amp;&amp;)。由于0 -&gt; int 是标准转换,而0 -&gt; const A&amp;0 -&gt; A&amp;&amp; 都是用户定义的转换,所以转换0 -&gt; int 获胜并选择A(int){0} 转换为A

您的困惑来自于混合两种重载解决方案。

【讨论】:

  • 正如我上面的评论所说,你认为A a{0}是一种转换吗,我认为不是,而且在我的理解中,简单来说,copy-list-initialization基本上和直接列表初始化,除了考虑构造函数是用显式声明的,或者如果你能找到关于复制列表初始化的标准,说 {0} -> A 是一个转换,你会在这里传递报价
  • 对于{0} -> A 是一个转换,我可以举个例子来证明它不是,比如struct Data { Data(int) { } }; struct A { A(Data) { } };int main() { A a = { 0 }; } 如你所说如果{0}->A 是一个用户-定义转换,那么 0 -> 数据也是一个自定义转换,标准中说在一个转换序列中,只有一个自定义转换,显然,{0} -> A 没有自定义转换
  • @jackX 没看懂,通过初始化自然形成一个转换序列。你在找[over.best.ics]/1吗?
  • 是的,a conversion sequence is naturally formed through initialization,那你如何解释我在评论中提到的情况,正如你所说,你认为{0} - &gt; A是用户定义的转换,那么0-&gt;Data是另一个,所以转换序列中有两个用户定义的转换(格式错误)
  • @jackX 用{0}初始化A,构造函数A(Data)被选中,用来完成转换,只有一个自定义转换。由于调用A(Data)(以及随后的重载决议),转换0 -&gt; Data 是一个嵌套转换。当一个嵌套在另一个中时,连接两个转换是没有意义的。
【解决方案2】:

答案在这里,因为参数是initializer list,所以重载解析时会执行[over.ics.list]规则。

[over.ics.list]/6

  1. 否则,如果参数是 非聚合类 X 并且每个 [over.match.list] 的重载解析选择 X 的单个最佳构造函数 C 来执行类型对象的初始化X 来自参数初始值设定项列表:
    • 如果 C 不是初始值设定项列表构造函数并且初始值设定项列表具有 cv U 类型的单个元素,其中 U 是 X 或从 X 派生的类,如果 U 是 X,则隐式转换序列具有精确匹配等级,或者如果 U 从 X 派生,则转换排名。
    • 否则,隐式转换序列是用户定义的转换序列,第二个标准转换序列是恒等转换。

因此,对于这三个候选构造函数,它们的参数都是非聚合类类型,所以使用隐式转换序列将初始值设定项列表的单个元素转换为构造函数参数的构造函数对应的参数类型B,因此这些转换序列都是用户定义的转换序列,第二个标准转换序列都是恒等转换。所以它们的等级是无法区分的。 p>

【讨论】:

    猜你喜欢
    • 2016-04-27
    • 1970-01-01
    • 2015-01-12
    • 1970-01-01
    • 2012-06-29
    • 2011-10-12
    • 1970-01-01
    • 1970-01-01
    • 2020-01-02
    相关资源
    最近更新 更多