什么是右值,左值,x值,glvalues和prvalues?

时间:2010-08-30 15:02:42

标签: c++ expression c++-faq c++11

在C ++ 03中,表达式是 rvalue 左值

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

  1. 右值
  2. 左值
  3. x值
  4. glvalue
  5. prvalue
  6. 两个类别分为五类。

    • 这些新的表达类别是什么?
    • 这些新类别如何与现有的左值和左值类别相关联?
    • C ++ 0x中的rvalue和左值类别是否与C ++ 03中的相同?
    • 为什么需要这些新类别? WG21众神只是试图迷惑我们凡人吗?

12 个答案:

答案 0 :(得分:581)

我想这篇文章可能不是那么简短的介绍:n3055

整个大屠杀始于移动语义。一旦我们有可以移动而不是复制的表达式,突然很容易掌握规则,要求区分可以移动的表达式,以及在哪个方向上。

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

他们需要吗?如果我们希望放弃新功能,可能不会。但为了实现更好的优化,我们应该接受它们。

引用n3055

  • 左值(所谓的,历史上, 因为左值可以出现在 作业的左侧 表达式)指定一个函数或 一个东西。 [示例:如果E是 指针类型的表达式,然后是*E 是一个左值表达式 E的对象或功能 点。作为另一个例子, 调用一个函数的结果 返回类型是左值引用 左值。]
  • xvalue (an “eXpiring”值)也指一个 对象,通常接近其末尾 一生(所以它的资源可以 例如,被移动)。 xvalue是 某些种类的结果 涉及右值的表达式 引用。 [例子:The 调用一个函数的结果 返回类型是一个右值引用 xvalue。]
  • glvalue (“广义”左值)是左值 xvalue
  • 右值(所谓的, 历史上,因为价值可以 出现在一个右侧 赋值表达式)是一个xvalue, 临时对象或 其子对象,或者是其值 与对象无关。
  • prvalue (“纯”rvalue)是一个右值 那不是xvalue。 [例子:The 调用一个函数的结果 返回类型不是引用是 prvalue]

有问题的文件是这个问题的一个很好的参考,因为它显示了引入新命名法后标准的确切变化。

答案 1 :(得分:325)

  

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

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

  

- 一个左值(所谓的历史,因为左值可能出现在左右   作业的左侧   表达式)指定一个函数或   一个东西。 [例子:如果E是   那么指针类型的表达式   * E是一个左值表达式,指的是E的对象或函数   点。作为另一个例子,结果   调用返回的函数   type是左值引用是一个   左值。 - 例子]

     

- xvalue(an   “eXpiring”值)也指一个   对象,通常接近其末尾   一生(所以它的资源可能是   举动,例如)。 xvalue是   某些表达方式的结果   涉及右值参考(8.3.2)。 [   示例:调用a的结果   返回类型为的函数   右值参考是一个x值。 -结束   例子]

     

