What are rvalues, lvalues, xvalues, glvalues, and prvalues?
在C++ 03中,表达式是rValor或LValk。
在C++ 11中,表达式可以是:
两大类已成为五大类。
- 这些新的表达式类别是什么?
- 这些新类别如何与现有的右值和左值类别相关?
- C++0X中的RValk和LValk类与C++ 03中的值相同吗?
- 为什么需要这些新类别?wg21诸神只是想迷惑我们这些凡人吗?
我想这份文件可以作为一个不那么简短的介绍:N3055
整个大屠杀都是从移动语义学开始的。一旦我们有了可以移动而不可以复制的表达式,突然间容易掌握的规则就要求区分可以移动的表达式和方向。
根据我的猜测,基于草案,R/L值的区别保持不变,只有在移动的情况下才会变得混乱。
他们需要吗?如果我们想放弃新功能,可能不会。但为了实现更好的优化,我们可能应该接受它们。
引用N3055:
- 一个左值(所谓的,历史上的,因为lvalues可能出现在作业的左侧表达式)指定函数或物体。[示例:如果
E 是指针类型的表达式,然后是*E 。是引用的左值表达式E 的对象或功能。点。另一个例子是调用函数的结果返回类型是左值引用是一个左值 - 一个xValm"到期"值)也指物体,通常在其末端附近终身(以便其资源例如,移动)。一个x值是某些类型的结果涉及右值的表达式参考文献。[例子:调用函数的结果返回类型是右值引用是一个xValue.
- glvalue("广义"lvalue)是lvalue或一个xValk。
- 一个右值(所谓,历史上,因为价值观出现在赋值表达式)是xvalue,临时物体或子对象,或不与对象关联。
- 一prvalue("pure"rvvalue)是一个rvvalue这不是XValue。[例子:调用函数的结果返回类型不是引用是PR值
所讨论的文档对于这个问题是一个很好的参考,因为它显示了引入新术语后标准发生的确切变化。
What are these new categories of expressions?
FCD(N3092)有一个很好的描述:
— An lvalue (so called, historically, because lvalues could appear on the
left-hand side of an assignment
expression) designates a function or
an object. [ Example: If E is an
expression of pointer type, then
*E is an lvalue expression referring to the object or function to which E
points. As another example, the result
of calling a function whose return
type is an lvalue reference is an
lvalue. —end example ]— An xvalue (an
"eXpiring" value) also refers to an
object, usually near the end of its
lifetime (so that its resources may be
moved, for example). An xvalue is the
result of certain kinds of expressions
involving rvalue references (8.3.2). [
Example: The result of calling a
function whose return type is an
rvalue reference is an xvalue. —end
example ]— A glvalue ("generalized"
lvalue) is an lvalue or an xvalue.—
An rvalue (so called, historically,
because rvalues could appear on the
right-hand side of an assignment
expressions) is an xvalue, a temporary
object (12.2) or subobject thereof, or
a value that is not associated with an
object.— A prvalue ("pure" rvalue) is
an rvalue that is not an xvalue. [
Example: The result of calling a
function whose return type is not a
reference is a prvalue. The value of a
literal such as 12, 7.3e5, or true is
also a prvalue. —end example ]Every
expression belongs to exactly one of
the fundamental classifications in
this taxonomy: lvalue, xvalue, or
prvalue. This property of an
expression is called its value
category. [ Note: The discussion of
each built-in operator in Clause 5
indicates the category of the value it
yields and the value categories of the
operands it expects. For example, the
built-in assignment operators expect
that the left operand is an lvalue and
that the right operand is a prvalue
and yield an lvalue as the result.
User-defined operators are functions,
and the categories of values they
expect and yield are determined by
their parameter and return types. —end
note
不过,我建议您阅读整个3.10节的lvalues和rvalues。
How do these new categories relate to the existing rvalue and lvalue categories?
再一次:
Are the rvalue and lvalue categories in C++0x the same as they are in C++03?
随着移动语义学的引入,rvalues的语义学得到了发展。
Why are these new categories needed?
以便定义和支持移动构造/分配。
我从你最后一个问题开始:
Why are these new categories needed?
C++标准包含许多处理表达式的值类别的规则。有些规则区分左值和右值。例如,当涉及过载分辨率时。其他规则区分glvalue和prvalue。例如,您可以拥有不完整或抽象类型的glvalue,但没有不完整或抽象类型的prvalue。在我们使用这个术语之前,实际上需要区分引用lvalue/rvalue的glvalue/prvalue的规则,它们要么是无意中错误的,要么包含大量对规则a la"的解释和例外……除非该rvalue是由于未命名的rvalue引用…"。所以,给glvalues和prvalues的概念取个名字似乎是个好主意。
What are these new categories of expressions?
How do these new categories relate to the existing rvalue and lvalue categories?
我们仍然有与C++ 98兼容的术语LValk和RValk。我们只是将rvalues分为两个子组,xvalues和prvalues,我们将lvalues和xvalues称为glvalues。XValues是一种新的未命名右值引用的值类别。每个表达式都是这三个表达式之一:lvalue、xvalue、prvalue。维恩图如下:
1 2 3 4 5 6 7 | ______ ______ / X \ / / \ \ | l | x | pr | \ \ / / \______X______/ gl r |
函数示例:
1 2 3 | int prvalue(); int& lvalue(); int&& xvalue(); |
但也不要忘记,命名的rvalue引用是lvalue:
1 2 3 4 | void foo(int&& t) { // t is initialized with an rvalue expression // but is actually an lvalue expression itself } |
Why are these new categories needed? Are the WG21 gods just trying to confuse us mere mortals?
我觉得其他答案(虽然很多答案都很好)并不能真正抓住这个特定问题的答案。是的,这些类别和类似的存在允许移动语义,但是复杂性的存在有一个原因。这是在C++ 11中移动东西的一个不违反的规则:
只有在这样做毫无疑问是安全的时候,你才可以移动。
这就是这些类别存在的原因:能够在安全的地方谈论价值,在不安全的地方谈论价值。
在R值引用的最早版本中,移动很容易发生。太容易了。很容易,当用户真正无意时,有很多潜在的隐式移动对象。
在这种情况下,移动某物是安全的:
如果你这样做:
1 2 3 4 | SomeType &&Func() { ... } SomeType &&val = Func(); SomeType otherVal{val}; |
这是做什么的?在旧版本的规范中,在输入5个值之前,这将引发一个移动。当然可以。您向构造函数传递了一个右值引用,因此它绑定到接受右值引用的构造函数。这是显而易见的。
这个只有一个问题,你没有要求移动它。哦,你可能会说
如果不是临时的,而且你没有要求移动它,那么移动是错误的。
显而易见的解决方案是使
一旦你这样做了,你就不能再说
它不是左值,因为您不能从左值移动。我们需要通过返回一个
我们所拥有的是一个值,您可以将其视为左值,但它是可以从中隐式移动的。我们称之为xvalue。
请注意,xvalues是使我们获得其他两类值的原因:
prvalue实际上只是先前类型的rvvalue的新名称,即它们是不是xvalues的rvalue。
glvalues是xvalues和lvalues在一个组中的联合,因为它们共享许多共同的属性。
所以说真的,所有这些都归结为xvalues,需要将移动限制在精确且仅限于特定的地方。这些位置由右值类别定义;prvalues是隐式移动,xvalues是显式移动(
IMHO,The best explanation about its meaning gave us stroustrup+take into account examples of Dániel Sándor and Mohan:
Stroustrup:
Now I was seriously worried. Clearly we were headed for an impasse or
a mess or both. I spent the lunchtime doing an analysis to see which
of the properties (of values) were independent. There were only two
independent properties:
has identity – i.e. and address, a pointer, the user can determine whether two copies are identical, etc.can be moved from – i.e. we are allowed to leave to source of a"copy" in some indeterminate, but valid stateThis led me to the conclusion that there are exactly three kinds of
values (using the regex notational trick of using a capital letter to
indicate a negative – I was in a hurry):
iM : has identity and cannot be moved fromim : has identity and can be moved from (e.g. the result of casting an lvalue to a rvalue reference)
Im : does not have identity and can be moved from.The fourth possibility,
IM , (doesn’t have identity and cannot be moved) is not
useful inC++ (or, I think) in any other language.In addition to these three fundamental classifications of values, we
have two obvious generalizations that correspond to the two
independent properties:
i : has identitym : can be moved fromThis led me to put this diagram on the board:
Naming
I observed that we had only limited freedom to name: The two points to
the left (labelediM andi ) are what people with more or less
formality have calledlvalues and the two points on the right
(labeledm andIm ) are what people with more or less formality
have calledrvalues . This must be reflected in our naming. That is,
the left"leg" of theW should have names related tolvalue and the
right"leg" of theW should have names related torvalue. I note
that this whole discussion/problem arise from the introduction of
rvalue references and move semantics. These notions simply don’t exist
in Strachey’s world consisting of justrvalues andlvalues . Someone
observed that the ideas that
- Every
value is either anlvalue or anrvalue - An
lvalue is not anrvalue and anrvalue is not anlvalue are deeply embedded in our consciousness, very useful properties, and
traces of this dichotomy can be found all over the draft standard. We
all agreed that we ought to preserve those properties (and make them
precise). This further constrained our naming choices. I observed that
the standard library wording usesrvalue to meanm (the
generalization), so that to preserve the expectation and text of the
standard library the right-hand bottom point of theW should be named
rvalue. This led to a focused discussion of naming. First, we needed to decide
onlvalue. Shouldlvalue meaniM or the generalizationi ? Led
by Doug Gregor, we listed the places in the core language wording
where the wordlvalue was qualified to mean the one or the other. A
list was made and in most cases and in the most tricky/brittle text
lvalue currently meansiM . This is the classical meaning of lvalue
because"in the old days" nothing was moved;move is a novel notion
inC++0x . Also, naming the topleft point of theW lvalue gives us
the property that every value is anlvalue or anrvalue , but not both.So, the top left point of the
W islvalue and the bottom right point
isrvalue. What does that make the bottom left and top right points?
The bottom left point is a generalization of the classical lvalue,
allowing for move. So it is ageneralized lvalue. We named it
glvalue. You can quibble about the abbreviation, but (I think) not
with the logic. We assumed that in serious usegeneralized lvalue
would somehow be abbreviated anyway, so we had better do it
immediately (or risk confusion). The top right point of the W is less
general than the bottom right (now, as ever, calledrvalue ). That
point represent the original pure notion of an object you can move
from because it cannot be referred to again (except by a destructor).
I liked the phrasespecialized rvalue in contrast togeneralized but
lvaluepure rvalue abbreviated toprvalue won out (and
probably rightly so). So, the left leg of the W islvalue and
glvalue and the right leg isprvalue andrvalue. Incidentally,
every value is either a glvalue or a prvalue, but not both.This leaves the top middle of the
W :im ; that is, values that have
identity and can be moved. We really don’t have anything that guides
us to a good name for those esoteric beasts. They are important to
people working with the (draft) standard text, but are unlikely to
become a household name. We didn’t find any real constraints on the
naming to guide us, so we picked ‘x’ for the center, the unknown, the
strange, the xpert only, or even x-rated.
一.导言
ISOC+11(Officially ISO/IEC 14882:2011)是最新版本的标准C+Programming Language。IT contains some new features,and concepts,for example:
- RVALUE References
- Value,Glvalue,Prvalue expression value categories
- 移动语义
如果我们想了解新的表达价值类别的概念,我们就必须知道有RVALUE和LVALUE参考。更好地了解RVALUES可以转换为非Const RVALUE References。
1 2 | int& r_i=7; // compile error int&& rr_i=7; // OK |
如果我们从第3337号工作草案(与ISOC++11标准最相似的版本)中选定了亚组标题为LVALUES和RVALUES的概念,我们可以获得一些价值类别概念的直觉。
3.10 Lvalues and rvalues [basic.lval]
1 Expressions are categorized according to the taxonomy in Figure 1.
- An lvalue (so called, historically, because lvalues could appear on the left-hand side of an assignment expression) designates a function
or an object. [ Example: If E is an expression of pointer type, then
*E is an lvalue expression referring to the object or function to which E points. As another example, the result of calling a function
whose return type is an lvalue reference is an lvalue. —end example ]- An xvalue (an"eXpiring" value) also refers to an object, usually near the end of its lifetime (so that its resources may be moved, for
example). An xvalue is the result of certain kinds of expressions
involving rvalue references (8.3.2). [ Example: The result of calling
a function whose return type is an rvalue reference is an xvalue. —end
example ]- A glvalue ("generalized" lvalue) is an lvalue or an xvalue.
- An rvalue (so called, historically, because rvalues could appear on the right-hand side of an assignment expression) is an xvalue, a
temporary object (12.2) or subobject thereof, or a value that is not
associated with an object.- A prvalue ("pure" rvalue) is an rvalue that is not an xvalue. [ Example: The result of calling a function whose return type is not a
reference is a prvalue. The value of a literal such as 12, 7.3e5, or
true is also a prvalue. —end example ]Every expression belongs to exactly one of the fundamental
classifications in this taxonomy: lvalue, xvalue, or prvalue. This
property of an expression is called its value category.
但我并不确定这一次的subsection is enough to understand the concepts clearly,because"usually"is not really general,"near the end of its lifetime"is not really concerte,"involving rvalue references"is not really clear,and"example:the result of calling a function whose return type is an rve reference is an xvalue."像蛇一样,咬着它的尾巴。
初级价值类别
每一个表达式都属于一个基本价值类别。这些价值类别是LVALUE、XVALUE和Prvalue类别。
阿尔瓦卢埃
The expression e belongs to the Lvalue category if and only if e refers to an entity that already has an identity(address,name or alias)that makes it accessible outside of E.
ZZU1XValues
表达属于xvalue类别if,and only if it is
-The result of calling a function,whether implicitly or explicitly,whose return type is an rvalue reference to the type of object being returned,or
1 2 3 4 5 6 7 8 9 10 | int&& f(){ return 3; } int main() { f(); // The expression f() belongs to the xvalue category, because f() return type is an rvalue reference to object type. return 0; } |
a cast to an rvalue reference to object type,or
1 2 3 4 5 6 7 | int main() { static_cast<int&&>(7); // The expression static_cast<int&&>(7) belongs to the xvalue category, because it is a cast to an rvalue reference to object type. std::move(7); // std::move(7) is equivalent to static_cast<int&&>(7). return 0; } |
-A class member access expression designating a non-static data member of non-reference type in which the object expression is an xvalue,or
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | struct As { int i; }; As&& f(){ return As(); } int main() { f().i; // The expression f().i belongs to the xvalue category, because As::i is a non-static data member of non-reference type, and the subexpression f() belongs to the xvlaue category. return 0; } |
-第一次操作是XValue的指针对成员的表达,第二次操作是数据成员的指针。
Note that the effect of the rules above is that named rvalue references to objects are treated as lvalues and unnamed rvalue references to objects are treated as xvalues;rvalue references to functions are treated as lvalues whether named
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include <functional> struct As { int i; }; As&& f(){ return As(); } int main() { f(); // The expression f() belongs to the xvalue category, because it refers to an unnamed rvalue reference to object. As&& rr_a=As(); rr_a; // The expression rr_a belongs to the lvalue category, because it refers to a named rvalue reference to object. std::ref(f); // The expression std::ref(f) belongs to the lvalue category, because it refers to an rvalue reference to function. return 0; } |
Prvalues
表达式属于Prvalue category if and only if e belongs neither to the Lvalue nor to the Xvalue category.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | struct As { void f(){ this; // The expression this is a prvalue expression. Note, that the expression this is not a variable. } }; As f(){ return As(); } int main() { f(); // The expression f() belongs to the prvalue category, because it belongs neither to the lvalue nor to the xvalue category. return 0; } |
混合价值类别
还有两个重要的混合价值类别。这些价值类别是RVALUE和GLVALUE类别。
Rvalues
该表达式属于RVALUE类别if,只有属于XVALUE类别,或属于Prvalue类别。
Note that this definition means that the expression e belongs to the RVALUE category if and only if e refers to an entity that has not had any identity that makes it accessible outside of e yet.
Glvalues
表达式E属于GLVALUCTEGORY IF,只有属于LVALUE类别或XVALUCTEGORY。
实用规则
Scott Meyer发布了一条非常有用的Thumb规则,以区分RVALUES和LVALUES。
BLCK1/
C++ 03的类别过于受限,无法正确地将R值引用引入到表达式属性中。
通过引入它们,可以说一个未命名的右值引用的计算结果是右值,这样重载解析将更喜欢右值引用绑定,这将使它选择移动构造函数而不是复制构造函数。但研究发现,这会引起周围的问题,例如动态类型和限定条件。
要显示这一点,请考虑
1 2 3 4 5 | int const&& f(); int main() { int &&i = f(); // disgusting! } |
在XEXPROCED草案中,这是允许的,因为在C++ 03中,非类类型的值从不符合CV限定。但我们打算在右值引用案例中应用
动态类型的问题具有相似的性质。在C++ 03中,类类型的rRead具有已知的动态类型——它是该表达式的静态类型。因为要换一种方式使用它,需要引用或取消引用,这些引用或取消引用的值为左值。对于未命名的右值引用,情况并非如此,但它们可以显示多态行为。所以要解决这个问题,
未命名的右值引用变为XValues。它们可以是限定的,并且可能具有不同的动态类型。它们确实像预期的那样,在重载期间更喜欢使用右值引用,并且不会绑定到非常量左值引用。
以前的值(文本,由强制转换为非引用类型创建的对象)现在变成了prvalue。它们在重载期间与xvalue具有相同的首选项。
以前的左值仍然是左值。
两个分组是为了捕获那些可以限定并且可以具有不同动态类型(glvalues)的分组,以及那些重载倾向于使用右值引用绑定(rvalues)的分组。
我一直在努力,直到我跨越了CPREFERENCE.com explanation of the value categories.
事实上,这很简单,但我发现它常常以难以记忆的方式解释。这是非常详细的时间表。我会把这页的一些部分:
Primary categories
The primary value categories correspond to two properties of expressions:
has identity: it's possible to determine whether the expression refers to the same entity as another expression, such as by comparing addresses of the objects or the functions they identify (obtained directly or indirectly);
can be moved from: move constructor, move assignment operator, or another function overload that implements move semantics can bind to the expression.
Expressions that:
- have identity and cannot be moved from are called lvalue expressions;
- have identity and can be moved from are called xvalue expressions;
- do not have identity and can be moved from are called prvalue expressions;
- do not have identity and cannot be moved from are not used.
lvalue
An lvalue ("left value") expression is an expression that has identity and cannot be moved from.
rvalue (until C++11), prvalue (since C++11)
A prvalue ("pure rvalue") expression is an expression that does not have identity and can be moved from.
xvalue
An xvalue ("expiring value") expression is an expression that has identity and can be moved from.
glvalue
A glvalue ("generalized lvalue") expression is an expression that is either an lvalue or an xvalue. It has identity. It may or may not be moved from.
rvalue (since C++11)
An rvalue ("right value") expression is an expression that is either a prvalue or an xvalue. It can be moved from. It may or may not have identity.
How do these new categories relate to the existing rvalue and lvalue categories?
C++ 03值仍然是C++ 11值,而C++ 03的值在C++ 11中被称为PROVALL。
对上述优秀答案的一个补充,在我读了斯特劳斯特鲁普并认为我理解了中值/左值的区别之后,我还是感到困惑。当你看到
把
1 2 3 | int&& a = 3; int&& c = a; //error: cannot bind 'int' lvalue to 'int&&' int& b = a; //compiles |
这对于构造函数中的
您将把
如果你想搬家的话。当我把
正如前面的答案详尽地涵盖了价值类别背后的理论,我想补充一件事:你可以真正玩它并测试它。
对于值类别的一些实际实验,可以使用decltype说明符。它的行为明确区分了三个主要值类别(xvalue、lvalue和prvalue)。
使用预处理器可以节省一些输入…
主要类别:
1 2 3 | #define IS_XVALUE(X) std::is_rvalue_reference<decltype((X))>::value #define IS_LVALUE(X) std::is_lvalue_reference<decltype((X))>::value #define IS_PRVALUE(X) !std::is_reference<decltype((X))>::value |
混合类别:
1 2 | #define IS_GLVALUE(X) IS_LVALUE(X) || IS_XVALUE(X) #define IS_RVALUE(X) IS_PRVALUE(X) || IS_XVALUE(X) |
现在,我们可以(几乎)复制CPP中有关价值类别的所有示例。
这里有一些C++ 17的例子(对于简洁的StasyAsHyt):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | void doesNothing(){} struct S { int x{0}; }; int x = 1; int y = 2; S s; static_assert(IS_LVALUE(x)); static_assert(IS_LVALUE(x+=y)); static_assert(IS_LVALUE("Hello world!")); static_assert(IS_LVALUE(++x)); static_assert(IS_PRVALUE(1)); static_assert(IS_PRVALUE(x++)); static_assert(IS_PRVALUE(static_cast<double>(x))); static_assert(IS_PRVALUE(std::string{})); static_assert(IS_PRVALUE(throw std::exception())); static_assert(IS_PRVALUE(doesNothing())); static_assert(IS_XVALUE(std::move(s))); // The next one doesn't work in gcc 8.2 but in gcc(trunk). Clang 7.0.0 and msvc 19.16 are doing fine. static_assert(IS_XVALUE(S().x)); |
一旦你找到了主要的分类,混合的分类就有点无聊了。
有关更多示例(和实验),请查看编译器资源管理器上的以下链接。不过,别费心看大会了。我添加了很多编译器,只是为了确保它能在所有常见的编译器中工作。