14

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&是唯一的参数,所以只考虑从隐含对象参数转换为隐含对象参数的转换序列 - 忽略转换函数产生的类型。在这两种情况下,隐含的对象参数是初始化表达式的 type 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 中并产生类型 T 或可以通过标准转换序列 (13.3.3.1.1) 转换为类型 T 的非显式转换函数是候选函数。对于直接初始化,那些不隐藏在 S 中并产生类型 T 的显式转换函数或可以通过限定转换 (4.4) 转换为类型 T 的类型也是候选函数。对于这个选择候选函数的过程,返回 cv 限定类型的转换函数被认为会产生该类型的 cv 非限定版本。返回“对 cv2 X 的引用”的转换函数返回左值或 x 值,具体取决于引用的类型,类型为“cv2 X”,因此在选择候选函数的过程中被认为产生 X。

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

根据标准,这是否模棱两可?

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

不同之处在于,在我的示例中,两个转换函数具有相同的资格。

4

1 回答 1

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是类类型,候选函数选择如下:

  • 考虑了 S 及其基类的转换函数。那些不隐藏在 S 中并产生类型 T 或可以通过标准转换序列 (13.3.3.1.1) 转换为类型 T 的非显式转换函数是候选函数。对于直接初始化,那些不隐藏在 S 中并产生类型 T 的显式转换函数或可以通过限定转换 (4.4) 转换为类型 T 的类型也是候选函数。对于这个选择候选函数的过程,返回 cv 限定类型的转换函数被认为会产生该类型的 cv 非限定版本。返回“对 cv2 X 的引用”的转换函数返回左值或 x 值,具体取决于引用的类型,

请注意,两者A::operator A*()A::operator B*()都是候选函数,因为A*B*都可以转换为void*每个 4.10p2:“类型为“指向 cv 的指针”的纯右值T,其中T是对象类型,可以转换为类型为“指向 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被定义为比另一个可行函数更好F2的函数,如果对于所有参数iICSi(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*toA*的转换优于to 的转换, B*to的void*转换优于A*to的转换。void*B*void*

这正是 OP 的情况,除了名称AB颠倒。OP 的两个转换运算符的重载解决方案有利于解决,A::operator B*()因为引用的规则使转换序列B*void*优于A*void*

于 2013-08-06T21:31:07.687 回答