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

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

本文关键字:多少 转换 列表 初始化 复制      更新时间:2023-10-16
#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。prvalue通过复制列表初始化或直接列表初始化来初始化其结果对象,具体取决于引用的初始化类型。然后使用prvalue直接初始化引用。[注意:与往常一样,如果引用类型是对非常量类型的左值引用,则绑定将失败,并且程序格式错误。—尾注]

所以它等于const B& = {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} -> 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

你的困惑源于混淆了两个超负荷的解决方案。

答案在这里,因为参数是初始值设定项列表,所以在重载解析时执行[over.ics.list]规则。

[over.ics.list]/6

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

因此,对于这三个candicate构造函数,它们的参数都是非aggerate类类型,因此隐式转换序列用于将初始化器列表的单个元素转换为构造函数CCD_ 25的参数的构造函数的相应参数类型,因此这些转换序列都是用户定义的转换序列。所以它们的等级是不可区分的。