在C ++ 03中,表达式可以是右值或左值。

在C ++ 11中,表达式可以是:


rvalue
lvalue
xvalue
prvalue

两个类别已变成五个类别。


这些是什么新功能表达式类别?
这些新类别如何与现有的右值和左值类别关联?
C ++ 0x中的rvalue和lvalue类别与C ++ 03中的类别相同吗?
为什么需要这些新类别? WG21众神只是在试图使我们仅仅是凡人吗?


评论

@Philip Potter:在C ++ 03中?是。左值可以用作右值,因为存在标准的左值到右值转换。

@Tyler:“如果可以分配,则为左值,否则为右值。” ->错误,您可以分配给类右值:string(“ hello”)= string(“ world”)。

请注意,这是值类别。表达式可以具有更多的属性。其中包括位字段(true / false),临时字段(true / false)和类型(其类型)。

我认为以上Fred的链接比这里的任何答案都更好。但是,链接已死。它已移至:stroustrup.com/terminology.pdf

在C ++中,即使您的类型也具有类型

#1 楼

我猜这个文档可能只是一个简短的介绍:n3055

整个屠杀始于move语义。一旦我们有了可以移动且不能复制的表达式,突然容易掌握的规则就要求在可以移动的表达式之间以及在哪个方向上进行区分。

根据我基于草稿的推测, / l值的区别保持不变,只是在移动事物变得混乱的情况下。

他们需要吗?如果我们希望放弃这些新功能,可能不会。但是,要允许更好的优化,我们可能应该接受它们。

引用n3055:


