表达式struct Elf
要求x->y
是指向完整类类型的指针,或者当x
是类的实例时,需要为{{1}定义x
}}。但是当后者是这种情况时,为什么不我可以使用转换函数(即将对象operator->()
转换为指针)?例如:
x
这会显示错误消息:
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'
呢?
答案 0 :(得分:5)
这是由于表达式中运算符的特殊重载解析规则。对于大多数运算符,如果任一操作数具有类或枚举类型,则运算符函数和内置运算符相互竞争,并且重载决策确定将使用哪一个。这就是a[1]
的情况。但是,有一些例外情况,适用于您案件的例外情况在标准的第[13.3.1.2p3.3]段中(强调我的所有引文):
(3.3) - 对于运算符
,
,一元运算符&
或运算符->
, 内置候选集是空的。对于所有其他运营商, 内置候选包括所有候选运算符函数 在13.6中定义,与给定的运算符相比,
- 具有相同的运营商名称,
- 接受相同数量的操作数,
- 接受根据13.3.3.1可以转换给定操作数或操作数的操作数类型,并且
- 没有与非功能模板专业化的非成员候选者相同的参数类型列表。
因此,对于a[1]
,用户定义的转换用于获取可以应用内置[]
运算符的指针,但对于那里的三个例外,只有运算符函数被认为是第一个(在这种情况下没有任何)。后来,[13.3.1.2p9]:
如果运算符是运算符
,
,则为一元运算符&
,或者 运算符->
,并且没有可行的函数,那么运算符就是 假设是内置运算符并按照解释 第5条。
简而言之,对于这三个运算符,仅当其他所有操作都失败时才会考虑内置版本,然后他们必须处理操作数而不进行任何用户定义的转换。
据我所知,这是为了避免混淆或模棱两可的行为。例如,内置运算符,
和&
对于(几乎)所有操作数都是可行的,因此如果在重载解析的正常步骤中考虑它们,则重载它们将不起作用。
运算符->
在重载时有异常行为,因为它可能会导致重载->
的调用链,如[注释129]中所述:
如果
operator->
函数返回的值具有类类型,则为此 可能导致选择并调用另一个operator->
函数。该 进程重复,直到operator->
函数返回值为 非类型。
我想你有可能从一个重载->
的类开始,它返回另一个类类型的对象,它不会重载->
但是有一个用户定义的转换一个指针类型,导致内置->
的最终调用被认为有点混乱。将此限制为显式重载->
看起来更安全。
所有报价均来自当前工作草案N4431,但相关部分自C ++ 11以来未发生变化。
答案 1 :(得分:0)
我没有手头的标准,也许有人可以进来并在我之后提出更好的答案。但是,从cppreference.com的叙述中可以看出:
内置运算符的左操作数。和操作员 - >是完整标量类型T(对于运算符。)或指向完整标量类型T *(对于运算符 - >)的表达式,在调用运算符之前对其进行求值。右操作数是T的成员对象或成员函数的名称或T的基类之一,例如, expr.member,可选地合格,例如expr.name::member,可选地使用模板消歧器,例如expr.template成员。 对于内置类型,表达式A-> B完全等同于(* A).B。 如果是用户定义的运算符 - >提供,运营商 - >再次调用它返回的值,直到operator->到达,返回一个普通指针。之后,内置语义应用于该指针。
重点是我的。
如果是运营商 - >将被另一个运算符的结果递归调用 - > (它将具有指针返回类型),它强烈暗示运算符 - >必须在指针类型上调用。