clang和gcc都接受以下代码并选择A::operator B*
。
struct B
{
};
struct A : B
{
operator A*();
operator B*();
};
A a;
void* x = a;
我对标准的阅读 - 特别是下面用粗体突出显示的句子 - 表明这种转换应该是模棱两可的。
A::operator A*
和A::operator B*
都是重载解析的候选者,因为A*
和B*
都可以通过标准转换转换为void*
。因为隐含对象参数A&
是唯一参数,所以只考虑从隐含对象参数转换为隐含对象参数的转换序列 - 忽略转换函数产生的类型。在这两种情况下,隐含对象参数都是初始化表达式的类型A
,隐含对象参数是A&
。如果两个转换序列相同,则无法区分两个转换序列。
8.5初始值设定项[dcl.init]
初始化器的语义如下。 目标类型是对象或引用的类型 初始化,源类型是初始化表达式的类型。
- 如果目的地类型是[参考/数组/类 ...] [已删除的详细信息不适用于此方案]
- 否则,如果源类型是(可能是cv限定的)类类型,则考虑转换函数。 枚举适用的转换函数(13.3.1.5),并通过重载选择最佳函数 决议(13.3)。调用所选的用户定义转换以转换初始化程序 表达到正在初始化的对象中。 如果转换无法完成或模糊不清,那么 初始化是不正确的。
13.3.1.5通过转换函数初始化[over.match.conv]
在8.5中指定的条件下,作为非类型对象的初始化的一部分,进行转换 可以调用函数将类类型的初始化表达式转换为对象的类型 初始化。 重载分辨率用于选择要调用的转换函数。假设“cv1 T“是被初始化的对象的类型,”cv S“是初始化表达式的类型,S a 类类型,候选函数选择如下:
- 考虑S及其基类的转换函数。 那些非显式转化 未隐藏在S和yield类型T中的函数或可转换为类型T的类型 通过标准转换序列(13.3.3.1.1)是候选函数。对于直接初始化,那些 显式转换函数,它们不在S中隐藏,并且产生类型T或可以是的类型 转换为具有资格转换(4.4)的类型T也是候选函数。转变 返回cv限定类型的函数被视为产生该类型的cv-nonqualified版本 这个选择候选函数的过程。转换函数返回“对cv2的引用 X“返回左值或x值,取决于引用的类型,类型为”cv2 X“,因此 考虑为这个选择候选函数的过程产生X.
参数列表有一个参数,它是初始化表达式。 [注意:这个参数将是 与转换函数的隐式对象参数进行比较。 - 后注]
根据标准这是不明确的?
编辑:请注意,这是一个类似的问题,但与Distinguishing between user-defined conversion sequences by the initial standard conversion sequence
不同不同之处在于,在我的示例中,两个转换函数具有相同的限定条件。
答案 0 :(得分:9)
TLDR:当其他条件相同时,重载决策会破坏转换函数从其返回值到目标类型的最佳转换的平衡。
所有参考文献均为ISO / IEC 14882:2011(C ++ 11)。初始化的行为:
void* x = a;
定义如下。首先,这个是初始化,如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 a 类类型,候选函数选择如下:
- 考虑S及其基类的转换函数。那些非显式转换 未隐藏在S和yield类型T中的函数或可转换为类型T的类型 通过标准转换序列(13.3.3.1.1)是候选函数。对于直接初始化,那些 显式转换函数,它们不在S中隐藏,并且产生类型T或可以是的类型 转换为具有资格转换(4.4)的类型T也是候选函数。转变 返回cv限定类型的函数被视为产生该类型的cv-nonqualified版本 这个选择候选函数的过程。转换函数返回“对cv2的引用 X“返回左值或x值,取决于引用的类型,类型为”cv2 X“,因此 考虑为这个选择候选函数的过程产生X.
请注意,A::operator A*()
和A::operator B*()
都是候选函数,因为A*
和B*
都可转换为void*
每个
4.10p2:"类型为“指向cv T
的指针”的prvalue,其中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)。
要求见第2页:
首先,要成为一个可行的函数,候选函数应具有足够的参数来与数字一致 列表中的参数。
和p3:
其次,要使
F
成为可行的函数,每个参数都应存在隐式转换序列 (13.3.3.1)将该参数转换为F
的相应参数。
我们的转换函数可以轻松满足这两个要求:它们具有与初始化表达式a
相同类型的单个(隐式)参数。
13.3.3 [over.match.best]中描述了可行功能的最佳的确定。它定义了一些描述转换序列的形式,这是将实际函数参数的类型转换为形式函数参数类型所必需的操作序列。在我们的转换函数的情况下,它们都只有一个参数,其类型与实际参数的类型完全相同,因此"转换序列"对应于每个重载的是标识序列。它们受到p1中语言的歧视:
鉴于这些定义,如果所有参数
F1
,{{F2
,则可行函数i
被定义为更好函数而不是另一个可行函数ICSi(F1)
。 1}}不是比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*
向A*
的转换优于B*
到{{1}的转换将void*
转换为A*
比将void*
转换为B*
更好。
这正是OP的情况,除了名称void*
和A
被颠倒之外。由于引用的规则使转换序列B
→A::operator B*()
优于B*
→{{1},因此OP的两个转换运算符的重载分辨率得到解决,有利于void*
}}