左值(过去称为“
”,因为左值可以出现在分配的
左侧
表达式)表示一个函数或一个对象。 [示例:如果E是指针类型的表达式,则*E
是引用
E 一个x值(一个
“ eXpiring”值)还指的是
对象,通常是在其
生命周期即将结束时(例如,以便其资源
被移动)。 xvalue是
涉及rvalue
引用的某些类型
表达式的结果。 [示例:调用其
返回类型为右值引用的函数的结果是
xvalue。]

glvalue(“广义”左值)为一个左值
或一个x值。
一个右值(从历史上讲,因为右值可能会出现在赋值表达式的右侧,所以称为
)是一个x值,
是一个临时值对象或其
子对象,或与对象不相关的值。
A
prvalue(“ pure” rvalue)是一个非xvalue的rvalue
。 [示例:调用

的函数的结果返回类型不是引用是
prvalue]


有问题的文档对于该问题是一个很好的参考,因为它显示了发生的标准的确切变化由于引入了新的术语。

评论


谢谢,这个答案真的很有帮助!但是我的编译器不同意您关于xvalues和prvalues的示例。他们是完全相反的。按右值引用返回给我一个prvalue,按值返回给我一个xvalue。你把它们弄混了吗,还是我的测试床坏了?我用GCC 4.6.1,clang(来自svn)和MSVC进行了尝试,它们都表现出相同的行为。

–金·格拉斯曼(KimGräsman)
2014年9月9日在16:56



我使用此处的宏来测试各种表达式:stackoverflow.com/a/6114546/96963可能是它们误诊了。

–金·格拉斯曼(KimGräsman)
2014年9月9日17:11

添加xvalue不适用于移动语义。仅使用左值和右值,移动语义,完善的前向和右值引用仍然可以正常工作。我认为xvalue仅用于decltype运算符:如果操作数表达式为xvalue,则decltype给出rvalue引用的类型。

–配体
14年7月29日在4:54

@MuhamedCicak“每个表达式都是左值或右值”:它是真的;标准(或文档n3055)没有说错。删除此句子的原因是您正在查看文档的两个版本之间的更改。该句子被删除,因为在添加了更精确的解释后,该句子变得多余了。

–max
17年8月21日在22:29



我的朋友们,我们正式没有香蕉了。

–阿维夫·科恩(Aviv Cohn)
6月27日22:41

#2 楼


这些新的表达式类别是什么?

FCD(n3092)有一个很好的描述:

—左值(历史上称为,因为左值可以出现在分配的
左侧
表达式)表示一个函数或一个对象。 [示例:如果E是指针类型的
表达式,则
* E是一个左值表达式,它引用E — xvalue(一个
“ eXpiring”值)也指一个
对象,通常在其
生命周期的尽头(因此其资源可能是
,例如)。 xvalue是某些类型的表达式的结果,其中包含右值引用(8.3.2)。 [
示例:调用返回类型为
右值引用的
函数的结果为xvalue。 —end
示例]
— glvalue(“广义”
lvalue)是lvalue或xvalue。

rvalue(过去称为“
,因为右值可能出现在赋值的右侧
表达式)是一个x值,一个临时
对象(12.2)或其子对象或

对象不相关的值。
-prvalue(“纯” rvalue)是不是xvalue的rvalue。 [
示例:调用返回类型不是
引用的
函数的结果是prvalue。
文字的值(例如12、7.3e5或true)也是
。 —结束示例]
每个
表达式完全属于该分类法的基本分类之一:左值,左值或右值。
表达式的此属性称为其值
类别。 [注意:第5章对每个内置运算符的讨论
指出了它产生的值的类别和
的值类别它期望的操作数。例如,
内置赋值运算符期望
左操作数是左值,而
右操作数是prvalue 它们的期望值和收益的类别由
其参数和返回类型确定。 —end
请注意

我还是建议您阅读整个章节3.10 Lvalues和rvalues。

这些新类别与现有的rvalue和lvalue类别有何关系?

再次:


C ++ 0x中的右值和左值类别是否与C ++ 03中的相同?

特别是随着move语义的引入,右值的语义得到了发展。

为什么需要这些新类别?

以便可以定义move构造/赋值并受支持。

评论


我喜欢这里的图表。我认为用“每个表达式完全属于该分类法的基本分类之一:左值,左值或右值”来回答这个问题可能会有用。然后,很容易使用该图显示将这三个基本类组合在一起以生成glvalue和rvalue。

–亚伦·麦克戴(Aaron McDaid)
13-10-20在12:30

“ is glvalue”等效于“ is not prvalue”,“ is rvalue”等效于“ is not lvalue”。

–弗拉基米尔·列谢尼科夫(Vladimir Reshetnikov)
18年9月7日在1:40

这对我最大的帮助:bajamircea.github.io/assets/2016-04-07-move-forward/…(价值类别的芬恩图)

– John P
19年3月9日在16:15

@AaronMcDaid嗨,您/有人可以回答的快速问题...为什么不将glvalue命名为lvalue,将lvalue命名为plvalue,以保持一致?

– Vijay Chavda
19年9月9日在9:01

#3 楼

我将从您的最后一个问题开始:


为什么需要这些新类别?


C ++标准包含许多处理表达式的值类别的规则。一些规则区分左值和右值。例如,涉及过载解析。其他规则区分glvalue和prvalue。例如,您可以有一个具有不完整或抽象类型的glvalue,但没有具有不完整或抽象类型的prvalue。在使用此术语之前,实际上需要区分称为lvalue / rvalue的glvalue / prvalue的规则,它们要么是无意中的错误,要么包含很多规则的解释和例外,例如:“ ...除非rvalue是由于未命名引起的”右值参考...”。因此,仅给glvalues和prvalues概念一个名称似乎是一个好主意。


这些新的表达式类别是什么?
这些新的类别如何实现?与现有的rvalue和lvalue类别有关?


我们仍然拥有与C ++ 98兼容的术语lvalue和rvalue。我们只是将右值分为两个子组,即x值和pr值,并且将l值和x值称为glvalues。 Xvalues是一种用于未命名的rvalue引用的新型值类别。每个表达式都是以下三个之一:lvalue,xvalue,prvalue。维恩图看起来像这样:

    ______ ______
   /      X      \
  /      / \      \
 |   l  | x |  pr  |
  \      \ /      /
   \______X______/
       gl    r


带有函数的示例:

int   prvalue();
int&  lvalue();
int&& xvalue();


但是也不要别忘了命名的右值引用是左值:

void foo(int&& t) {
  // t is initialized with an rvalue expression
  // but is actually an lvalue expression itself
}


#4 楼


为什么需要这些新类别? WG21众神只是在试图使我们仅仅是凡人吗?


我不认为其他答案(尽管其中很多是很好的)确实抓住了这个特定问题的答案。是的,存在这些类别等以允许移动语义,但是存在复杂性是出于一个原因。这是在C ++ 11中移动内容的一条严格规则:

只有在无疑安全的情况下才可以移动。

这就是为什么存在这些类别的原因:能够讨论可以安全地移离值的值,而可以讨论不安全移值的值。

在最早的r值引用版本中,移动很容易发生。太容易了很容易,当用户没有真正意图时,存在大量隐式移动内容的潜力。

在某些情况下,安全移动内容是很容易的:


是临时对象还是子对象。 (prvalue)
当用户明确地说要移动它时。

如果您这样做:

SomeType &&Func() { ... }

SomeType &&val = Func();
SomeType otherVal{val};


这是什么意思做?在规范的较旧版本中,在引入5个值之前,这会引起很大的变化。当然可以。您将右值引用传递给构造函数,因此它绑定到采用右值引用的构造函数。这很明显。

这只是一个问题。您没有要求移动它。哦,您可能会说&&应该是一个线索,但这并不能改变它违反规则的事实。 val不是临时的,因为临时者没有名字。您可能延长了临时文件的寿命,但这意味着它不是临时文件。就像其他任何堆栈变量一样。

如果它不是临时变量,并且您不要求移动它,那么移动是错误的。

显而易见的解决方案是将val设为左值。这意味着您不能离开它。好的;它被命名,所以它是一个左值。

一旦您这样做,就不能再说SomeType&&在任何地方都意味着同一件事。现在,您已经在命名右值引用和未命名右值引用之间进行了区分。好吧,命名的右值引用就是左值;那就是我们上面的解决方案。因此,我们怎么称呼未命名的右值引用(上面的Func的返回值)?

它不是左值,因为您不能从左值移动。并且我们需要能够通过返回&&来移动;您还能如何明确地说出要移动的东西?毕竟,这就是std::move返回的结果。它不是右值(旧式),因为它可以位于等式的左侧(实际情况稍微复杂一些,请参阅下面的问题和注释)。它既不是左值也不是右值。这是一种新事物。

我们拥有的是一个值,您可以将其视为左值,但它可以隐式地从中移出。我们称它为xvalue。

请注意,xvalues是使我们获得另外两类值的原因:


prvalue实际上只是的新名称以前的右值类型,即它们是不是xvalue的右值。
glvalues是一组xvalues和lvalues的并集,因为它们确实共享许多共同的属性。

所以,实际上,这全都归结为xvalues和限制移动的需求确切地且仅在某些地方。这些位置由右值类别定义; prvalue是隐式移动,xvalues是显式移动(std::move返回一个xvalue)。

评论


这很有趣,但是可以编译吗? Func不应该有退货声明吗?

– ThomasMcLeod
2012年7月7日在16:42

@Thomas:这是一个例子;无论如何创建返回值都无关紧要。重要的是它返回一个&&。

–尼科尔·波拉斯(Nicol Bolas)
2012年7月7日在17:11

注意:prvalue可以在等式的左侧,也可以-如X foo();中所示。 foo()= X; ...由于这个根本原因,我不能完全遵循上面的出色答案,因为您实际上只能根据新xvalue和旧式prvalue进行区分在lhs上。

–丹·尼森鲍姆(Dan Nissenbaum)
13年3月18日在16:18

X是一类; X foo();是一个函数声明,并且foo()= X();是一行代码。 (在上面的评论中,我在foo()= X();中省略了第二组括号。)有关我刚刚发布的突出显示此用法的问题,请参见stackoverflow.com/questions/15482508/…

–丹·尼森鲍姆(Dan Nissenbaum)
13年3月18日在23:15



@DanNissenbaum“ xvalue不能在赋值表达式的左侧”-为什么不呢?见ideone.com/wyrxiT

–米哈伊尔
16-3-20在22:22

#5 楼

恕我直言,关于其含义的最佳解释给了我们Stroustrup +并考虑了DánielSándor和Mohan的示例:

Stroustrup:


现在我很担心。显然,我们将陷入僵局或陷入混乱。我在午餐时间进行了分析,以查看(值)属性中的哪些是独立的。只有两个独立的属性:



has identity –即和地址,一个指针,用户可以确定两个副本是否相同,等等。

can be moved from –即,我们被允许以某种不确定但有效的状态留给“副本”的来源

这导致我得出结论,确切地说,存在三种类型的
值(使用大写字母的正则表达式表示法
表示否定–我很着急):



iM:具有身份,不能从中移出

im:具有身份,可以从中移出(例如,将左值强制转换为右值引用的结果)

Im:没有身份并可以移动。

第四种可能性IM(没有身份且不能移动)在任何其他语言中对C++(或我认为)都不有用。


除了这三个基本的值分类,我们
有两个明显的概括,对应于两个独立的属性:



i:具有标识

m:可以从以下位置移出:

这使我将这张图放到板上:


命名

我观察到名字的自由度有限:左边
的两个点(分别标记为iMi)就是拥有或多或少有名的人
手续称为lvalues和右边的两个点
(分别标记为mIm)被正规化的人称为rvalues。这必须在我们的命名中得到体现。也就是说,
W的左“支腿”应具有与lvalue相关的名称,而
W的右“支腿”应具有与rvalue.相关的名称我要注意
整个讨论/问题来自引入右值引用和移动语义。这些概念在Strachey的仅由rvalueslvalues组成的世界中根本不存在
。有人
观察到,每个value都是lvaluervalue的想法
lvalue不是rvaluervalue不是lvalue

深深地植根于我们的意识中,非常有用的特性,并且在整个标准草案中都可以找到这种二分法的痕迹。我们
都同意我们应该保留这些属性(并使其精确)。这进一步限制了我们的命名选择。我观察到
标准库的措词使用rvalue来表示m(即
泛化),以便保留W右下角的期望和文本。应命名为
rvalue.

这引起了对命名的重点讨论。首先,我们需要在lvalue.上确定
lvalue是指iM还是泛化i?由道格·格雷戈尔(Doug Gregor)领导的
,我们在核心语言措词中列出了
地方,其中lvalue一词可以表示一个或另一个。列出了
,并且在大多数情况下以及最棘手/最脆弱的文字中
lvalue当前的意思是iM。这是lvalue
的经典含义,因为“在过去”中什么都没有移动。 move是一个新颖的概念
C++0x中。同样,命名W lvalue的左上角点为我们提供了
属性,即每个值都是lvaluervalue,但不能同时是这两个值。

因此,W的左上角点是lvalue和右下角点是rvalue.是什么使左下角点和右上角点呢?
左下点是经典左值的概括,
允许移动。因此它是一个generalized lvalue.。我们将其命名为
glvalue.您可以对缩写词有所疑问,但是(我认为)不是
具有逻辑。我们假设严重使用generalized lvalue
会以某种方式被缩写,因此我们最好立即进行
(否则会造成混淆)。 W的右上角点一般小于右下角点(现在称为rvalue)。该
点代表可以移动的对象的原始纯概念,因为它无法再次引用(析构函数除外)。
我喜欢短语specialized rvaluegeneralized lvalue相反,但是将pure rvalue缩写为prvalue胜出(而且
可能是正确的)。因此,W的左腿是lvalue
glvalue,右腿是prvaluervalue.
每个值都是glvalue或prvalue,但不是两者。

这会留下W的顶部中间部分:im;也就是说,具有
标识并且可以移动的值。我们真的没有任何东西可以引导我们为那些神秘的野兽起好名字。它们对于
使用(草稿)标准文本的人员很重要,但不太可能成为家喻户晓的名字。我们没有在
命名上找到任何实际约束来指导我们,因此我们选择了'x'作为中心,未知数,
奇数,仅xpert或什至x级。 br />



评论


是的,如果您想了解C ++ comitee的原始建议和讨论,则比标准要好:D

–伊万·库什(Ivan Kush)
16 Sep 2'在8:35



文字没有身份,也不能从中移走;它们仍然很有用。

– DrPizza
17年4月15日在8:45

我只想澄清一件事。 int && f(){返回1; }和MyClass && g(){返回MyClass();返回xvalue,对不对?然后我在哪里可以找到表达式f()的身份?和“ g();”?它们具有身份,因为return语句中还有另一个表达式,它们引用的对象与它们所引用的对象相同-我理解正确吗?

–丹尼尔·桑多(DanielSándor)
17-10-21在22:45

@DrPizza根据标准:字符串文字是左值,所有其他文字是pr值。严格来说,您可以说非字符串文字不可移动,但这并不是标准的编写方式。

–布赖恩·范登堡(Brian Vandenberg)
17/12/21在2:35



#6 楼

简介

ISOC ++ 11(正式为ISO / IEC 14882:2011)是C ++编程语言标准的最新版本。它包含一些新功能和概念,例如:


右值引用
xvalue,glvalue,prvalue表达式值类别
移动语义

如果我们想了解新表达式值类别的概念,我们必须知道存在右值和左值引用。
最好将右值传递给非常量右值引用。

int& r_i=7; // compile error
int&& rr_i=7; // OK


如果引用工作草案N3337中与Lvalues和rvalues有关的小节(与已发布的最相似的草案),我们可以对价值类别的概念有所了解。 ISOC ++ 11标准)。


3.10左值和右值[basic.lval]

1根据图1中的分类法对表达式进行分类。 />

一个左值(历史上称为,因为左值可能出现在赋值表达式的左侧)指定一个函数或一个对象。 [示例:如果E是指针类型的表达式,则
* E是一个左值表达式,它引用E指向的对象或函数。作为另一个示例,调用返回类型为左值引用的函数
的结果为左值。 —结束示例]
xvalue(“ eXpiring”值)也指向对象,通常在其生命周期即将结束时(例如,
,可以移动其资源)。 xvalue是某些类型的表达式的结果,其中包含rvalue引用(8.3.2)。 [示例:调用
返回类型是右值引用的函数的结果是xvalue。 —end
示例]
glvalue(“广义”左值)是左值或x值。
rvalue(之所以称为历史值,是因为rvalue可能出现在右手边。一个赋值表达式)是一个xvalue,a
临时对象(12.2)或其子对象,或者与对象没有关联的值。
prvalue(“纯” rvalue)是不是xvalue的rvalue。 [示例:调用返回类型不是
引用的函数的结果是prvalue。文字的值(例如12、7.3e5或
true)也是prvalue。 —结束示例]

每个表达式完全属于该分类法中的基本
类别之一:左值,左值或右值。表达式的此
属性称为其值类别。


但是我不太确定该小节足以清楚地理解概念,因为“通常”是并不是很一般,“寿命即将结束”不是很具体,“涉及右值引用”还不是很清楚,“示例:调用返回类型为右值引用的函数的结果是一个xvalue。”听起来像蛇在咬它的尾巴。

主值类别

每个表达式都完全属于一个主值类别。这些值类别是左值,x值和右值类别。

左值

当且仅当E指代ALREADY具有一个使其可以在E之外访问的标识(地址,名称或别名)。

#include <iostream>

int i=7;

const int& f(){
    return i;
}

int main()
{
    std::cout<<&"www"<<std::endl; // The expression "www" in this row is an lvalue expression, because string literals are arrays and every array has an address.  

    i; // The expression i in this row is an lvalue expression, because it refers to the same entity ...
    i; // ... as the entity the expression i in this row refers to.

    int* p_i=new int(7);
    *p_i; // The expression *p_i in this row is an lvalue expression, because it refers to the same entity ...
    *p_i; // ... as the entity the expression *p_i in this row refers to.

    const int& r_I=7;
    r_I; // The expression r_I in this row is an lvalue expression, because it refers to the same entity ...
    r_I; // ... as the entity the expression r_I in this row refers to.

    f(); // The expression f() in this row is an lvalue expression, because it refers to the same entity ...
    i; // ... as the entity the expression f() in this row refers to.

    return 0;
}


xvalues

表达式E属于xvalue类别当且仅当

-调用函数的结果(无论是隐式还是显式),其返回类型是对要返回的对象类型的右值引用,或者

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;
}


-转换为对对象类型的右值引用,或者

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;
}


-指定非静态的类成员访问表达式非引用类型的数据成员,其中对象表达式是xvalue,或者

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,第二个操作数是指向数据成员的指针。

请注意,上述规则的作用是命名的rvalue引用对象被视为左值,对对象的未命名右值引用被视为x值;不论是否命名,对函数的右值引用均视为左值。

#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;
}
<且仅当且仅当表达式E属于prvalue类别时如果E既不属于左值也不属于xvalue类别。

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

