9

这是一些示例代码:

#include <iostream>

class Foo
{
public:
  explicit Foo(int x) : data(x) {};

  Foo& operator++()
  {
    data += 1;
    return *this;
  }

  void *get_addr()
  {
    return (void*)this;
  }

  friend Foo operator + (const Foo& lhs, const Foo& rhs);
  friend std::ostream& operator << (std::ostream& os, const Foo& f);

private:
  int data;
};

std::ostream& operator << (std::ostream& os, const Foo& f)
{
  return (os << f.data);
}

Foo operator + (const Foo& lhs, const Foo& rhs)
{
  return Foo(lhs.data + rhs.data);
}

void bar(Foo& f)
{
  std::cout << "bar(l-value ref)" << std::endl;
}

void bar(const Foo& f)
{
  std::cout << "bar(const l-value ref)" << std::endl;
}

void bar(Foo&& f)
{
  std::cout << "bar(r-value ref)" << std::endl;
}

int main()
{
  // getting the identity of the object
  std::cout << Foo(5).get_addr() << std::endl;  // Can write &Foo(5)
                                                // by overloading &
  // overload resolution
  bar(Foo(5));                                       // prints r-value ref

  // default copy assignment
  std::cout << (Foo(78) = Foo(86)) << std::endl;     // prints 86

  // mutating operations
  std::cout << (++Foo(5)) << std::endl;              // prints 6

  // more mutating operations
  std::cout << (++(Foo(78) + Foo(86))) << std::endl; // prints 165
  // overload resolution
  bar((Foo(78) + Foo(86)));                          // prints r-value ref
}

Foo(5) 之类的表达式是纯右值还是通用右值?我可以在这些表达式上调用 get_addr() 的事实是否意味着它们具有身份?或者我不能应用默认的 & 运算符(我的意思是非重载)这一事实是否意味着它们没有身份,因此是纯右值?

是否也可以公平地说,通过产生它的表达式产生的价值的可变性与这个价值分类正交?

4

2 回答 2

17

每个表达式都是一个,并且只有一个:

  • 左值
  • 极值
  • 公允价值

作为左值和 xvalue 的表达式的联合统称为glvalues

作为 xvalues 和 prvalues 的表达式的并集统称为rvalues

因此,xvalue 表达式被称为 glvalues 和 rvalues。

在Alf的答案中找到的方便图正确地说明了我用上面的文字描述的关系,并且在 C++ 标准的第 3.10 节中也可以找到,版本 C++11 及更高版本。

我上面所说的一切,我怀疑 OP 已经知道,只是从这个问题的标题的措辞。


琐事:

Bjarne Stroustrup 发明了这种表达式分类,这样做可能使整个右值引用提案免于在核心工作组中崩溃。我将永远感激不尽。


我要添加的是一种方法,可以让您自己发现任何表达式属于三个底部分类类别中的哪一个:lvalue、xvalue 或 prvalue。

#include <type_traits>
#include <typeinfo>
#include <iostream>
#ifndef _MSC_VER
#   include <cxxabi.h>
#endif
#include <memory>
#include <string>
#include <cstdlib>

template <typename T>
std::string
expression_name()
{
    typedef typename std::remove_reference<T>::type TR;
    std::unique_ptr<char, void(*)(void*)> own
           (
#ifndef _MSC_VER
                __cxxabiv1::__cxa_demangle(typeid(TR).name(), nullptr,
                                           nullptr, nullptr),
#else
                nullptr,
#endif
                std::free
           );
    std::string r = own != nullptr ? own.get() : typeid(TR).name();
    if (std::is_const<TR>::value)
        r += "const ";
    if (std::is_volatile<TR>::value)
        r += "volatile ";
    if (std::is_lvalue_reference<T>::value)
        r = "lvalue expression of type " + r;
    else if (std::is_rvalue_reference<T>::value)
        r = "xvalue expression of type " + r;
    else
        r = "prvalue expression of type " + r;
    return r;
}

上面的函数可以像这样使用:

std::cout << "some_expression is a " << expression_name<decltype(some_expression)>() << '\n';

它将回答这个 OP 的问题。例如:

int main()
{
    std::cout << "Foo(5) is a " << expression_name<decltype(Foo(5))>() << '\n';
    std::cout << "Foo(5).get_addr() is a " << expression_name<decltype(Foo(5).get_addr())>() << '\n';
    std::cout << "Foo(78) = Foo(86) is a " << expression_name<decltype(Foo(78) = Foo(86))>() << '\n';
    std::cout << "++Foo(5) is a " << expression_name<decltype(++Foo(5))>() << '\n';
    std::cout << "++(Foo(78) + Foo(86)) is a " << expression_name<decltype(++(Foo(78) + Foo(86)))>() << '\n';
    std::cout << "Foo(78) + Foo(86) is a " << expression_name<decltype(Foo(78) + Foo(86))>() << '\n';
    std::cout << "std::move(Foo(5)) is a " << expression_name<decltype(std::move(Foo(5)))>() << '\n';
    std::cout << "std::move(++Foo(5)) is a " << expression_name<decltype(std::move(++Foo(5)))>() << '\n';
}

对我来说打印出来:

Foo(5) is a prvalue expression of type Foo
Foo(5).get_addr() is a prvalue expression of type void*
Foo(78) = Foo(86) is a lvalue expression of type Foo
++Foo(5) is a lvalue expression of type Foo
++(Foo(78) + Foo(86)) is a lvalue expression of type Foo
Foo(78) + Foo(86) is a prvalue expression of type Foo
std::move(Foo(5)) is a xvalue expression of type Foo
std::move(++Foo(5)) is a xvalue expression of type Foo

使用此功能时要注意的一个方面:

decltype(variable_name)将给出变量名称的声明类型。如果您想在使用时发现表达式variable_name的值类别(与其声明的类型相反),那么您需要(variable_name)在使用时添加额外的括号decltype。那是:

decltype((variable_name))

表达式 variable_name的类型,而不是声明的类型variable_name

例如给出:

    Foo&& foo = Foo(5);
    std::cout << "foo is a " << expression_name<decltype(foo)>() << '\n';

这将错误地输出:

foo is a xvalue expression of type Foo

将额外的括号添加到decltype

    std::cout << "foo is a " << expression_name<decltype((foo))>() << '\n';

foo类型名称转换为表达式。现在输出是:

foo is a lvalue expression of type Foo

如果您不确定是否需要添加括号以获得正确答案,那么只需添加它们。添加它们不会使正确答案出错——除非您要获取变量的声明类型,而不是表达式的类型。在后一种情况下,您需要一个密切相关的功能:type_name<T>().

于 2013-12-21T17:54:02.383 回答
7

C++ 中的任何表达式都是左值右值。因此,您要求的是右值分类。为此,请检查 C++11 标准 §3.10/1 中显示分类树的图。

表达类别分类


有关更多信息(无需深入研究标准),请参阅什么是右值、左值、...。


关于

“是像 Foo(5) 右值还是纯右值这样的表达式”

prvalue 是 rvalue 的必要条件——因为它不可能是 lvalue。

prvalue “ (“pure” rvalue)是一个不是 xvalue 的右值”,而xvalue是“涉及右值引用的某些类型的表达式的结果” 构造函数调用不会产生右值引用,因此它不是 xvalue . 所以右值是一个纯右值,一个纯右值。

于 2013-12-21T08:50:49.930 回答