O que são rvalues, lvalues, xvalues, glvalues ​​e prvalues?


1356

No C ++ 03, uma expressão é um rvalue ou um lvalue .

No C ++ 11, uma expressão pode ser uma:

  1. rvalue
  2. lvalue
  3. xvalue
  4. glvalue
  5. prvalue

Duas categorias se tornaram cinco categorias.

  • Quais são essas novas categorias de expressões?
  • Como essas novas categorias se relacionam com as categorias rvalue e lvalue existentes?
  • As categorias rvalue e lvalue no C ++ 0x são as mesmas do C ++ 03?
  • Por que essas novas categorias são necessárias? Os deuses do WG21 estão apenas tentando nos confundir meros mortais?

9
@ Philip Potter: Em C ++ 03? Sim. Um lvalue pode ser usado como um rvalue porque há uma conversão padrão de lvalue para rvalue.
James McNellis

14
@ Tyler: "Se você pode atribuir a ele, é um lvalue, caso contrário, é um rvalue." -> errada, você pode atribuir a rvalues classe: string("hello") = string("world").
Fredoverflow 30/08/10

4
Observe que esta é a categoria de valor. Existem mais propriedades que expressões podem ter. Isso inclui campo de bits (verdadeiro / falso), temporário (verdadeiro / falso) e tipo (o tipo dele).
Johannes Schaub - litb 30/08/10

30
Acho que o link de Fred acima é melhor do que qualquer uma das respostas aqui. O link está morto, no entanto. Foi movido para: stroustrup.com/terminology.pdf
R. Martinho Fernandes

74
em C ++ até mesmo seus tipos têm tipos
nielsbot

Respostas:


634

Acho que este documento pode servir como uma introdução não tão curta: n3055

Todo o massacre começou com a semântica do movimento. Uma vez que temos expressões que podem ser movidas e não copiadas, de repente as regras fáceis de entender exigiram distinção entre expressões que podem ser movidas e em qual direção.

Pelo que suponho que, com base no rascunho, a distinção de valor de R / L permanece a mesma, apenas no contexto de mover as coisas ficam confusas.

Eles são necessários? Provavelmente não, se quisermos perder os novos recursos. Mas, para permitir uma melhor otimização, provavelmente devemos adotá-los.

Citando n3055 :

  • Um lvalue (chamado historicamente, porque lvalues ​​pode aparecer no lado esquerdo de uma expressão de atribuição) designa uma função ou um objeto. [Exemplo: Se Eé uma expressão do tipo ponteiro, então *E é uma expressão lvalue referente ao objeto ou função para a qual E aponta. Como outro exemplo, o resultado da chamada de uma função cujo tipo de retorno é uma referência lvalue é um lvalue.]
  • Um xvalue (um valor "eXpiring") também se refere a um objeto, geralmente próximo ao final de sua vida útil (para que seus recursos possam ser movidos, por exemplo). Um xvalue é o resultado de certos tipos de expressões que envolvem referências a rvalue. [Exemplo: o resultado da chamada de uma função cujo tipo de retorno é uma referência rvalue é um xvalue.]
  • Um glvalue (lvalue "generalizado") é um lvalue ou um xvalue .
  • Um rvalue (chamado, historicamente, porque rvalues ​​pode aparecer no lado direito de uma expressão de atribuição) é um xvalue, um objeto temporário ou subobjeto do mesmo ou um valor que não está associado a um objeto.
  • Um prvalor (valor "puro") é um valor que não é um valor x. [Exemplo: o resultado da chamada de uma função cujo tipo de retorno não é uma referência é um pré-valor]

O documento em questão é uma ótima referência para essa pergunta, porque mostra as mudanças exatas no padrão que ocorreram como resultado da introdução da nova nomenclatura.


Obrigado, esta resposta é realmente útil! Mas meu compilador não concorda com seus exemplos de xvalues ​​e prvalues; eles são exatamente o oposto. Retornar por referência rvalue me dá um valor inicial, e retornar por valor me dá um valor x. Você os confundiu, ou minha cama de teste está quebrada? Eu tentei isso com o GCC 4.6.1, clang (de svn) e MSVC, e todos eles mostram o mesmo comportamento.
Kim Gräsman 9/03/2014

Opa, apenas segui o link e notei que os exemplos estão na fonte. Eu vou encontrar a minha cópia do padrão e verificar o que diz ...
Kim Gräsman