当且仅当E属于xvalue类别或prvalue类别时,表达式E才属于rvalue类别。 。

请注意,此定义意味着,当且仅当E引用的实体没有任何身份使其无法在E YET之外访问时,该表达式E才属于右值类别。 >
glvalues

当且仅当E属于lvalue类别或xvalue类别时,表达式E才属于glvalue类别。

一个实用规则

Scott Meyer发布了一条非常有用的经验法则,用于区分左值与左值。



如果可以使用表达式的地址,
如果表达式的类型是左值引用(例如T&或const T&等),则该表达式为左值。
否则,表达式为右值。从概念上(通常实际上也实际上),右值对应于临时对象,例如从函数返回的对象或通过隐式类型转换创建的对象。大多数文字值(例如10和5.3)也是右值。



评论


lvalues的所有示例和xvalues的所有示例也都是glvalues的示例。感谢您的编辑!

–丹尼尔·桑多(DanielSándor)
16年1月30日在19:28

你是对的。三个主要值类别已足够。右值也不是必需的。为了方便起见,我认为rvalue和glvalue在标准中。

–丹尼尔·桑多(DanielSándor)
16 Jan 30'19:52



很难理解struct As {void f(){this;}}的this变量是prvalue。我认为这应该是一个左值。直到标准9.3.2声明:在非静态(9.3)成员函数的主体中,关键字this是prvalue表达式。

