C++ 中未计算的非静态数据成员的左值类别的合理性是什么?

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

提问人:user3188445 提问时间:6/8/2021 最后编辑:user3188445 更新时间:6/9/2021 访问量:762

问:

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 的声明类型。iX

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

显然,如果是静态数据成员,则为左值。但是对于一个非静态成员,我能找到的唯一提示是 [expr.context] 中的一些非规范性语言:iX::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&intint&&X::i

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

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

C++ 语言律师 C++20 decltype

评论


答:

-1赞 i-hate-cookies 6/8/2021 #1

您问题的根源似乎是 和 之间的区别。为什么产生一个?看: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)

评论

1赞 user3188445 6/8/2021
但是你对我的问题有答案吗?当是非静态成员时,左值类别的合理性是什么?我想我们都同意,如果是一个左值,那么就是一个。但是,规范的哪一部分首先证明它是左值的合理性呢?X::iiX::idecltype((X::i))int&
11赞 StoryTeller - Unslander Monica 6/8/2021 #2

像这样的表达式 - 根本无法计算,更不用说确定对象的身份了 - 怎么能被认为是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::ii

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

评论

0赞 Brian Bi 6/9/2021
难道不应该要求编译器发出诊断,因为括号强制它被视为表达式,但在这种情况下它不是有效的表达式吗?它未经评估的事实并不能以某种方式改变这一点。如果编译器想要作为扩展生成,很好,但它仍然应该发出诊断。X::iint&
0赞 Brian Bi 6/9/2021
也许我没有正确表达自己。我想即使它没有括号,它仍然是一个表达式。但至少在未括号的情况下,该标准似乎通过给出其含义的规则来专门定义它。在括号中,规则只是“像对待任何其他上下文中的表达式一样对待它,但不要评估它”。所以它应该是格式错误的。
1赞 StoryTeller - Unslander Monica 6/9/2021
@Brian - 可以说,它最多是未定义的,并且不违反可诊断规则。至少我找不到一个。在未计算的操作数 timsong-cpp.github.io/cppwp/n4861/expr.prim.id#2.3 中明确允许 - 但含义并非全部。
0赞 Brian Bi 6/9/2021
啊哈,我忘了那个规则。是的,我想它毕竟不是畸形的。太糟糕了,标准没有定义这里应该是什么。decltype
7赞 Language Lawyer 6/8/2021 #3

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

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

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

表达式的值类别不是由 [basic.lval] 中的“定义”确定的,就像“其计算决定对象身份的表达式”一样,而是为每种表达式显式指定。

评论

0赞 user3188445 6/8/2021
规范令人困惑(尤其是您指出的结果的使用),但这很有帮助,谢谢。不知道为什么在我给你投赞成票后有人把你投了反对票,但我很欣赏你的参考。
1赞 Language Lawyer 6/8/2021
@user3188445这里使用“结果”一词早于“结果”的当前定义。CWG 人员在添加新术语时似乎不会费心清理措辞。这不仅发生在“结果”上。