4
Eu uso as macros daqui para testar várias expressões: stackoverflow.com/a/6114546/96963 Pode ser que eles diagnosticem mal as coisas.
Kim Gräsman 9/03/2014

1
Adicionar o xvalue não é para a semântica de movimentação. Somente com lvalue e rvalue, a semântica de movimentação, a referência perfeita para frente e rvalue ainda funcionam bem. Eu acho que o xvalue é apenas para o operador decltype: se a expressão do operando for xvalue, o decltype fornecerá o tipo de referência rvalue.
ligando

1
@MuhamedCicak "Toda expressão é um lvalue ou um rvalue": é verdade; e o padrão (ou o documento n3055) não diz que é falso. O motivo pelo qual essa frase foi riscada é que você estava observando alterações entre duas versões do documento. A frase foi removida porque se tornou supérflua depois que uma explicação mais precisa foi adicionada.
max

337

Quais são essas novas categorias de expressões?

O FCD (n3092) tem uma excelente descrição:

- Um lvalue (assim chamado, historicamente, porque lvalues ​​pode aparecer no lado esquerdo de uma expressão de atribuição) designa uma função ou um objeto. [Exemplo: Se E é uma expressão do tipo ponteiro, * E é uma expressão de valor l que se refere ao objeto ou função para a qual E aponta. Como outro exemplo, o resultado da chamada de uma função cujo tipo de retorno é uma referência lvalue é um lvalue. Exemplo final]

- Um xvalue (um valor "eXpiring") também se refere a um objeto, geralmente próximo ao final de sua vida útil (para que seus recursos possam ser movidos, por exemplo). Um xvalue é o resultado de certos tipos de expressões que envolvem referências a rvalue (8.3.2). [Exemplo: o resultado da chamada de uma função cujo tipo de retorno é uma referência rvalue é um xvalue. Exemplo final]

- Um glvalue (lvalue "generalizado") é um lvalue ou um xvalue.

- Um rvalue (assim chamado, historicamente, porque rvalues ​​podem aparecer no lado direito de uma expressão de atribuição) é um xvalue, um objeto temporário (12.2) ou subobjeto do mesmo ou um valor que não está associado a um objeto.

- Um pré-valor ("puro" rvalue) é um rvalue que não é um xvalue. [Exemplo: o resultado da chamada de uma função cujo tipo de retorno não é uma referência é um prvalor. O valor de um literal como 12, 7.3e5 ou true também é um pré-valor. Exemplo final]