- 一个glvalue(“一般化”   左值(lvalue)是左值或左值。

     

-   一个rvalue(所谓的,历史上,   因为右值可以出现在   作业的右侧   表达式)是一个xvalue,一个临时的   对象(12.2)或其子对象,或   与a无关的值   对象

     

- 一个prvalue(“纯”rvalue)是   一个不是xvalue的rvalue。 [   示例:调用a的结果   返回类型不是a的函数   参考是一个prvalue。 a的价值   文字如12,7.3e5或true是   也是一个prvalue。 - 例子]

     

每   表达式恰好属于其中之一   中国的基本分类   这个分类:lvalue,xvalue或   prvalue。这个属性   表达式称为其值   类别。 [注:讨论   第5章中的每个内置运算符   表示它的值的类别   收益率和价值类别   它期望的操作数。例如,   内置赋值运算符期望   左操作数是左值和   右操作数是prvalue   并产生一个左值作为结果。   用户定义的运算符是函数,   以及它们的价值类别   期望和收益率由   他们的参数和返回类型。 -结束   注

我建议您阅读整个 3.10 Lvalues and rvalues 部分。

  

这些新类别如何与现有的左值和左值类别相关联?

再次:

Taxonomy

  

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

rvalues的语义特别随着移动语义的引入而发展。

  

为什么需要这些新类别?

这样就可以定义和支持移动构造/分配。

答案 2 :(得分:172)

我将从你的上一个问题开始:

  

为什么需要这些新类别?

C ++标准包含许多处理表达式值类别的规则。一些规则区分左值和右值。例如,当涉及到重载决策时。其他规则区分glvalue和prvalue。例如,您可以使用不完整或抽象类型的glvalue,但没有不完整或抽象类型的prvalue。在我们使用这个术语之前,实际需要区分glvalue / prvalue的规则是指lvalue / rvalue和它们无意中是错误的还是包含了很多解释和例外的规则......“除非rvalue是由于未命名右值参考......“。因此,将glvalues和prvalues的概念作为自己的名称似乎是一个好主意。

  

这些新的表达类别是什么?   这些新类别如何与现有的左值和左值类别相关?

我们仍然有与C ++ 98兼容的术语lvalue和rvalue。我们只是将rvalues分成两个子组,xvalues和prvalues,我们将lvalues和xvalues称为glvalues。 Xvalues是未命名的右值引用的一种新值类别。每个表达式都是以下三个中的一个:左值,右值,右值。维恩图看起来像这样:

    ______ ______
   /      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
}

答案 3 :(得分:150)

  

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

我觉得其他答案(尽管其中很多都是好的)确实能够抓住这个特定问题的答案。是的,这些类别等存在允许移动语义,但复杂性存在的原因之一。这是在C ++ 11中移动东西的一个不可侵犯的规则:

只有在毫无疑问安全的情况下才会移动。

这就是为什么存在这些类别的原因:能够谈论可以安全地离开它们的价值观,并谈论不存在的价值观。

在最早版本的r值参考中,运动很容易发生。 太容易了。当用户并不真正想要的时候,很容易隐藏地移动东西。

以下是可以安全移动的情况:

  1. 当它是临时或子对象时。 (prvalue)
  2. 当用户明确表示要移动
  3. 如果你这样做:

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

    这是做什么的?在规范的旧版本中,在5个值进入之前,这将引发一个移动。当然可以。您将rvalue引用传递给构造函数,因此它绑定到采用右值引用的构造函数。这很明显。

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

    如果它不是暂时的,并且您没有要求移动它,那么移动错误。

    显而易见的解决方案是使val成为左值。这意味着你无法从中移动。好的;它被命名,所以它是一个左值。

    一旦你这样做,你就不能再说SomeType&&意味着同样的事情。您现在已经区分了命名的右值引用和未命名的右值引用。那么,命名的右值引用是左值;这是我们上面的解决方案。那么我们称之为未命名的右值引用(上面Func的返回值)?

    这不是左值,因为你无法从左值移动。我们需要能够通过返回&&来移动;你怎么能明确地说要移动一些东西?毕竟,这就是std::move返回的内容。它不是rvalue(旧式),因为它可以位于等式的左侧(事情实际上有点复杂,请参阅this question和下面的注释)。它既不是左值也不是左值;这是一种新事物。

    我们拥有的是一个可以视为左值的值,除了它可以隐式移动。我们称之为xvalue。

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

    • prvalue实际上只是前一种rvalue的新名称,即它们是不是 xvalues的rvalues。

    • Glvalues是一个组中xvalues和lvalues的并集,因为它们共享许多属性。

    所以真的,这一切都归结为xvalues以及将运动限制在精确且仅限于某些地方的需要。这些地方由右值类别定义; prvalues是隐式移动,xvalues是显式移动(std::move返回xvalue)。

答案 4 :(得分:117)

恕我直言,关于其含义的最佳解释给了我们Stroustrup +考虑了Dániel SándorMohan的例子:

<强> Stroustrup的:

  

现在我非常担心。很明显,我们正陷入僵局或   一团糟或两者皆有。我花了午餐时间做分析看看哪个   属性(值)是独立的。只有两个   独立财产:

     
      
  • has identity - 即地址,指针,用户可以确定两个副本是否相同等。
  •   
  • can be moved from - 即我们被允许留下来复制&#34;在一些不确定但有效的状态
  •   
     

这使我得出结论,正好有三种   值(使用正则表达式使用大写字母的技巧)   表示否定 - 我匆忙):

     
      
  • iM:具有身份,无法移动
  •   
  • im:具有身份并且可以移动(例如,将左值转换为右值参考的结果)
  •   
  • Im:没有身份,可以从中移除。

         

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

  •   
     

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

     
      
  • i:有身份
  •   
  • m:可以从
  • 移出   
     

这让我把这个图放在了电路板上:   enter image description here

     

命名

     

我发现我们只有有限的自由命名:两点指向   左边(标有iMi)是人们或多或少的人   形式已经调用了lvalues和右边的两个点   (标有mIm)是具有或多或少形式的人   已拨打rvalues。这必须反映在我们的命名中。那是,   左边&#34;腿&#34; W的{​​{1}}应该包含与lvalue相关的名称   对#34;腿&#34; W应该包含与rvalue.相关的名称   整个讨论/问题来自于引入   rvalue引用和移动语义。这些概念根本不存在   在Strachey的世界中,只有rvalueslvalues。有人   