–r0ng
18年1月24日在23:07



@ r0ng这是prvalue,但是*这是lvalue

–大量
18-3-22在13:56

“ www”并不总是具有相同的地址。它是一个左值,因为它是一个数组。

– wally
19年4月11日在13:55

#7 楼

C ++ 03的类别太受限制,无法正确地将右值引用引入表达式属性中。

引入它们后,据说未命名的右值引用的计算结果为右值,因此重载解析将首选右值引用绑定,这将使其选择移动构造器而不是复制构造器。但是,发现这会引起各种问题,例如动态类型和限定条件。

为了说明这一点,请考虑

int const&& f();

int main() {
  int &&i = f(); // disgusting!
}


在xvalue之前的草稿中,这是允许的,因为在C ++ 03中,non的rvalue类类型从不具备简历资格。但是我们希望const适用于右值引用的情况,因为这里我们确实引用了对象(=内存!),而从非类右值中删除const主要是因为周围没有对象。

动态类型的问题具有相似的性质。在C ++ 03中,类类型的右值具有已知的动态类型-它是该表达式的静态类型。因为要采用另一种方法,所以需要引用或取消引用,它们的值均为左值。未命名的右值引用并非如此,但是它们可以显示多态行为。因此,要解决此问题,


未命名的右值引用将成为xvalues。它们可以被限定,并且可能具有不同的动态类型。它们确实像预期的那样在重载期间更喜欢右值引用,并且不会绑定到非常量左值引用。
以前是右值(文字,通过强制转换为非引用类型创建的对象)现在变成了右值。在重载期间,它们与xvalues具有相同的首选项。
以前是左值的仍然是左值。