Toda expressão pertence a exatamente uma das classificações fundamentais nessa taxonomia: lvalue, xvalue ou prvalue. Essa propriedade de uma expressão é chamada de categoria de valor. [Nota: A discussão de cada operador interno na Cláusula 5 indica a categoria do valor que ele gera e as categorias de valor dos operandos que ele espera. Por exemplo, os operadores de atribuição internos esperam que o operando esquerdo seja um lvalue e que o operando direito seja um prvalor e produza um lvalue como resultado. Operadores definidos pelo usuário são funções e as categorias de valores que eles esperam e produzem são determinadas pelo tipo de parâmetro e retorno. - end note

Eu sugiro que você leia a seção inteira 3.10 Valores e valores, no entanto.

Como essas novas categorias se relacionam com as categorias rvalue e lvalue existentes?

Novamente:

Taxonomia

As categorias rvalue e lvalue no C ++ 0x são as mesmas do C ++ 03?

A semântica de rvalues ​​evoluiu particularmente com a introdução da semântica de movimento.

Por que essas novas categorias são necessárias?

Para que a movimentação / construção / atribuição possa ser definida e apoiada.


54
Eu gosto do diagrama aqui. Eu acho que pode ser útil começar a resposta com "Toda expressão pertence exatamente a uma das classificações fundamentais nesta taxonomia: lvalue, xvalue ou prvalue". Então é fácil usar o diagrama para mostrar que essas três classes fundamentais são combinadas para gerar glvalue e rvalue.
Aaron McDaid

2
"is glvalue" é equivalente a "is not prvalue" e "is rvalue" é equivalente a "is not lvalue".
Vladimir Reshetnikov

2
Este ajudou-me a mais: bajamircea.github.io/assets/2016-04-07-move-forward/... (Diagrama de Venn das categorias de valor)
John P

1
@AaronMcDaid Oi, pergunta rápida, se você / alguém pode responder ... Por que não nomear glvaluecomo lvaluee lvaluecomo plvalue, para ser consistente?
Vijay Chavda

184

Vou começar com sua última pergunta:

Por que essas novas categorias são necessárias?

O padrão C ++ contém muitas regras que lidam com a categoria de valor de uma expressão. Algumas regras fazem uma distinção entre lvalue e rvalue. Por exemplo, quando se trata de sobrecarregar a resolução. Outras regras fazem uma distinção entre glvalue e prvalue. Por exemplo, você pode ter um glvalue com um tipo incompleto ou abstrato, mas não há um pré-valor com um tipo incompleto ou abstrato. Antes de termos essa terminologia, as regras que realmente precisam distinguir entre glvalue / prvalue referem-se a lvalue / rvalue e elas estavam involuntariamente erradas ou continham muitas explicações e exceções à regra a la "... a menos que o rvalue seja devido a nome não identificado referência de valor ... ". Portanto, parece uma boa idéia apenas dar aos conceitos de glvalues ​​e pré-valores seu próprio nome.

Quais são essas novas categorias de expressões? Como essas novas categorias se relacionam com as categorias rvalue e lvalue existentes?

Ainda temos os termos lvalue e rvalue que são compatíveis com o C ++ 98. Acabamos de dividir os rvalues ​​em dois subgrupos, xvalues ​​e prvalues, e nos referimos a lvalues ​​e xvalues ​​como glvalues. Xvalues ​​são um novo tipo de categoria de valor para referências de rvalue sem nome. Toda expressão é um destes três: lvalue, xvalue, prvalue. Um diagrama de Venn ficaria assim:

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

Exemplos com funções:

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

Mas também não esqueça que as referências nomeadas rvalue são lvalues:

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

165

Por que essas novas categorias são necessárias? Os deuses do WG21 estão apenas tentando nos confundir meros mortais?

Não acho que as outras respostas (por mais boas que sejam) capturam realmente a resposta para essa pergunta em particular. Sim, essas categorias e outras existem para permitir a semântica de movimentação, mas a complexidade existe por um motivo. Esta é a regra inviolável de mover coisas no C ++ 11:

Você se moverá somente quando for inquestionavelmente seguro fazê-lo.

É por isso que essas categorias existem: poder falar sobre valores para onde é seguro sair delas e falar sobre valores onde não está.

Na versão mais antiga das referências de valor-r, o movimento acontecia facilmente. Muito facilmente. Facilmente o suficiente para que houvesse muito potencial para mover coisas implicitamente quando o usuário realmente não pretendia.

Aqui estão as circunstâncias em que é seguro mover algo:

  1. Quando é temporário ou subobjeto. (prvalor)
  2. Quando o usuário disse explicitamente para movê-lo .

Se você fizer isto:

SomeType &&Func() { ... }

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

O que isso faz? Nas versões mais antigas da especificação, antes que os 5 valores chegassem, isso provocaria uma mudança. Claro que sim. Você passou uma referência rvalue ao construtor e, portanto, ela se liga ao construtor que recebe uma referência rvalue. Isso é óbvio.

Há apenas um problema com isso; você não pediu para movê-lo. Ah, você pode dizer que isso &&deveria ter sido uma pista, mas isso não muda o fato de que ele infringiu a regra. valnão é temporário porque os temporários não têm nomes. Você pode ter prolongado a vida útil do temporário, mas isso significa que não é temporário ; é como qualquer outra variável de pilha.

Se não é temporário e você não pediu para movê-lo, o movimento está errado.

A solução óbvia é fazer valum valor. Isso significa que você não pode sair dela. Tudo bem; é nomeado, então é um valor l.

Depois de fazer isso, você não pode mais dizer que SomeType&&significa a mesma coisa em todos os lugares. Agora você fez uma distinção entre referências rvalue nomeadas e referências rvalue não nomeadas. Bem, as referências nomeadas rvalue são lvalues; essa foi a nossa solução acima. Então, o que chamamos de referências rvalue sem nome (o valor de retorno Funcacima)?

Não é um valor l, porque você não pode passar de um valor l. E precisamos poder mover retornando a &&; de que outra forma você poderia dizer explicitamente para mover alguma coisa? std::moveAfinal, é isso que retorna. Não é um rvalue (estilo antigo), porque pode estar no lado esquerdo de uma equação (as coisas são realmente um pouco mais complicadas, consulte esta pergunta e os comentários abaixo). Não é um valor nem um valor; é um novo tipo de coisa.

O que temos é um valor que você pode tratar como um valor l, exceto pelo fato de ser implicitamente móvel. Chamamos isso de xvalue.

Observe que xvalues ​​são o que nos leva a obter as outras duas categorias de valores:

  • Um prvalue é realmente apenas o novo nome para o tipo anterior de rvalue, ou seja, são os rvalues ​​que não são xvalues.

  • Glvalues ​​são a união de xvalues ​​e lvalues ​​em um grupo, porque compartilham muitas propriedades em comum.

Realmente, tudo se resume a valores-x e à necessidade de restringir o movimento a exatamente e somente a determinados lugares. Esses locais são definidos pela categoria rvalue; prvalues ​​são os movimentos implícitos e xvalues ​​são os movimentos explícitos ( std::moveretorna um xvalue).


11
@ Thomas: É um exemplo; não importa como ele cria o valor de retorno. O que importa é que ele retorne a &&.
Nicol Bolas

1
Nota: os valores prévios podem estar no lado esquerdo de uma equação, também - como em X foo(); foo() = X;... Por esse motivo fundamental, não consigo seguir completamente a excelente resposta acima até o fim, porque você realmente só faz a distinção entre o novo xvalue e o valor antigo do estilo, com base no fato de que pode estar no lhs.
Dan Nissenbaum 18/03/2013

1
Xser uma classe; X foo();sendo uma declaração de função e foo() = X();uma linha de código. (Deixei o segundo conjunto de parênteses no foo() = X();meu comentário acima.) Para uma pergunta que eu acabei de postar com esse uso destacado, consulte stackoverflow.com/questions/15482508/…
Dan Nissenbaum 18/03/2013

1
@DanNissenbaum "xvalue não pode estar no lado esquerdo da expressão de atribuição" - por que não? Veja ideone.com/wyrxiT
Mikhail

1
Resposta esclarecedora. Esta é sem dúvida a melhor resposta aqui. Isso me deu uma justificativa para introduzir as novas categorias de valor e o que aconteceu antes.
Nikos

136

IMHO, a melhor explicação sobre seu significado nos deu Stroustrup + levar em conta exemplos de Dániel Sándor e Mohan :

Stroustrup:

Agora eu estava seriamente preocupado. Claramente estávamos indo para um impasse ou uma bagunça ou ambos. Passei a hora do almoço fazendo uma análise para ver quais propriedades (de valores) eram independentes. Havia apenas duas propriedades independentes:

  • has identity - ie endereço, ponteiro, o usuário pode determinar se duas cópias são idênticas, etc.
  • can be moved from - ou seja, podemos deixar a fonte de uma "cópia" em algum estado indeterminado, mas válido

Isso me levou à conclusão de que existem exatamente três tipos de valores (usando o truque de notação regex de usar uma letra maiúscula para indicar um negativo - eu estava com pressa):

  • iM: tem identidade e não pode ser movido de
  • im: possui identidade e pode ser movido de (por exemplo, o resultado da conversão de um lvalue em uma referência rvalue)
  • Im: não possui identidade e pode ser movido de.

    A quarta possibilidade,, IM(não tem identidade e não pode ser movida) não é útil em C++(ou, eu acho) em qualquer outro idioma.

Além dessas três classificações fundamentais de valores, temos duas generalizações óbvias que correspondem às duas propriedades independentes:

  • i: tem identidade
  • m: pode ser movido de

Isso me levou a colocar este diagrama no quadro: insira a descrição da imagem aqui

Nomeação

Observei que tínhamos apenas liberdade limitada para nomear: os dois pontos à esquerda (rotulados iMe i) são o que as pessoas com mais ou menos formalidade chamaram lvaluese os dois pontos à direita (rotulados me Im) são o que as pessoas com mais ou menos formalidade ligou rvalues. Isso deve se refletir em nossa nomeação. Ou seja, a "perna" esquerda doW deve ter nomes relacionados lvaluee a "perna" direita do Wdeve ter nomes relacionados a rvalue.. Observe que toda essa discussão / problema surge da introdução de referências rvalue e da semântica de movimento. Essas noções simplesmente não existem no mundo de Strachey, consistindo de apenas rvaluese lvalues. Alguém observou que as idéias que

  • Todo valueé um lvalueou umrvalue
  • Um lvaluenão é umrvalue e um rvaluenão é umlvalue

estão profundamente enraizados em nossa consciência, propriedades muito úteis e traços dessa dicotomia podem ser encontrados em todo o rascunho do padrão. Todos concordamos que devemos preservar essas propriedades (e torná-las precisas). Isso restringiu ainda mais nossas escolhas de nomes. Observei que o texto padrão da biblioteca usarvalue significar m(a generalização), de modo que, para preservar a expectativa e o texto da biblioteca padrão, o ponto inferior direito da página Wdeve ser nomeado rvalue.

Isso levou a uma discussão focada em nomeação. Primeiro, precisamos decidir sobre lvalue.Deveria lvaluesignificar iMou a generalização i? Liderados por Doug Gregor, listamos os lugares no idioma principal em que a palavra lvaluefoi qualificada para significar um ou outro. Foi feita uma lista e, na maioria dos casos, e no texto mais complicado / quebradiço lvalueatualmente significa iM. Este é o significado clássico de lvalue porque "antigamente" nada era movido; moveé uma noção nova em C++0x. Além disso, nomear o ponto topleft de nos W lvaluedá a propriedade de que todo valor é um lvalueou um rvalue, mas não ambos.

Portanto, o ponto superior esquerdo do Wis lvaluee o ponto inferior direito é rvalue.O que isso faz com os pontos inferior esquerdo e superior direito? O ponto inferior esquerdo é uma generalização do valor clássico, permitindo a movimentação. Então é um generalized lvalue.nome que glvalue.você chama. Você pode discutir sobre a abreviação, mas (eu acho) não com a lógica. Assumimos que em uso sério generalized lvalue , de alguma forma, seria abreviado de qualquer maneira, portanto é melhor fazê-lo imediatamente (ou arriscar confusão). O ponto superior direito do W é menos geral que o inferior direito (agora, como sempre, chamado rvalue). Esse ponto representa a noção pura original de um objeto do qual você pode se mover, porque não pode ser referido novamente (exceto por um destruidor). Gostei da frase specialized rvalueem contraste comgeneralized lvalue maspure rvalue abreviada paraprvalue venceu (e provavelmente com razão). Portanto, a perna esquerda do W é lvaluee glvaluea perna direita é prvaluee rvalue.Aliás, todo valor é um valor glvalue ou prvalue, mas não ambos.

Isso deixa o topo do meio do W: im; isto é, valores que têm identidade e podem ser movidos. Realmente não temos nada que nos guie a um bom nome para essas bestas esotéricas. Eles são importantes para as pessoas que trabalham com o texto padrão (rascunho), mas é improvável que se tornem um nome familiar. Não encontramos nenhuma restrição real na nomeação para nos guiar, por isso escolhemos 'x' para o centro, o desconhecido, o estranho, o xpert apenas ou até a classificação x.

Steve exibindo o produto final


14
sim, é melhor ler as propostas e discussões originais do comitê C ++, do que o padrão, se você quiser entender o que elas significavam: D
Ivan Kush

8
Os literais não têm identidade e não podem ser movidos; no entanto, são úteis.
DrPizza

Eu só quero esclarecer uma coisa. int && f () {return 1; } e MyClass && g () {return MyClass (); } retornar xvalue, certo? Então, onde posso encontrar a identidade das expressões f (); e "g ();"? Eles têm identidade, porque há outra expressão na declaração de retorno, que se refere ao mesmo objeto a que se referem - eu entendi certo?
Dániel Sándor 21/10

6
@DrPizza De acordo com o padrão: literais de string são lvalues, todos os outros literais são prvalues. A rigor, você pode argumentar dizendo que literais não-string devem ser imóveis, mas não é assim que o padrão é escrito.
Brian Vandenberg

59

INTRODUÇÃO

ISOC ++ 11 (oficialmente ISO / IEC 14882: 2011) é a versão mais recente do padrão da linguagem de programação C ++. Ele contém alguns novos recursos e conceitos, por exemplo:

  • referências rvalue
  • Categorias de valor da expressão xvalue, glvalue, prvalue
  • mover semântica

Se quisermos entender os conceitos das novas categorias de valor de expressão, precisamos estar cientes de que existem referências rvalue e lvalue. É melhor saber que rvalues ​​podem ser passados ​​para referências não constantes de rvalue.

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

Podemos obter alguma intuição dos conceitos de categorias de valor se citarmos a subseção Lvalues ​​e rvalues ​​do rascunho N3337 (o rascunho mais semelhante ao padrão publicado ISOC ++ 11).

3.10 Valores e valores [basic.lval]

1 As expressões são categorizadas de acordo com a taxonomia na Figura 1.

  • Um lvalue (assim chamado, historicamente, porque lvalues ​​pode aparecer no lado esquerdo de uma expressão de atribuição) designa uma função ou um objeto. [Exemplo: Se E é uma expressão do tipo ponteiro, * E é uma expressão de valor l que se refere ao objeto ou função para a qual E aponta. Como outro exemplo, o resultado da chamada de uma função cujo tipo de retorno é uma referência lvalue é um lvalue. Exemplo final]
  • Um xvalue (um valor "eXpiring") também se refere a um objeto, geralmente próximo ao final de sua vida útil (para que seus recursos possam ser movidos, por exemplo). Um xvalue é o resultado de certos tipos de expressões que envolvem referências a rvalue (8.3.2). [Exemplo: o resultado da chamada de uma função cujo tipo de retorno é uma referência rvalue é um xvalue. Exemplo final]
  • Um glvalue (lvalue "generalizado") é um lvalue ou um xvalue.
  • Um rvalue (assim chamado, historicamente, porque rvalues ​​pode aparecer no lado direito de uma expressão de atribuição) é um xvalue, um
    objeto temporário (12.2) ou seu subobjeto ou um valor que não é
    associado a um objeto.
  • Um prvalor (valor "puro") é um valor que não é um valor x. [Exemplo: o resultado da chamada de uma função cujo tipo de retorno não é uma
    referência é um prvalor. O valor de um literal como 12, 7.3e5 ou
    true também é um pré-valor. Exemplo final]

Toda expressão pertence a exatamente uma das classificações fundamentais nessa taxonomia: lvalue, xvalue ou prvalue. Essa propriedade de uma expressão é chamada de categoria de valor.

Mas não tenho muita certeza de que essa subseção seja suficiente para entender claramente os conceitos, porque "geralmente" não é realmente geral "," no final de sua vida útil "não é realmente concreto", não é muito claro "envolver referências de valor", e "Exemplo: o resultado da chamada de uma função cujo tipo de retorno é uma referência rvalue é um xvalue". Parece que uma cobra está mordendo o rabo.

CATEGORIAS DE VALOR PRIMÁRIO

Toda expressão pertence a exatamente uma categoria de valor primário. Essas categorias de valor são lvalue, xvalue e prvalue.

lvalues

A expressão E pertence à categoria lvalue se, e somente se E, se refere a uma entidade que JÁ teve uma identidade (endereço, nome ou alias) que a torna acessível fora de 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

A expressão E pertence à categoria xvalue se, e somente se, for

- o resultado da chamada de uma função, implícita ou explicitamente, cujo tipo de retorno é uma referência rvalue ao tipo de objeto que está sendo retornado, ou

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

- uma conversão para uma referência rvalue ao tipo de objeto, ou

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

- uma expressão de acesso de membro da classe que designa um membro de dados não estáticos do tipo não de referência no qual a expressão do objeto é um valor x, ou

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

- uma expressão de ponteiro para membro em que o primeiro operando é um valor x e o segundo operando é um ponteiro para o membro de dados.

Observe que o efeito das regras acima é que referências de rvalue nomeadas a objetos são tratadas como lvalues ​​e referências de rvalue não nomeadas a objetos são tratadas como xvalues; As referências rvalue para funções são tratadas como lvalues, nomeadas ou não.

#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

A expressão E pertence à categoria prvalue se e somente se E não pertence à categoria lvalue nem à categoria 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;
}

