[expr.ref]p(6.3.2):
否则,如果
E1.E2
引用非静态成员函数和E2
的类型是"参数类型列表cvref 限定符opt返回 T 的函数",则E1.E2
是一个 prvalue。该表达式指定一个非静态成员函数。这 表达式只能用作成员的左侧操作数 函数调用 ([class.mfct])。[ 注意:任何冗余集 表达式周围的括号将被忽略 ([expr.prim.paren])。 — 尾注 ]E1.E2
的类型是"参数类型列表函数"简历返回T
"。
例如,下面main
中的第二条语句无法编译,可能是因为上面突出显示的句子。但是,为什么语言设置为以这种方式工作呢?
#include<iostream>
void g();
struct S { void f(); };
S s;
int main(){
std::cout << "decltype(g) == void() ? " << std::is_same<decltype(g), void()>::value << 'n'; // Ok
std::cout << "decltype(s.f) == void() ? " << std::is_same<decltype(s.f), void()>::value << 'n'; // Doesn't compile probably because of the sentence hihlighted above in [expr.ref]p(6.3.2).
}
当你做E1.E2
时,你不是在谈论E1
类型的一般性质。您请求访问E1
指定的对象中的事物,其中要访问的事物的名称为E2
。如果E2
是静态的,它访问类静态的东西;如果E2
是非静态的,则它访问特定于该对象的成员事物。这很重要。
成员变量成为子对象。如果您的类S
具有非静态数据成员int i;
,则s.i
是对int
的引用。从int&
的角度来看,这种引用的行为与任何其他int&
没有什么不同。
让我更清楚地说:任何int*
或int&
都可以指向/引用一个完整的对象或作为其他对象的子对象的int
int
。单结构int&
可以通过这种方式承担双重任务。*
鉴于对s.i
的理解,s.f
的假定含义是什么?嗯,它应该是相似的,对吧?s.f
将是某种东西,当用params
调用时,将等同于做s.f(params)
。
但这不是C++中存在的东西。
C++中没有任何语言结构可以表示s.f
的含义。这样的构造需要存储对s
以及成员S::f
的引用。
函数指针无法执行此操作。函数指针需要能够与void*
**进行指针互转换。但是这样的s.f
需要存储成员S::f
以及对s
本身的引用。所以根据定义,它必须大于void*
。
成员指针也无法执行此操作。成员指针显式不随身携带其this
对象(这就是重点);您必须在调用时使用特定成员指针调用语法.*
或.->
提供它们。
哦,有一些方法可以在语言中对此进行编码:lambdas,std::bind
等。但是没有语言级别的结构具有这种精确的含义。
因为C++以这种方式是不对称的,s.i
具有可编码的含义但不具有s.f
,C++使不可编码的含义成为非法的。
你可能会问为什么这样的结构不能简单地构建。其实没那么重要。该语言按原样运行良好,并且由于s.f
需要的复杂性,最好让你使用 lambda(诚然,应该有办法让它更短来编写这样的东西)如果这是你想要的。
如果你想让裸s.f
等同于S::f
(即:指定成员函数),那也不起作用。首先,S::f
也没有类型;使用这样的 PRvalue 您唯一能做的就是将其转换为指向成员的指针。其次,成员函数指针不知道它来自哪个对象,所以为了使用一个来调用成员,你需要给它s
。因此,在调用表达式中,s
必须出现两次。这真的很愚蠢。
*:您可以执行某些操作来完成无法对子对象执行的操作。但是这些会引发UB,因为它们无法被编译器检测到,因为int*
不会说它是否来自子对象。这是重点;没有人能分辨出其中的区别。
**:标准不要求这样做,但标准不能做一些完全使这种实现无法的事情。大多数实现都提供此功能,基本上任何 DLL/SO 加载代码都依赖于它。哦,它也与 C 完全不兼容,这使得它无法启动。
stat_result.st_mtime
语法继承自 C,在 C 中它总是有一个值(更具体地说,是一个左值)。 因此,即使在方法调用的情况下,它在语法上也是一个表达式,但它没有价值,因为它是与其关联的调用表达式一起计算的。
它(如您所引用的)给定成员函数的类型,以满足通过正确类型的表达式调用函数的要求。 但是,为它定义decltype
会误导,因为它不能是一个完整的表达式(就像每个未计算的操作数一样),并且具有decltype
的常见表达式 SFINAE 不会阻止受保护实例化的硬错误。
请注意,非静态成员函数可以在未计算的操作数中命名,但这允许S::f
(或仅在类中f
,尽管可以说被重写为在成员函数中(*this).f
),而不是s.f
。 该表达式具有相同的类型,但出于同样的原因,该表达式本身受到限制(与&
一起出现以形成指向成员 [function] 的指针):如果要以其他方式使用它,它将可用作普通的 [function] 指针,这是不可能的。