完成了两个分组,以捕获那些可以限定且可以具有不同动态类型(glvalues)的分组,以及那些重载首选rvalue引用绑定(rvalues)的分组。

评论


答案显然是合理的。 xvalue只是可以通过cv限定和动态输入的rvalue!

–配体
2014年7月29日在6:28

#8 楼

我已经为此苦苦挣扎了很长时间,直到遇到cppreference.com对值类别的解释。
它实际上很简单,但是我发现它经常以难以记忆的方式进行解释。在此非常示意性地解释。我将引用页面的某些部分:

主要类别
主要值类别对应于表达式的两个属性:


具有标识:可以通过比较对象的地址或它们所标识的功能(直接或间接获得)来确定该表达式是否与另一个表达式引用相同的实体;


来自:移动构造函数,移动赋值运算符或实现移动语义的另一个函数重载可以绑定到表达式。


具有以下标识的表达式:不能移出的称为左值表达式;
具有身份,可以移出的称为xvalue表达式;
没有身份,可以移出的称为prvalue表达式;
没有标识,并且不能从其移出。

左值
左值(“左值”)表达式是具有标识并且不能被移动的表达式from。
rvalue(直到C ++ 11),prvalue(从C ++ 11开始)
prvalue(“ pure rvalue”)表达式是不具有标识并且可以从中移出的表达式。
xvalue
xvalue(“过期值”)表达式是具有标识并且可以从中移出的表达式。
glvalue
glvalue(“广义lvalue”)表达式是是左值或x值的表达式。它具有身份。
rvalue(自C ++ 11起)
rvalue(“正确的值”)表达式是prvalue或xvalue的表达式。可以移动。它可能具有或不具有身份。


