当两个候选人具有相同的cv资格时,转换函数初始化是否应该是不明确的?

wil*_*llj 14 c++ standards overloading initialization language-lawyer

clang和gcc都接受以下代码并选择A::operator B*.

struct B
{
};

struct A : B
{
    operator A*();
    operator B*();
};

A a;
void* x = a;
Run Code Online (Sandbox Code Playgroud)

我对标准的阅读 - 特别是下面用粗体突出显示的句子 - 表明这种转换应该是模棱两可的.

这两个A::operator A*A::operator B*是重载,因为候选人A*B*都转换为void*通过一个标准的转换.因为隐含的对象参数A&是唯一的参数,所以只考虑从隐含的对象参数转换为隐含的对象参数的转换序列 - 忽略转换函数产生的类型.在这两种情况下,隐含的对象参数都是初始化表达式的类型A,而隐含的对象参数是A&.如果两个转换序列相同,则无法区分两个候选者.

8.5初始值设定项[dcl.init]

初始化器的语义如下.的目标类型是对象或引用的类型被初始化和源类型是初始化表达式的类型.

- 如果目标类型是[ reference/array/class ...] [已删除的详细信息不适用于此方案]

- 否则,如果源类型是(可能是cv限定的)类类型,则考虑转换函数. 列举了适用的转换函数(13.3.1.5),并通过重载决策(13.3)选择最佳函数.调用如此选择的用户定义转换以将初始化表达式转换为正在初始化的对象.如果转换不能完成或不明确,则初始化是错误的.

13.3.1.5通过转换函数初始化[over.match.conv]

在8.5中指定的条件下,作为非类型对象初始化的一部分,可以调用转换函数将类类型的初始化表达式转换为要初始化的对象的类型.重载分辨率用于选择要调用的转换函数.假设"cv1 T"是要初始化的对象的类型,并且"cv S"是初始化表达式的类型,其中S是类类型,候选函数选择如下:

- 考虑S及其基类的转换函数.那些未隐藏在S和yield类型T中的非显式转换函数或可通过标准转换序列(13.3.3.1.1)转换为类型T的类型是候选函数.对于直接初始化,那些未隐藏在S和yield类型T中的显式转换函数或者可以通过限定转换(4.4)转换为类型T的类型也是候选函数.返回cv限定类型的转换函数被认为是为此选择候选函数的过程产生该类型的cv非限定版本.返回"引用cv2 X"的转换函数返回lvalues或xvalues,具体取决于引用类型,类型为"cv2 X",因此被认为是为此选择候选函数的过程产生X.

参数列表有一个参数,它是初始化表达式.[注意:此参数将与转换函数的隐式对象参数进行比较. - 尾注]

根据标准这是不明确的?

编辑:请注意,这是一个类似的问题,但与通过初始标准转换序列区分用户定义的转换序列不同

不同之处在于,在我的示例中,两个转换函数具有相同的限定条件.

Cas*_*sey 9

TLDR:当其他条件相同时,重载决策会破坏转换函数从其返回值到目标类型的最佳转换的平衡.


所有参考文献均参考ISO/IEC 14882:2011(C++ 11).初始化的行为:

void* x = a;
Run Code Online (Sandbox Code Playgroud)

定义如下.首先,这 8.5 Initializers [dcl.init]中描述的初始化,并且符合p1中描述的语法.由于目标类型void*是非类类型,并且源类型A 类类型,因此此特定初始化程序的格式为8.5 p16,bullet 7中所述:

否则,如果源类型是(可能是cv限定的)类类型,则考虑转换函数.列举了适用的转换函数(13.3.1.5),并通过重载决策(13.3)选择最佳函数.调用如此选择的用户定义转换以将初始化表达式转换为正在初始化的对象.如果转换不能完成或不明确,则初始化是错误的.

"适用转换函数的枚举"详见13.3.1.5 p1:

在8.5中指定的条件下,作为非类型对象初始化的一部分,可以调用转换函数将类类型的初始化表达式转换为要初始化的对象的类型.重载分辨率用于选择要调用的转换函数.假设"cv1 T"是要初始化的对象的类型,并且"cv S"是初始化表达式的类型,其中S是类类型,候选函数选择如下:

  • 考虑S及其基类的转换函数.那些未隐藏在S和yield类型T中的非显式转换函数或可通过标准转换序列(13.3.3.1.1)转换为类型T的类型是候选函数.对于直接初始化,那些未隐藏在S和yield类型T中的显式转换函数或者可以通过限定转换(4.4)转换为类型T的类型也是候选函数.返回cv限定类型的转换函数被认为是为此选择候选函数的过程产生该类型的cv非限定版本.返回"引用cv2 X"的转换函数返回lvalues或xvalues,具体取决于引用类型,类型为"cv2 X",因此被认为是为此选择候选函数的过程产生X.

注意,这两个A::operator A*()A::operator B*()是候选的功能,因为两个A*B*可以转化为void*每4.10p2:"类型的prvalue'指向cv T’,其中T是对象类型,可以被转换成类型的prvalue'指向cv void’." 鉴于两个函数都是候选函数,重载解析必须在它们之间进行选择

过载分辨率在13.3 [over.match]中描述.p2表示:

重载决策选择要在语言中的七个不同上下文中调用的函数:

...

  • 调用转换函数,用于从类类型的表达式初始化非类类型的对象(13.3.1.5)

...

这些上下文中的每一个都以其自己独特的方式定义候选函数集和参数列表.但是,一旦确定了候选函数和参数列表,在所有情况下,最佳函数的选择都是相同的:

  • 首先,选择候选函数的子集(具有适当数量的参数并满足某些其他条件的函数)以形成一组可行函数(13.3.2).

  • 然后,基于将每个参数与每个可行函数的相应参数匹配所需的隐式转换序列(13.3.3.1)来选择最佳可行函数.

我们的两个功能中哪一个可行?13.3.2 [over.match.viable] p1:

从为给定上下文(13.3.1)构造的候选函数集合中,选择一组可行函数,通过比较最佳拟合的参数转换序列(13.3.3),从中选择最佳函数.

要求见p2:

首先,要成为一个可行的函数,候选函数应具有足够的参数以在数量上与列表中的参数一致.

和p3:

第二,为了F成为一个可行的函数,每个参数都应该存在一个隐式转换序列(13.3.3.1),它将该参数转换为相应的参数F.

我们的转换函数很容易满足这两个要求:它们具有与初始化表达式相同类型的单个(隐式)参数a.

确定最佳可行功能的方法见13.3.3 [over.match.best].它定义了一些形式,用于描述转换序列,从实际函数参数的类型转换为形式函数参数的类型所需的操作序列.在我们的转换函数的情况下,它们都只有一个参数,其类型与实际参数的类型完全相同,因此对应于每个重载的"转换序列"是标识序列.他们受到p1语言的歧视:

鉴于这些定义,如果对于所有参数,可行函数F1被定义为比另一个可行函数更好的函数,并不是比转换序列更差的转换序列,然后F2iICSi(F1)ICSi(F2)

  • 对于某些论证j,ICSj(F1)是一个更好的转换序列ICSj(F2),或者,如果不是,

  • 上下文是由用户定义的转换初始化(见8.5,13.3.1.5和13.3.1.6),从返回类型F1到目标类型的标准转换序列(即,被初始化的实体的类型)是转换序列比从返回类型F2到目标类型的标准转换序列更好.

最后一颗子弹怎么样?我们的一个重载是否具有从其返回类型到更好的标准转换序列void*

13.3.3.2对第二个要点中的隐式转换序列[over.ics.rank] p4状态进行排序:

如果类B直接或间接地从类派生A,则转换为B*to A*比转换为B*to 更好void*,并且转换为A*to void*比转换为B*to 更好void*.

这正是OP的情况,除了名称AB反转.A::operator B*()由于引用的规则使转换序列B*void*优于A*→,因此解决了OP的两个转换运算符的过载分辨率void*