为什么 [expr.ref]p(6.3.2) 中的句子 "The expression can be used only as the left-hand operand of a member function call"?

Why the sentence "The expression can be used only as the left-hand operand of a member function call" in [expr.ref]p(6.3.2)?

[expr.ref]p(6.3.2):

Otherwise, if E1.E2 refers to a non-static member function and the type of E2 is “function of parameter-type-list cv ref-qualifieropt returning T”, then E1.E2 is a prvalue. The expression designates a non-static member function. The expression can be used only as the left-hand operand of a member function call ([class.mfct]). [ Note: Any redundant set of parentheses surrounding the expression is ignored ([expr.prim.paren]). — end note ] The type of E1.E2 is “function of parameter-type-list cv returning 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 是静态的,它访问 class 静态的东西;如果 E2 是 non-static,则它访问 特定于该对象 的成员事物。这很重要。

成员变量成为子对象。如果您的 class S 有一个 non-static 数据成员 int i;s.i 是对 int 的引用。从 int& 的角度来看,该引用与任何其他 int&.

的行为没有区别

让我说得更清楚一点:任何 int*int& 都可以指向 to/reference 一个 int 这是一个完整的对象或一个 int是其他某个对象的子对象。单个构造 int& 可以以这种方式服务于 double-duty。*

鉴于对 s.i 的理解,s.f 的假定含义是什么?嗯,应该差不多吧? s.f 是某种东西,当用 params 调用时,相当于 s.f(params).

但这不是 C++ 中存在的东西。

C++中没有语言结构可以表示s.f的意思。这样的构造需要存储对 s 以及成员 S::f.

的引用

函数指针不能做到这一点。函数指针需要能够 pointer-interconvertible 和 void***。但是这样的 s.f 需要存储成员 S::f 以及对 s 本身的引用。所以根据定义,它必须大于 void*.

成员指针也做不到。成员指针明确地不携带它们的 this 对象(这就是重点);您必须使用特定的成员指针调用语法 .*.->.

在 call-time 处提供它们

哦,有多种方法可以在语言中对其进行编码:lambdas、std::bind 等。但是没有 language-level 具有这种精确含义的结构。

因为 C++ 在这方面是不对称的,其中 s.i 具有可编码的含义但 s.f 没有,C++ 使不可编码的含义不合法。

您可能会问为什么不简单地构建这样的结构。这并不是那么重要。该语言按原样工作得非常好,并且由于 s.f 需要的复杂性,最好让您使用 lambda(诚然,应该有办法让它更短,这样写事情)如果那是你想要的。

而如果你想让一个裸体的 s.f 等同于 S::f(即:指定成员函数),那也不起作用。首先,S::f 也没有类型;对于这样的纯右值,您唯一可以做的就是将其转换为指向成员的指针。第二,成员函数指针不知道它来自什么对象,所以为了使用一个来调用成员,你需要给它s。因此,在调用表达式中,s 必须出现两次。这真的很傻。

*:有些事情你可以来完成你不能对子对象做的事情。但是那些会引发 UB,因为编译器无法检测到它们,因为 int* 没有说明它是否来自子对象。这是重点;没人能分辨出来。

**:标准不要求这样做,但是标准不能做一些 out-right 使得这样的实现 不可能.大多数实现都提供此功能,基本上任何 DLL/SO 加载代码都依赖它。哦,它也与 C 完全不兼容,这使它成为 non-starter.

stat_result.st_mtime 语法继承自 C,它总是有一个值(更具体地说,是一个左值)。因此,即使在方法调用的情况下,它语法上也是一个表达式,但它没有任何价值,因为它是与其关联的调用表达式一起计算的。

它是(如你引用的)给定成员函数的 type 以满足 requirement that a function be called via an expression of the correct type. It would, however, be misleading to define decltype for it since it cannot be a full-expression (as is every unevaluated operand) 和具有 decltype 的公共表达式 SFINAE不会阻止受保护实例化的硬错误。

请注意,non-static 成员函数 can be named in an unevaluated operand, but that allows S::f (or just f within the class, although that is arguably rewritten to be (*this).f in a member function), not s.f. That expression has the same type 但由于相同的原因本身受到限制(与 & 一起出现以形成指向成员 [function] 的指针):如果它如果要以其他方式使用,它将用作普通的 [function] 指针,这是不可能的。