评论


在某些书中,x值显示其x来自“专家”或“例外”

–noɥʇʎԀʎzɐɹƆ
16年7月1日在13:29


#9 楼


这些新类别如何与现有的rvalue和lvalue类别相关?


C ++ 03左值仍然是C ++ 11左值,而C ++ 03 rvalue在C ++ 11中称为prvalue。

#10 楼

由于前面的答案详尽地涵盖了价值类别背后的理论,因此我想补充一件事:您可以实际使用它并进行测试。

进行一些动手实验值类别,则可以使用decltype指定符。它的行为明确区分了三个主要值类别(xvalue,lvalue和prvalue)。

使用预处理程序可以节省一些键入时间...

主要类别:

#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


混合类别:

#define IS_GLVALUE(X) (IS_LVALUE(X) || IS_XVALUE(X))
#define IS_RVALUE(X) (IS_PRVALUE(X) || IS_XVALUE(X))


现在我们可以重现(几乎)cppreference中值类别的所有示例。

以下是C ++ 17的一些示例(用于简短的static_assert):

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 9.1. Clang 7.0.0 and msvc 19.16 are doing fine.
static_assert(IS_XVALUE(S().x)); 


一旦您使用了混合类别,就会感到无聊找到主要类别。

有关更多示例(和实验),请查看以下有关编译器资源管理器的链接。不过,不要打扰阅读程序集。我添加了很多编译器,只是为了确保它可以在所有常见的编译器中正常工作。