观察到的想法      
      
  • 每个value都是lvaluervalue
  •   
  • lvalue不是rvaluervalue不是lvalue
  •   
     

深深嵌入我们的意识,非常有用的属性,和   在标准草案中可以找到这种二分法的痕迹。我们   所有人都同意我们应该保留这些属性(并制作它们   精确)。这进一步限制了我们的命名选择。我观察到了   标准库措辞使用rvalue表示m(   概括),以便保持期望和文本   标准库应该命名W的右下角   rvalue.

     

这引发了对命名的集中讨论。首先,我们需要决定   在lvalue. lvalueiM还是概括i? LED   通过Doug Gregor,我们列出了核心语言措辞中的位置   单词lvalue有资格表示其中一个或另一个。一个   列表是在大多数情况下以及最棘手/最脆弱的文本中制作的   lvalue目前的意思是iM。这是左值的经典含义   因为&#34;在过去#34;什么都没动; move是一个新颖的概念   在C++0x。另外,命名W lvalue的topleft点给了我们   每个值都是lvaluervalue的属性,但不是两者。

     

因此,W的左上角是lvalue,右下角是   是rvalue.左下角和右上角是什么?   左下角是经典左值的推广,   允许移动。所以它是generalized lvalue.我们命名它   glvalue.你可以对缩写进行狡辩,但(我认为)不是   与逻辑。我们假设认真使用generalized lvalue   无论如何都会以某种方式缩写,所以我们最好这样做   立即(或冒险混淆)。 W的右上角较少   一般而不是右下角(现在,一如既往地称为rvalue)。那   point代表您可以移动的对象的原始纯概念   因为它不能再次引用(除了析构函数)。   与specialized rvalue相比,我喜欢短语generalized lvalue,但缩写为pure rvalue的{​​{1}}赢了(和   可能是正确的)。所以,W的左腿是prvalue和   lvalue而右腿是glvalueprvalue顺便说一句,   每个值都是glvalue或prvalue,但不是两者。

     

这会留下rvalue.W的顶部中间位置;也就是说,有的价值观   身份和可以移动。我们真的没有任何指导   我们为那些神秘的野兽命名。它们很重要   使用(草案)标准文本的人,但不太可能   成为家喻户晓的名字。我们没有发现任何真正的限制   命名指导我们,所以我们选择'x'作为中心,未知,   很奇怪,只有xpert,甚至是x-rated。

     

Steve showing off the final product

答案 5 :(得分:36)

引言

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

  • 右值参考
  • xvalue,glvalue,prvalue表达式值类别
  • 移动语义

如果我们想要了解新表达式值类别的概念,我们必须知道有rvalue和左值引用。 最好知道rvalues可以传递给非const rvalue引用。

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

如果我们引用N3337工作草案(最相似的草案到已公布的ISOC ++ 11标准)中标题为Lvalues和rvalues的小节,我们可以对价值类别的概念有一些直觉。

  

3.10 Lvalues和rvalues [basic.lval]

     

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

     
      
  • 左值(所谓的,历史上,因为左值可能出现在赋值表达式的左侧)指定一个函数   或一个物体。 [例子:如果E是指针类型的表达式,那么   * E是一个左值表达式,指的是E指向的对象或函数。作为另一个例子,调用函数的结果   其返回类型是左值引用是左值。 - 示例]
  •   
  • xvalue(“eXpiring”值)也指对象,通常接近其生命周期的末尾(以便可以移动其资源,   例)。 xvalue是某些表达式的结果   涉及右值参考(8.3.2)。 [例子:调用的结果   返回类型为右值引用的函数是xvalue。 -结束   例子]
  •   
  • glvalue(“generalized”左值)是左值或x值。
  •   
  • 一个右值(历史上所谓的,因为右值可能出现在赋值表达式的右侧)是一个x值,一个左右   临时对象(12.2)或其子对象,或不是
    的值   与对象相关联。
  •   
  • prvalue(“纯”rvalue)是一个不是xvalue的rvalue。 [示例:调用返回类型不是
    的函数的结果   参考是一个prvalue。文字的值,例如12,7.3e5或
      true也是一个prvalue。 - 示例]
  •   
     

每个表达式都属于基本的一个   此分类中的分类:lvalue,xvalue或prvalue。这个   表达式的属性称为其值类别。

但我不太确定这个小节是否足以清楚地理解这些概念,因为&#34;通常&#34;并不是一般的,&#34;接近其生命周期的结束&#34;实际上并不具体,&#34;涉及右值参考&#34;并不是很清楚,&#34;示例:调用返回类型为右值引用的函数的结果是xvalue。&#34;听起来像一条蛇咬着它的尾巴。

主要值类别

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

左值