CATEGORIAS DE VALOR MISTO

Existem mais duas importantes categorias de valores mistos. Essas categorias de valor são categorias rvalue e glvalue.

rvalues

A expressão E pertence à categoria rvalue se e somente se E pertencer à categoria xvalue ou à categoria prvalue.

Observe que essa definição significa que a expressão E pertence à categoria rvalue se e somente se E se refere a uma entidade que não teve nenhuma identidade que a torna acessível fora de E YET.

glvalues

A expressão E pertence à categoria glvalue se e somente se E pertencer à categoria lvalue ou à categoria xvalue.

UMA REGRA PRÁTICA

Scott Meyer publicou uma regra prática muito útil para distinguir rvalues ​​de lvalues.

  • Se você pode pegar o endereço de uma expressão, a expressão é um valor l.
  • Se o tipo de uma expressão é uma referência lvalue (por exemplo, T & ou const T & etc.), essa expressão é um lvalue.
  • Caso contrário, a expressão é um rvalue. Conceitualmente (e geralmente também de fato), rvalues ​​correspondem a objetos temporários, como aqueles retornados de funções ou criados por meio de conversões implícitas de tipo. A maioria dos valores literais (por exemplo, 10 e 5.3) também são rvalues.

3
Todos os exemplos para lvalues ​​e todos os exemplos para xvalues ​​também são exemplos para glvalues. Obrigado pela edição!
Dániel Sándor 30/01

