在c++中,我正在寻找标准的关键部分解释了我观察到的行为上的细微差别语言的两个指针到成员的访问操作符,.*
和->*
。
根据我的测试程序如下所示,而->*
似乎允许其右表达式可以隐式转换为任意类型pointer to member of S
、.*
则不然。编译时GCC和clang,这两个编译器都会对标记为'(2)'的行产生错误。说明我的类Offset
不能用作成员指针。
测试程序https://godbolt.org/z/46nMPvKxE
#include <iostream>
struct S { int m; };
template<typename C, typename M>
struct Offset
{
M C::* value;
operator M C::* () { return value; } // implicit conversion function
};
int main()
{
S s{42};
S* ps = &s;
Offset<S, int> offset{&S::m};
std::cout << ps->*offset << 'n'; // (1) ok
std::cout << s.*offset << 'n'; // (2) error
std::cout.flush();
}
编译器输出
GCC 12.2:
'offset' cannot be used as a member pointer, since it is of type 'Offset<S, int>'
clang 15.0:
right hand operand to .* has non-pointer-to-member type 'Offset<S, int>'
程序变异
为了证明->*
实际上使用Offset
执行隐式转换转换函数,我声明它显式用于测试目的,
explicit operator M C::* () { return value; } // no longer implicit conversion function
导致编译器对标记为'(1)'的行也产生错误:
GCC 12.2:
error: no match for 'operator->*' (operand types are 'S*' and 'Offset<S, int>')
note: candidate: 'operator->*(S*, int S::*)' (built-in)
note: no known conversion for argument 2 from 'Offset<S, int>' to 'int S::*'
clang 15.0:
error: right hand operand to ->* has non-pointer-to-member type 'Offset<S, int>'
虽然是,但这两个操作符之间的区别有充分的文档记录->*
是可重载的,而.*
不是,我的代码显然没有使用这个选项而是依赖于为原始指针类型S*
定义的内置operator ->*
。
除了在可重载性方面的差异之外,我只找到了说明表达式的相似性的文档。引自标准(https://open-std.org/jtc1/sc22/wg21/docs/papers/2020/n4868.pdf):
)[7.6.4.2]二进制操作符。*将第二个操作数(类型为"指向T的成员的指针")绑定到第一个操作数操作数,该操作数应为类T或类T为明确可访问基的类的左值类。结果是第二个操作数指定类型的对象或函数。
[7.6.4.3][…表达式E1->E2被转换成等价形式((E1))。*E2.
引自cppreference.com (https://en.cppreference.com/w/cpp/language/operator_member_access#Built-in_pointer-to-member_access_operators):
两个操作符的第二个操作数是指向T的成员(数据或函数)的指针或指向T的明确可访问基类B的成员的指针类型的表达式。表达式E1->*E2完全等价于(*E1)。*E2用于内置类型;这就是为什么下面的规则只针对E1.*E2.
没有找到右操作数转换的概念。
我忽略了什么?有人能给我解释一下这种行为差异吗?
当使用可重载操作符并且至少有一个操作数是类或枚举类型时,重载解析使用包含内置候选操作符([over.match.oper]/3)的候选操作集执行,特别是对于->*
,请参见[over.built]/9。
在本例中,选择了一个内置候选者,因此隐式转换应用于第二个操作数,然后将->*
解释为内置操作符([over.match.oper]/11)。
对于.*
,根本没有重载解析,因此也没有隐式转换。