使用"operator T*()"而不是"T* operator->()"进行成员访问

Use "operator T*()" instead of "T* operator->()" for member access

本文关键字:成员 gt 访问 operator 使用 operator-      更新时间:2023-10-16

表达式x->y要求x是完成类型的指针,或者当x是类的实例时,需要为x定义的operator->()。但是,当后者是这种情况时,为什么不是我可以使用转换函数(即,将对象x转换为指针)?例如:

struct A
{
    int mi;
    operator A*() { return this; }
};
int main()
{
    A a;
    a[1];  // ok: equivalent to *(a.operator A*() + 1);
    a->mi; // ERROR
}

这给出了一个错误消息:

error: base operand of '->' has non-pointer type 'A'

但是问题是,为什么不使用a.operator A*(),就像a[1]一样?

这是由于表达式运营商的特殊超载分辨率规则所致。对于大多数运营商而言,如果操作数具有类型是类或枚举的类型,则操作员功能和内置运算符相互竞争,并且过载分辨率确定要使用哪种。这就是a[1]发生的情况。但是,有一些例外,适用于您的案件的例外是标准中的[13.3.1.2p3.3](所有报价中的强调我的强调):

(3.3) - 对于操作员,,Unary操作员&或操作员->内置候选人集为空。对于所有其他运营商, 内置候选人包括所有候选运营商功能 与给定的操作员相比,在13.6中定义了,

  • 具有相同的操作员名称,
  • 接受相同数量的操作数,
  • 接受可以根据13.3.3.1和
  • 转换给定的操作数或操作数的操作数类型
  • 没有任何不是函数模板专业化的非成员候选人的参数类型列表。

因此,对于a[1],用户定义的转换用于获取一个可以应用内置[]运算符的指针,但是对于那里的三个例外,只有运算符函数首先考虑到(并且那里'在这种情况下)。后来,[13.3.1.2p9]:

如果操作员是操作员,,则单位操作员&或 操作员->,并且没有可行的功能,那么操作员是 假定是内置的操作员,并根据 第5条。

简而言之,对于这三个操作员,只有在其他所有操作失败时才考虑内置版本,然后他们必须在操作数上工作,而无需任何用户定义的转换。

据我所知,这样做是为了避免混淆或模棱两可的行为。例如,内置运算符,&对于(几乎)所有操作数都是可行的,因此,如果在过载的正常步骤中,将它们过载它们将无效,则将它们无法正常工作。

运算符->在超载时具有异常行为,因为它可能导致一系列过载的->的调用链,如[Note 129]中所述:

如果operator->函数返回的值具有类类型,则 可能会导致选择并调用另一个operator->功能。这 进程重复直到operator->函数返回值 非班级类型。

我想您会从超载->的类开始,该类返回其他类型的对象,该类型不会超载->,但具有用户定义的转换为指针类型,从而导致最终内置->的调用被认为有些混乱。将其限制为明确的->超载看起来更安全。


所有报价均来自N4431,即当前的工作草案,但是相关部分自C 11。

以来没有更改

我没有交手的标准,也许有人可以进来并在我之后提供更好的答案。但是,从cppreference.com上的叙述中:

内置操作员的左操作数。操作员 ->是完整标量T型(用于操作员)的表达式。正确的操作数是T或T的基本类之一的成员对象或成员函数的名称,例如Expr.Member,可选的合格,例如expr.name :: member,可选地使用模板disambiguator,例如expr.template成员。 表达式A-> b完全等于(*a).b对于内置类型。如果提供了用户定义的操作员 ->,则在其递归返回的值上再次调用操作员 ->,直到达到运算符 ->到达返回普通指针的操作员。之后,将内置语义应用于该指针。

强调是我的。

如果操作员 ->要在另一个操作员的结果上递归调用 ->(将具有指针返回类型),这强烈暗示操作员 ->必须在指针类型上调用。

<</p>