评论


我认为#define IS_GLVALUE(X)IS_LVALUE(X)|| IS_XVALUE(X)实际上应该是#define IS_GLVALUE(X)(IS_LVALUE(X)|| IS_XVALUE(X)),否则请查看如果您&&两个IS_GLVALUE会发生什么。

–加百利·戴维勒(Gabriel Devillers)
19年11月14日在23:18

#11 楼

上面的出色答案的一个附录,甚至在我读完Stroustrup并认为我理解右值/左值之间的区别后,仍然使我感到困惑。当您看到

int&& a = 3时,很容易将int&&读为一种类型并得出结论a是一个右值。它不是:

int&& a = 3;
int&& c = a; //error: cannot bind 'int' lvalue to 'int&&'
int& b = a; //compiles


a有一个名称,并且实际上是一个左值。不要将&&视为a类型的一部分;只是告诉您a可以绑定到什么。

这对于构造函数中的T&&类型参数尤其重要。如果您编写

Foo::Foo(T&& _t) : t{_t} {}

,则将_t复制到t中。如果要移动,则需要

Foo::Foo(T&& _t) : t{std::move(_t)} {}。当我放弃move时,编译器会警告我吗?

评论


我认为这个答案可以澄清。 “允许a绑定到什么”:当然可以,但是在第2行和第3行中,您的变量是c&b,并且不是绑定到a的变量,并且a的类型在这里无关紧要,不是吗?如果将a声明为int a,则行将相同。实际的主要区别是第1行中的a不必绑定为3即可。

– Felix Dombek
16 Dec 20'1:20