当且仅当E指的是ALREADY具有使其可在E之外访问的身份(地址,名称或别名)的实体时,表达式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引用被视为lvalues,对对象的未命名rvalue引用被视为xvalues;对函数的右值引用被视为左值,无论是否命名。

#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

当且仅当E既不属于左值也不属于xvalue类别时,表达式E属于prvalue类别。

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类别。

右值

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

请注意,此定义表示当且仅当E指的是没有任何标识使其可在E YET之外访问的实体时,表达式E属于右值类别。

glvalues

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

实用规则

Scott Meyer published有一个非常有用的经验法则来区分rvalues和左值。

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

答案 6 :(得分:34)

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

随着它们的引入,据说未命名的右值引用求值为rvalue,这样重载解析更喜欢rvalue引用绑定,这将使它选择移动构造函数而不是复制构造函数。但是发现这会导致问题,例如Dynamic Types和资格问题。

要显示此信息,请考虑

int const&& f();

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

在pre-xvalue草案中,这是允许的,因为在C ++ 03中,非类类型的rvalues永远不会被cv限定。但是const意图适用于rvalue-reference情况,因为这里我们引用对象(= memory!),并且从非类rvalues中删除const主要是为了因为周围没有物体。

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

  • 未命名的右值引用变为 xvalues 。它们可以是合格的,并且可能具有不同的动态类型。它们像预期的那样在重载期间更喜欢rvalue引用,并且不会绑定到非const左值引用。

  • 之前的rvalue(文字,由非强制转换为非引用类型创建的对象)现在变为 prvalue 。它们在重载期间具有与xvalues相同的优先级。

  • 之前的左值是一个左值。

完成两个分组以捕获那些可以合格且可以具有不同动态类型( glvalues )的分组以及那些重载优先于右值引用绑定( rvalues )的分组。

答案 7 :(得分:24)

我一直在努力解决这个问题,直到我遇到cppreference.com对value categories的解释。

它实际上相当简单,但我发现它通常以难以记忆的方式解释。这里非常示意性地解释。我将引用页面的某些部分:

  

主要类别

     

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

     
      
  • 具有标识:可以确定表达式是否与另一个表达式引用相同的实体,例如通过比较对象的地址或它们识别的函数(直接或间接获得) );

  •   
  • 可以从移动:移动构造函数,移动赋值运算符或其他实现移动语义的函数重载可以绑定到表达式。

  •   
     

表达:

     
      
  • 具有身份并且无法移动被称为左值表达式;
  •   
  • 具有身份并且可以被移动称为 xvalue表达式;
  •   
  • 没有身份且可以移动被称为 prvalue表达式;
  •   
  • 没有身份,不能被移动不被使用。
  •   
     

左值

     

左值(“左值”)表达式是具有标识并且无法从移动的表达式。

     

rvalue(直到C ++ 11),prvalue(从C ++ 11开始)

     

prvalue(“纯rvalue”)表达式是没有身份的表达式,可以从移动。

     

x值

     

xvalue(“expiring value”)表达式是具有标识并且可以从移动的表达式。

     

glvalue

     

glvalue(“generalized lvalue”)表达式是一个左值或x值的表达式。它有身份。它可能会也可能不会被移走。

     

rvalue(自C ++ 11起)

     

rvalue(“right value”)表达式是prvalue或xvalue的表达式。它可以从移动。它可能有也可能没有身份。

答案 8 :(得分:16)

  

这些新类别如何与现有的左值和左值类别相关?

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

答案 9 :(得分:14)

上面的优秀答案的一个附录,即使在我阅读了Stroustrup并且认为我理解rvalue / lvalue区别之后,这一点让我很困惑。当你看到

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时,我的编译器会警告我吗?

答案 10 :(得分:9)

这些是 C++ 委员会用来定义 C++11 中移动语义的术语。 Here's the story

鉴于这些术语的精确定义、long lists of rules 或这张流行的图表,我发现它们很难理解:

value categories traditional diagram

在带有典型示例的维恩图上更容易:

value categories Venn diagram

基本上:

  • 每个表达式要么是左值要么是右值
  • 左值必须复制,因为它有标识,所以以后可以使用
  • 右值可以移动,因为它是临时的(纯右值)或显式移动的(xvalue)

现在,好问题是,如果我们有两个正交属性(“具有身份”和“可以移动”),那么完成左值、x值和右值的第四个类别是什么? 那将是一个没有身份(因此以后无法访问)且无法移动(需要复制其值)的表达式。这根本没有用,所以没有命名。

答案 11 :(得分:2)

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

要对值类别进行一些动手实验,可以使用decltype specifier。它的行为明确区分了三个主要值类别(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 on value category中的所有示例。

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

弄清楚主要类别后,混合类别很无聊。

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