1
Você está certo. As três categorias de valor primário são suficientes. Rvalue também não é necessário. Eu acho que rvalue e glvalue estão no padrão por conveniência.
Dániel Sándor 30/01

1
Tivemos dificuldade em entender struct As{void f(){this;}}a thisvariável é um pré-valor. Eu pensei que thisdeveria ser um valor. Até que o padrão 9.3.2 diga: No corpo de uma função de membro não estática (9.3), a palavra-chave this é uma expressão de pré-valor.
r0ng

3
@ r0ng thisé um prvalue mas *thisé um lvalue
Xeverous

1
"www" nem sempre tem o mesmo endereço. É um valor l, porque é uma matriz .
wally

35

As categorias do C ++ 03 são muito restritas para capturar a introdução de referências de rvalue corretamente nos atributos de expressão.

Com a introdução deles, foi dito que uma referência rvalue não nomeada é avaliada para um rvalue, de modo que a resolução de sobrecarga prefira ligações de referência rvalue, o que faria com que ele selecionasse mover construtores sobre construtores de cópia. Mas verificou-se que isso causa problemas ao redor, por exemplo, com tipos dinâmicos e com qualificações.

Para mostrar isso, considere

int const&& f();

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

Nos rascunhos com pré-valor x, isso era permitido, porque no C ++ 03, os valores de tipos não pertencentes à classe nunca são qualificados para cv. Mas pretende-se que isso constse aplique no caso rvalue-reference, porque aqui nós fazemos se referem a objetos (= memória!), E soltando const de rvalues não-classe é principalmente pela razão de que não há nenhum objeto ao redor.

