В C++ какие категории (lvalue, rvalue, xvalue и т. д.) могут ли попасть выражения, которые производят временные типы класса?

вот пример кода:

#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) prvalues или общие rvalues? Означает ли тот факт, что я могу вызвать get_addr() для этих выражений, что они имеют идентичность? Или тот факт, что я не могу применить оператор по умолчанию &- (я имею в виду не перегруженный), означает, что они не имеют идентичности и поэтому являются prvalues?

также справедливо сказать, что изменчивость произведенного значения через выражение, которое произвело его, ортогональна этому классификация ценностей?

2 ответов


выражение одно и только одно из:

  • lvalue
  • xvalue
  • prvalue

объединение выражений, которые являются lvalues и xvalues известны как glvalues.

объединение выражений, которые являются xvalues и prvalues известны как правосторонние значения.

таким образом, выражения xvalue известны как glvalues и значений rvalue.

удобная диаграмма, найденная в Альф's ответ правильно иллюстрирует отношения, которые я описал словами выше, а также находится в разделе 3.10 стандартов C++, версии C++11 и выше.

все, что я сказал выше, я подозреваю, что ОП уже знал, только из формулировки названия этого вопроса.


Общая информация:

Бьярне Страуструп изобрел эту классификацию выражения, и в это, возможно, спасло все ссылочное предложение rvalue от распад основной рабочей группы. Я буду вечно благодарен.


то, что я добавляю, - это способ выяснить для себя, в какую из трех нижних категорий классификации попадает любое выражение: 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';

и это ответит на вопрос этой операции. Для пример:

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>().


любое выражение в C++или an lvalue или R-значения. Следовательно, вы просите классификации, которые являются rvalues. Для этого проверьте рисунок, показывающий дерево классификаций, в стандарте C++11 §3.10 / 1.

Expression category taxonomy


для получения дополнительной информации (без углубления в стандарт) см. что такое rvalues, lvalues,....


о

"являются выражениями типа Foo (5) rvalues или prvalue"

prvalue необходимо rvalue-потому что это не может быть lvalue.

A prvalue "("pure" rvalue) - это rvalue, которое не является xvalue", и xvalue является " результатом определенных видов выражений, включающих ссылки на rvalue " вызов конструктора не создает ссылку на rvalue, следовательно, это не xvalue. Таким образом, rvalue является prvalue, чистым rvalue.