提问人:user3188445 提问时间:6/8/2021 最后编辑:user3188445 更新时间:6/9/2021 访问量:762
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
否则,如果 E 是无括号的 id 表达式或无括号的类成员访问 ([expr.ref]),则 decltype(E) 是 E 命名的实体的类型。如果没有这样的实体,或者如果 E 命名了一组重载函数,则程序的格式不正确;
- https://timsong-cpp.github.io/cppwp/n4861/dcl.type.decltype#1.3
X::i
是未计算上下文中的有效 id-expression,因此其 decltype 应为 in 的声明类型。i
X
第二个让我难住了。[dcl.type.decltype] 中只有一个子句,其中带括号的表达式生成左值引用:必须是两个编译器都认为是左值类别的表达式。但我在语言规范中找不到任何对此的支持。static_assert
X::i
显然,如果是静态数据成员,则为左值。但是对于一个非静态成员,我能找到的唯一提示是 [expr.context] 中的一些非规范性语言:i
X::i
在某些上下文中,会出现未计算的操作数 ([expr.prim.req]、[expr.typeid]、[expr.sizeof]、[expr.unary.noexcept]、[dcl.type.simple]、[temp.pre]、[temp.concept])。 不计算未计算的操作数。 [注意:在未计算的操作数中,可以命名非静态类成员 ([expr.prim.id]),并且对象或函数的命名本身不需要提供定义 ([basic.def.odr])。 未计算的操作数被视为完整表达式。 — 尾注 ]
- https://timsong-cpp.github.io/cppwp/n4861/expr.prop#expr.context-1
这表明这是一个有效的类型,但完整表达式的定义并没有说明值类别。我看不出有什么理由比(或)更合理。我的意思是左值是 glvalue,而 glvalue 是“其计算决定对象身份的表达式”。像这样的表达式 - 根本无法计算,更不用说确定对象的身份了 - 怎么能被认为是glvalue?decltype((X::i))
int&
int
int&&
X::i
gcc 和 clang 是否正确接受此代码,如果是,语言规范的哪一部分支持它?
备注:StoryTeller - Unslander Monica 的回答更有意义,因为 sizeof(X::i)
是允许的,这是一个 prvalue。decltype((X::i + 42))
答:
您问题的根源似乎是 和 之间的区别。为什么产生一个?看:decltype(X::i)
decltype((X::i))
(X::i)
int&
https://timsong-cpp.github.io/cppwp/n4861/dcl.type.decltype#1.5
否则,如果 E 是左值,则 decltype(E) 为 T&,其中 T 是 E 的类型;
然而,这里的关键点是,它产生一个事实并不重要:T&
https://timsong-cpp.github.io/cppwp/n4861/expr.type#1
如果表达式最初具有类型“对 T 的引用”([dcl.ref]、[dcl.init.ref]),则在进行任何进一步分析之前,将类型调整为 T。表达式指定由引用表示的对象或函数,表达式是左值或 x值,具体取决于表达式。[ 注意:在引用的生存期开始之前或结束之后,行为是未定义的(参见 [basic.life])。
那么,什么“证明”它是合理的呢?好吧,在这样做时,我们主要关心的是 of 的类型是什么,而不是它的价值类别或将其视为表达式时的属性。但是,如果我们确实在乎,就会有。decltype(X::i)
X::i
(X::i)
评论
X::i
i
X::i
decltype((X::i))
int&
像这样的表达式 - 根本无法计算,更不用说确定对象的身份了 - 怎么能被认为是glvalue?
X::i
这并不完全正确。可以评估该表达式(在正确的上下文中进行适当的转换)。
[class.mfct.非静态]
3 当一个不属于类成员访问语法的 id-expression 并且不用于形成指向成员的指针 ([expr.unary.op]) 时,在可以使用该语法的上下文中,在类 X 的成员中使用,如果名称查找将 id-expression 中的名称解析为某个类 C 的非静态非类型成员, 如果 id-expression 可能被计算,或者 C 是 X 或 X 的基类,则 id-expression 将转换为类成员访问表达式,使用 (*this) 作为 左侧的后缀表达式。算子。
因此,我们可以拥有类似的东西
struct X {
int i;
auto foo() const { return X::i; }
};
其中转换为 .这是明确允许的,因为X::i
(*this).X::i
[expr.prim.id]
2 表示类的非静态数据成员或非静态成员函数的 id-expression 只能用于:
- 作为类成员访问的一部分,其中对象表达式引用成员的类或从该类派生的类,或者...
而这个含义总是一个左值,表示类成员。(*this).X::i
i
所以你看,在可以计算的上下文中,它总是产生一个左值。因此,在这些上下文中,需要是左值引用类型。虽然类范围之外通常不能在表达式中使用,但说它的值类别是左值仍然不是完全武断的。我们只是稍微扩展了定义区域(这与标准并不矛盾,因为标准没有定义它)。X::i
decltype((X::i))
X::i
评论
X::i
int&
decltype
像 'X::i 这样的表达式——根本无法计算,更不用说确定对象的身份了——怎么能被认为是 glvalue?
忽略 «result» 的误用,它是 [expr.prim.id.qual]/2:
表示类的嵌套名称说明符是限定 id,可以选择后跟关键字 ([temp.names]),然后后跟该类 ([class.mem]) 或其基类之一的成员的名称;...如果成员是静态成员函数或数据成员,则结果为左值,否则为prvalue。
template
表达式的值类别不是由 [basic.lval] 中的“定义”确定的,就像“其计算决定对象身份的表达式”一样,而是为每种表达式显式指定。
评论