O problema para tipos dinâmicos é de natureza semelhante. No C ++ 03, rvalues ​​do tipo de classe têm um tipo dinâmico conhecido - é o tipo estático dessa expressão. Para ter uma outra maneira, você precisa de referências ou desreferências, que avaliam como um valor l. Isso não é verdade com referências rvalues ​​sem nome, mas elas podem mostrar comportamento polimórfico. Então, para resolvê-lo,

  • referências rvalue sem nome se tornam xvalues . Eles podem ser qualificados e potencialmente ter seu tipo dinâmico diferente. Eles, como pretendido, preferem referências rvalue durante sobrecarga e não se ligam a referências não constantes.

  • O que anteriormente era um rvalue (literais, objetos criados por projeções para tipos que não são de referência) agora se torna um pré - valor . Eles têm a mesma preferência que xvalues ​​durante a sobrecarga.

  • O que anteriormente era um lvalue permanece um lvalue.

E dois agrupamentos são feitos para capturar aqueles que podem ser qualificados e podem ter tipos dinâmicos diferentes ( glvalues ) e aqueles em que a sobrecarga prefere a ligação de referência de rvalue ( rvalues ).


1
a resposta é obviamente razoável. xvalue é apenas rvalue, que pode ser qualificado como cv e digitar dinâmico!
Ligand

