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

use*_*445 18 c++ decltype language-lawyer c++20

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&>);
Run Code Online (Sandbox Code Playgroud)

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

否则,如果 E 是无括号的 id 表达式或无括号的类成员访问 ([expr.ref]),则 decltype(E) 是由 E 命名的实体的类型。如果没有这样的实体,或者如果 E 命名为一组重载函数,程序格式错误;

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

X::i在未计算的上下文中是一个有效的id 表达式,所以它的 decltype 应该是iin的声明类型X

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

显然,如果i静态数据成员,那么X::i将是左值。但是对于非静态成员,我能找到的唯一提示是 [expr.context] 中的一些非规范语言:

在某些情况下,会出现未计算的操作数 ([expr.prim.req], [expr.typeid], [expr.sizeof], [expr.unary.noexcept], [dcl.type.simple], [temp.pre], [温度概念])。不计算未计算的操作数。[注意:在未计算的操作数中,可以命名非静态类成员([expr.prim.id])并且对象或函数的命名本身不需要提供定义([basic.def. odr])。未计算的操作数被视为完整表达式。— 尾注 ]

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

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

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

备注: StoryTeller - Unslander Monica 的回答更有意义,因为这sizeof(X::i)是允许的,而且这decltype((X::i + 42))是一个 prvalue。

Sto*_*ica 11

像这样的表达式——X::i根本无法计算,更不用说确定对象的身份了——怎么能被认为是左值?

这并不完全正确。可以评估该表达式(在正确的上下文中进行适当的转换之后)。

[class.mfct.非静态]

3当不属于类成员访问语法的一部分且未用于形成指向成员的指针 ([expr.unary.op]) 的 id 表达式在可以使用 this 的上下文中用于类 X 的成员时,如果名称查找将 id 表达式中的名称解析为某个类 C 的非静态非类型成员,并且如果 id 表达式可能被求值或者 C 是 X 或 X 的基类,则 id-使用 (*this) 作为后缀表达式将表达式转换为类成员访问表达式。操作员。

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

struct X {
  int i;
  auto foo() const { return X::i; }
};
Run Code Online (Sandbox Code Playgroud)

哪里X::i变成了(*this).X::i。这是明确允许的,因为

[expr.prim.id]

2表示类的非静态数据成员或非静态成员函数的 id 表达式只能用于:

  • 作为类成员访问的一部分,其中对象表达式引用成员的类或从该类派生的类,或...

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

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


Lan*_*yer 7

像 `X::i 这样的表达式——根本无法计算,更不用说确定对象的身份了——怎么能被认为是左值?

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

表示类的嵌套名称说明符,可选地后跟关键字template([temp.names]),然后后跟该类 ([class.mem]) 或其基类之一的成员的名称, 是一个合格的 id;... 如果成员是静态成员函数或数据成员,则结果为左值,否则为纯右值。

一个表达式的值类别不是由 [basic.lval] 中的 «definition» 决定的,就像 «一个表达式,其评估决定了一个对象的身份»,而是为每种表达式明确指定。