复制列表初始化的隐式转换等级是多少

What's the rank of implicitly conversion for copy-list-initialization

#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]

的原因

Otherwise, if T is a reference type, a prvalue of the type referenced by T is generated. The prvalue initializes its result object by copy-list-initialization or direct-list-initialization, depending on the kind of initialization for the reference. The prvalue is then used to direct-initialize the reference. [ Note: As usual, the binding will fail and the program is ill-formed if the reference type is an lvalue reference to a non-const type.  — end note ]

所以等价于const B& = {0},在本次初始化中,转换函数为B::B(A),参数为0,所以B tmp = {0}和'B::B(A)'被认为参数由参数 0 初始化,如 A parameter = 0.

Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in [over.match.copy], and the best one is chosen through overload resolution...

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

a standard conversion sequence is a better conversion sequence than a user-defined conversion sequence or an ellipsis conversion sequence, and...

标准转换优于用户自定义转换,所以#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} -> A{0} -> const B&{0} -> B&&这三个转换都是用户自定义的转换。

要将 {0} 转换为 A 会发生另一个 重载解析,这次您将面对三个构造函数 A(int)A(const A&)A(A&&)。由于 0 -> int 是标准转换,而 0 -> const A&0 -> A&& 都是用户自定义转换,因此转换 0 -> int 获胜,选择 A(int) 转换 {0}A.

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

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

[over.ics.list]/6

  1. Otherwise, if the parameter is a non-aggregate class X and overload resolution per [over.match.list] chooses a single best constructor C of X to perform the initialization of an object of type X from the argument initializer list:
    • If C is not an initializer-list constructor and the initializer list has a single element of type cv U, where U is X or a class derived from X, the implicit conversion sequence has Exact Match rank if U is X, or Conversion rank if U is derived from X.
    • Otherwise, the implicit conversion sequence is a user-defined conversion sequence with the second standard conversion sequence an identity conversion.

因此,对于这三个候选构造函数,其参数都是非聚合class类型,因此使用隐式转换序列将初始化列表的单个元素转换为相应的参数类型constructor 参数的constructor B 因此这些转换序列都是user-defined 转换序列 而第二个标准转换序列都是identity conversions.So 的rank它们难以区分