是什么证明了 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 allowed 和 decltype((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] 中的 «定义» 确定的,如 «其计算确定对象身份的表达式»,而是为每种表达式明确指定.
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 allowed 和 decltype((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] 中的 «定义» 确定的,如 «其计算确定对象身份的表达式»,而是为每种表达式明确指定.