26

Eu luto com isso há muito tempo, até me deparar com a explicação cppreference.com das categorias de valor .

Na verdade, é bastante simples, mas acho que muitas vezes é explicado de uma maneira difícil de memorizar. Aqui é explicado de forma muito esquemática. Vou citar algumas partes da página:

Categorias principais

As categorias de valor primário correspondem a duas propriedades das expressões:

  • tem identidade : é possível determinar se a expressão se refere à mesma entidade que outra expressão, como comparar endereços dos objetos ou as funções que eles identificam (obtidos direta ou indiretamente);

  • pode ser movido de : mover construtor, operador de atribuição de movimento ou outra sobrecarga de função que implemente a semântica de movimento pode ser ligada à expressão.

Expressões que:

  • ter identidade e não pode ser movido de são chamados expressões lvalue ;
  • tem identidade e pode ser movido de são chamados expressões xvalue ;
  • não tem identidade e pode ser movido de são chamados expressões de valor-valor ;
  • não tem identidade e não pode ser movido de não são usados.

lvalue

Uma expressão lvalue ("valor à esquerda") é uma expressão que possui identidade e não pode ser movida .

rvalue (até C ++ 11), prvalue (desde C ++ 11)

Uma expressão prvalue ("pure rvalue") é uma expressão que não possui identidade e pode ser movida .

