是什么证明了 C++ 中未评估的非静态数据成员的左值类别?

What justifies the lvalue category of unevaluated non-static data members in C++?

gcc 和 clang 都接受以下代码,我正在尝试找出原因。

// c++ -std=c++20 -Wall -c test.cc

#include <concepts>

struct X {
  int i;
};

// This is clearly required by the language spec:
static_assert(std::same_as<decltype(X::i), int>);

// This seems more arbitrary:
static_assert(std::same_as<decltype((X::i)), int&>);

根据 [dcl.type.decltype] 第一 static_assert 行是有意义的:

otherwise, if E is an unparenthesized id-expression or an unparenthesized class member access ([expr.ref]), decltype(E) is the type of the entity named by E. If there is no such entity, or if E names a set of overloaded functions, the program is ill-formed;

- https://timsong-cpp.github.io/cppwp/n4861/dcl.type.decltype#1.3

X::i 在未计算的上下文中是有效的 id-expression,因此它的 decltype 应该是 X.

中声明的 i 类型

第二个static_assert让我难住了。在 [dcl.type.decltype] 中只有一个子句,其中 带括号的 表达式产生一个左值引用:一定是两个编译器都认为 X::i 是一个左值表达式类别。但是我在语言规范中找不到对此的任何支持。

显然,如果 i 是一个 static 数据成员,那么 X::i 将是一个左值。但是对于 non-static 成员,我能找到的唯一提示是 [expr.context]:

中的一些非规范语言

In some contexts, unevaluated operands appear ([expr.prim.req], [expr.typeid], [expr.sizeof], [expr.unary.noexcept], [dcl.type.simple], [temp.pre], [temp.concept]). An unevaluated operand is not evaluated. [ Note: In an unevaluated operand, a non-static class member may be named ([expr.prim.id]) and naming of objects or functions does not, by itself, require that a definition be provided ([basic.def.odr]). An unevaluated operand is considered a full-expression. — end note ]

- https://timsong-cpp.github.io/cppwp/n4861/expr.prop#expr.context-1

这表明 decltype((X::i)) 是一个有效的类型,但是 definition of full-expression 没有说明值类别。我看不出有什么比 int(或 int&&)更能证明 int&。我的意思是左值是一个 glvalue,而 glvalue 是“一个表达式,其计算决定了一个对象的身份”。像 X::i 这样的表达式——根本无法求值,更不用说确定对象的身份了——怎么能被认为是泛左值?

gcc 和 clang 是否接受此代码?如果是,语言规范的哪一部分支持它?

备注: StoryTeller - 鉴于 sizeof(X::i) is alloweddecltype((X::i + 42)) 是纯右值这一事实,Unslander Monica 的回答更有意义。

您问题的根源似乎是 decltype(X::i)decltype((X::i)) 之间的区别。为什么 (X::i) 会产生 int&?参见:

https://timsong-cpp.github.io/cppwp/n4861/dcl.type.decltype#1.5

otherwise, if E is an lvalue, decltype(E) is T&, where T is the type of E;

然而,这里的关键点是它产生 T& 的事实并不重要:

https://timsong-cpp.github.io/cppwp/n4861/expr.type#1

If an expression initially has the type “reference to T” ([dcl.ref], [dcl.init.ref]), the type is adjusted to T prior to any further analysis. The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression. [ Note: Before the lifetime of the reference has started or after it has ended, the behavior is undefined (see [basic.life]). — end note ]

那么什么“证明”它呢?好吧,在执行 decltype(X::i) 时,我们主要关心 X::i 的类型是什么,而不是它的值类别或其作为表达式处理时的属性。但是,如果我们 关心,(X::i) 就在那里。

How can an expression like X::i--which can't be evaluated at all, let alone determine the identity an object--be considered a glvalue?

这不完全正确。该表达式 可以 求值(在正确的上下文中进行适当的转换)。

[class.mfct.non-static]

3 When an id-expression that is not part of a class member access syntax and not used to form a pointer to member ([expr.unary.op]) is used in a member of class X in a context where this can be used, if name lookup resolves the name in the id-expression to a non-static non-type member of some class C, and if either the id-expression is potentially evaluated or C is X or a base class of X, the id-expression is transformed into a class member access expression using (*this) as the postfix-expression to the left of the . operator.

所以我们可以有类似的东西

struct X {
  int i;
  auto foo() const { return X::i; }
};

其中X::i转化为(*this).X::i。由于

,这是明确允许的

[expr.prim.id]

2 An id-expression that denotes a non-static data member or non-static member function of a class can only be used:

  • as part of a class member access in which the object expression refers to the member's class or a class derived from that class, or ...

(*this).X::i 的含义始终是表示 class 成员 i 的左值。

所以你看,在X::i可以计算的上下文中,它总是产生一个左值。因此 decltype((X::i)) 在这些上下文中 需要 成为左值引用类型。虽然在 class 范围之外 X::i 通常不能用在表达式中,但说它的值类别是左值仍然不是完全武断的。我们只是稍微扩大了定义范围(这与标准并不矛盾,因为标准没有定义它)。

How can an expression like `X::i--which can't be evaluated at all, let alone determine the identity an object--be considered a glvalue?

忽略 «result» 的误用,它是 [expr.prim.id.qual]/2:

A nested-name-specifier that denotes a class, optionally followed by the keyword template ([temp.names]), and then followed by the name of a member of either that class ([class.mem]) or one of its base classes, is a qualified-id; ... The result is an lvalue if the member is a static member function or a data member and a prvalue otherwise.

表达式的值类别不是由 [basic.lval] 中的 «定义» 确定的,如 «其计算确定对象身份的表达式»,而是为每种表达式明确指定.