【问题标题】:Should initialization by conversion function be ambiguous when two candidates have the same cv-qualification?当两个候选人具有相同的 cv-qualification 时,转换函数的初始化是否应该模棱两可?
【发布时间】:2013-08-08 00:55:47
【问题描述】:

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]

初始化器的语义如下。 目标类型是对象或引用的类型 已初始化,源类型是初始化表达式的类型。

——如果目标类型是 [reference/array/class...] [删除的细节不适用于此场景]

— 否则,如果源类型是(可能是 cv 限定的)类类型,则考虑转换函数。 列举了适用的转换函数(13.3.1.5),通过重载选择最佳的 决议 (13.3)。调用如此选择的用户定义的转换来转换初始化器 表达式到正在初始化的对象中。 如果转换无法完成或不明确,则 初始化格式不正确。

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

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

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

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

按照标准这是模棱两可的吗?

编辑:注意这是一个类似的问题,但与Distinguishing between user-defined conversion sequences by the initial standard conversion sequence不同

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

【问题讨论】:

  • 两个转换函数都返回一个指向实例的相同基地址的指针,这是转换为void* 导致的。尝试多重继承和额外的转换运算符,转换应该是模棱两可的。
  • @CaptainObvlious 你怎么知道他们返回了什么? They do not even need to be implemented to test this question.
  • @Casey 因为转换运算符有一个返回类型来指定它们返回的内容。
  • @CaptainObvlious 知道返回类型不允许人们推断它们“返回指向实例相同基地址的指针”。

标签: c++ initialization standards overloading language-lawyer


【解决方案1】:

TLDR:当其他一切都相等时,重载决议打破了转换函数从其返回值到目标类型的最佳转换的关系。


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

void* x = a;

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

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

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

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

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

注意A::operator A*()A::operator B*() 都是候选函数,因为A*B* 都可以转换为void* per 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 中的语言区分:

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

  • 对于某些参数jICSj(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*转换为void*,以及将A*转换为@987654353 @ 比将B* 转换为void* 更好。

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

【讨论】:

  • 这是指在重载决议中从参数类型到候选函数的参数类型的转换序列的排名。在这种情况下,A*B* 都不是参数类型,void* 不是参数类型。因此,根据我对标准的阅读,没有理由比较这些转换序列。
  • @willj 这就是我没有展示我的作品的结果。这就是你的全部论点。 TLDR:当其他一切都相同时,重载决议打破了转换函数从其返回值到目标类型的最佳转换的关系。
  • 我对这里的 C++ 标准有点困惑。如果 B 是从 A 派生的,那么我会认为将 B* 转换为 void* 比将 A* 转换为 void* 更好。毕竟,B 是比 A 更具体的类型。按原样,标准基本上是在进入空白之前尽可能地“降级”相关对象。
  • 当您将任何指针转换为void* 时,无论您最初拥有哪个指针,您本质上都在降级它,因为void* 不知道它指向的类型。 IMO 的推理如下:void* 可以指向任何东西,A* 可以指向 A 和 B 类型的对象,B* 只能指向 B 类型的对象。
猜你喜欢
  • 2021-08-25
  • 2015-11-19
  • 1970-01-01
  • 1970-01-01
  • 2018-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多