xvalue

Uma expressão xvalue ("expiring value") é uma expressão que possui identidade e pode ser movida .

glvalue

Uma expressão glvalue ("lvalue generalizado") é uma expressão que é um lvalue ou um xvalue. Ele tem identidade . Pode ou não ser movido de.

rvalue (desde C ++ 11)

Uma expressão rvalue ("valor correto") é uma expressão que é um prvalue ou um xvalue. Ele pode ser movido de . Pode ou não ter identidade.


1
Em alguns livros, XValues são mostrados para ter seus x vêm de "expert" ou "excepcional"
noɥʇʎԀʎzɐɹƆ

E, mais importante, a lista de exemplos abrangente.
Ciro Santilli escreveu:

19

Como essas novas categorias se relacionam com as categorias rvalue e lvalue existentes?

Um valor C ++ 03 ainda é um valor C ++ 11, enquanto um valor C ++ 03 é chamado um valor prvalor no C ++ 11.


14

Um adendo às excelentes respostas acima, em um ponto que me confundiu mesmo depois de ler Stroustrup e pensar que compreendia a distinção rvalue / lvalue. Quando você vê

int&& a = 3,

é muito tentador ler int&&como um tipo e concluir que aé um rvalor. Não é:

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

atem um nome e é ipso facto um lvalue. Não pense nisso &&como parte do tipo de a; é apenas algo dizendo o quea é permitido vincular.

Isso é importante principalmente para T&&argumentos de tipo em construtores. Se você escrever

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

você irá copiar _tpara t. Você precisa

Foo::Foo(T&& _t) : t{std::move(_t)} {}se você quiser se mudar. Será que meu compilador me avisou quando eu deixei de fora o move!


1
Eu acho que essa resposta poderia ser esclarecida. "Com o que aé permitido vincular": Claro, mas nas linhas 2 e 3 suas variáveis ​​são c & b, e não é uma que se liga, e o tipo de aé irrelevante aqui, não é? As linhas seriam as mesmas se afossem declaradas int a. A principal diferença real aqui é que, na linha 1, não é constnecessário vincular a 3. #
Felix Dombek

12

Como as respostas anteriores cobriram exaustivamente a teoria por trás das categorias de valor, há apenas outra coisa que gostaria de acrescentar: você pode realmente brincar com ela e testá-la.

Para algumas experiências práticas com as categorias de valor, você pode usar o especificador decltype . Seu comportamento distingue explicitamente entre as três categorias de valor primário (xvalue, lvalue e prvalue).

Usar o pré-processador nos poupa digitação ...

Categorias principais:

#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

Categorias misturadas:

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

Agora podemos reproduzir (quase) todos os exemplos da cppreference na categoria de valor .

Aqui estão alguns exemplos com o C ++ 17 (para terse 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)); 

As categorias mistas são meio chatas quando você descobre a categoria principal.

Para mais alguns exemplos (e experimentação), confira o seguinte link no compiler explorer . Mas não se preocupe em ler a montagem. Eu adicionei muitos compiladores apenas para garantir que funcione em todos os compiladores comuns.


Eu acho que #define IS_GLVALUE(X) IS_LVALUE(X) || IS_XVALUE(X)deveria, na verdade, #define IS_GLVALUE(X) (IS_LVALUE(X) || IS_XVALUE(X))olhar o que acontece se vocês &&dois IS_GLVALUE.
Gabriel Devillers
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.