Em C ++, que categorias (lvalue, rvalue, xvalue, etc.) podem ser expressões que produzem temporárias de tipo de class?

Aqui está algum código de exemplo:

#include  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 } 

São expressões como valores Foo (5) ou valores gerais? O fato de eu poder chamar get_addr () nessas expressões significa que elas têm identidade? Ou o fato de eu não poder aplicar o & -operador padrão (quero dizer não sobrecarregado) significa que eles não têm identidade e, portanto, são valores de valor?

Também é justo dizer que a mutabilidade do valor produzido através da expressão que o produz é ortogonal a essa sorting de valor?

Toda expressão é uma, e apenas uma, de:

  • lvalue
  • xvalue
  • valor

A união de expressões que são lvalues ​​e xvalues ​​são conhecidas coletivamente como glvalues .

A união de expressões que são xvalues ​​e prvalues ​​são conhecidos coletivamente como rvalues .

Assim, expressões xvalue são conhecidas como glvalues ​​e rvalues.

O diagrama útil encontrado na resposta de Alf ilustra corretamente o relacionamento que descrevi com as palavras acima, e também é encontrado na seção 3.10 dos padrões C ++, versões C ++ 11 e acima.

Tudo o que eu disse acima, eu suspeito que o OP já sabia, apenas a partir do texto do título desta questão.


Curiosidades:

Bjarne Stroustrup inventou essa sorting de expressões e, ao fazê-lo, talvez tenha salvado toda a proposta de referência de valor do colapso do Core Working Group. Eu serei eternamente grato.


O que eu estou adicionando é uma maneira de descobrir para você qual das três categorias de sorting inferior qualquer expressão se enquadra: lvalue, xvalue ou prvalue.

 #include  #include  #include  #ifndef _MSC_VER # include  #endif #include  #include  #include  template  std::string expression_name() { typedef typename std::remove_reference::type TR; std::unique_ptr 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::value) r += "const "; if (std::is_volatile::value) r += "volatile "; if (std::is_lvalue_reference::value) r = "lvalue expression of type " + r; else if (std::is_rvalue_reference::value) r = "xvalue expression of type " + r; else r = "prvalue expression of type " + r; return r; } 

A function acima pode ser usada como:

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

E vai responder a pergunta deste OP. Por exemplo:

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

Para mim, imprime:

 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 

Uma área a ter cuidado no uso desta function:

decltype(variable_name) fornecerá o tipo declarado do nome da variável. Se você quiser descobrir a categoria de valor da expressão quando o variable_name é usado (em oposição ao seu tipo declarado), então você precisa adicionar parênteses extras ao redor (variable_name) quando usado em decltype . Isso é:

 decltype((variable_name)) 

é o tipo da expressão variable_name e não o tipo declarado de variable_name .

Por exemplo, dado:

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

Isto irá erroneamente produzir:

 foo is a xvalue expression of type Foo 

Adicione os parênteses extras ao decltype :

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

para converter foo de um nome de tipo em uma expressão. Agora a saída é:

 foo is a lvalue expression of type Foo 

Se você não tiver certeza se precisa adicionar parênteses ou não para obter a resposta correta, basta adicioná-los. Adicioná-los não fará uma resposta correta errada - a menos que você esteja procurando obter o tipo declarado de uma variável, e não o tipo de uma expressão. E nesse último caso, você quer uma function relacionada: type_name() .

Qualquer expressão em C ++ é um lvalue ou um rvalue . Portanto, você está pedindo as classificações que são valores. Para isso, inspecione a figura que mostra a tree de classificações, na norma C ++ 11 §3.10 / 1.

Taxonomia da categoria de expressão


Para mais informações (sem se aprofundar no padrão), consulte O que são valores, valores, ….


A respeito de

“São expressões como Foo (5) rvalues ​​ou prvalue”

um valor é necessário um valor – pois não poderia muito bem ser um lvalue.

Um valor de produto “(” puro “rvalue) é um rvalue que não é um xvalue”, e um xvalue é “o resultado de certos tipos de expressões envolvendo referências rvalue” Uma chamada de construtor não produz uma referência rvalue, portanto não é um xvalue . Portanto, o valor é um valor, um valor puro.