Que sont les valeurs rvalues, lvalues, xvalues, glvalues ​​et prvalues?


1357

En C ++ 03, une expression est soit une rvalue soit une lvalue .

En C ++ 11, une expression peut être:

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

Deux catégories sont devenues cinq catégories.

  • Quelles sont ces nouvelles catégories d'expressions?
  • Comment ces nouvelles catégories sont-elles liées aux catégories rvalue et lvalue existantes?
  • Les catégories rvalue et lvalue en C ++ 0x sont-elles les mêmes qu'en C ++ 03?
  • Pourquoi ces nouvelles catégories sont-elles nécessaires? Les dieux du WG21 essaient-ils simplement de nous confondre de simples mortels?

9
@Philip Potter: En C ++ 03? Oui. Une lvalue peut être utilisée comme rvalue car il existe une conversion standard de lvalue en rvalue.
James McNellis

14
@Tyler: "Si vous pouvez lui affecter, c'est une lvalue, sinon, c'est une rvalue." -> Faux, vous pouvez attribuer à rvalues de classe: string("hello") = string("world").
fredoverflow

4
Notez qu'il s'agit de la catégorie de valeur. Les expressions peuvent avoir plus de propriétés. Ceux-ci incluent le champ de bits (vrai / faux), temporaire (vrai / faux) et le type (le type de celui-ci).
Johannes Schaub - litb

30
Je pense que le lien de Fred ci-dessus est meilleur que toutes les réponses ici. Le lien est cependant mort. Il a été déplacé vers: stroustrup.com/terminology.pdf
R. Martinho Fernandes

74
en C ++ même vos types ont des types
nielsbot

Réponses:


634

Je suppose que ce document pourrait servir d'introduction pas si courte: n3055

Tout le massacre a commencé avec le mouvement sémantique. Une fois que nous avons des expressions qui peuvent être déplacées et non copiées, des règles soudainement faciles à saisir exigeaient une distinction entre les expressions qui peuvent être déplacées et dans quelle direction.

D'après ce que je suppose sur la base du brouillon, la distinction de la valeur r / l reste la même, seulement dans le contexte où les choses bougent deviennent désordonnées.

Sont-ils nécessaires? Probablement pas si nous souhaitons renoncer aux nouvelles fonctionnalités. Mais pour permettre une meilleure optimisation, nous devrions probablement les adopter.

Citant n3055 :

  • Une lvalue (soi-disant, historiquement, car lvalues ​​pouvait apparaître sur le côté gauche d'une expression d'affectation) désigne une fonction ou un objet. [Exemple: si Eest une expression de type pointeur, alors *E est une expression lvalue faisant référence à l'objet ou à la fonction vers laquelle E pointe. Comme autre exemple, le résultat de l'appel d'une fonction dont le type de retour est une référence lvalue est une lvalue.]
  • Une valeur x (une valeur «eXpiring») fait également référence à un objet, généralement vers la fin de sa durée de vie (afin que ses ressources puissent être déplacées, par exemple). Une valeur x est le résultat de certains types d'expressions impliquant des références rvalue. [Exemple: le résultat de l'appel d'une fonction dont le type de retour est une référence rvalue est une valeur x.]
  • Une glvalue ( lvalue «généralisée») est une lvalue ou une xvalue .
  • Une valeur r (ce que l'on appelle historiquement, car les valeurs pouvaient apparaître à droite d'une expression d'affectation) est une valeur x, un objet temporaire ou un sous-objet de celle-ci, ou une valeur qui n'est pas associée à un objet.
  • Une valeur pr (valeur «pure») est une valeur r qui n'est pas une valeur x. [Exemple: le résultat de l'appel d'une fonction dont le type de retour n'est pas une référence est une valeur]

Le document en question est une excellente référence pour cette question, car il montre les changements exacts dans la norme qui se sont produits à la suite de l'introduction de la nouvelle nomenclature.


Merci, cette réponse est vraiment utile! Mais mon compilateur n'est pas d'accord avec vos exemples pour xvalues ​​et prvalues; ils sont exactement le contraire. Le retour par référence rvalue me donne une valeur pr, et le retour par valeur me donne une valeur x. Les avez-vous mélangés ou mon banc d'essai est-il cassé? J'ai essayé cela avec GCC 4.6.1, clang (de svn) et MSVC, et ils montrent tous le même comportement.
Kim Gräsman

Oups, je viens de suivre le lien et j'ai remarqué que les exemples sont dans la source. Je vais chercher ma copie de la norme et vérifier ce qu'elle dit ...
Kim Gräsman

4
J'utilise les macros d'ici pour tester diverses expressions: stackoverflow.com/a/6114546/96963 Il se pourrait qu'elles diagnostiquent mal les choses.
Kim Gräsman

1
L'ajout de la valeur x n'est pas pour la sémantique de déplacement. Seulement avec lvalue et rvalue, la sémantique de déplacement, la référence parfaite vers l'avant et la valeur rvalue fonctionnent toujours bien. Je pense que la xvalue est juste pour l'opérateur decltype: si l'expression d'opérande est xvalue, le decltype donne le type de référence rvalue.
ligand

1
@MuhamedCicak "Chaque expression est soit une lvalue soit une rvalue": c'est vrai; et la norme (ou le document n3055) ne dit pas que c'est faux. La raison pour laquelle cette phrase a été barrée est que vous envisagiez des changements entre deux versions du document. La phrase a été supprimée car elle est devenue superflue après l'ajout d'une explication plus précise.
max

337

Quelles sont ces nouvelles catégories d'expressions?

Le FCD (n3092) a une excellente description:

- Une lvalue (ainsi appelée, historiquement, car lvalues ​​pouvait apparaître sur le côté gauche d'une expression d'affectation) désigne une fonction ou un objet. [Exemple: si E est une expression de type pointeur, alors * E est une expression de valeur se référant à l'objet ou à la fonction vers laquelle E pointe. Comme autre exemple, le résultat de l'appel d'une fonction dont le type de retour est une référence lvalue est une lvalue. —Fin exemple]

- Une valeur x (une valeur «eXpiring») fait également référence à un objet, généralement vers la fin de sa durée de vie (afin que ses ressources puissent être déplacées, par exemple). Une valeur x est le résultat de certains types d'expressions impliquant des références rvalue (8.3.2). [Exemple: Le résultat de l'appel d'une fonction dont le type de retour est une référence rvalue est une xvalue. —Fin exemple]

- Une glvalue (lvalue «généralisée») est une lvalue ou une xvalue.

- Une rvalue (ainsi appelée, historiquement, car les rvalues ​​peuvent apparaître sur le côté droit d'une expression d'affectation) est une valeur x, un objet temporaire (12.2) ou un sous-objet de celui-ci, ou une valeur qui n'est pas associée à un objet.

- Une prvalue (rvalue «pure») est une rvalue qui n'est pas une xvalue. [Exemple: Le résultat de l'appel d'une fonction dont le type de retour n'est pas une référence est une valeur. La valeur d'un littéral tel que 12, 7.3e5 ou true est également une valeur. —Fin exemple]

Chaque expression appartient exactement à l'une des classifications fondamentales de cette taxonomie: lvalue, xvalue ou prvalue. Cette propriété d'une expression est appelée sa catégorie de valeur. [Remarque: La discussion de chaque opérateur intégré dans l'Article 5 indique la catégorie de la valeur qu'il génère et les catégories de valeur des opérandes qu'il attend. Par exemple, les opérateurs d'affectation intégrés s'attendent à ce que l'opérande de gauche soit une lvalue et que l'opérande de droite soit une prvalue et produisent une lvalue comme résultat. Les opérateurs définis par l'utilisateur sont des fonctions, et les catégories de valeurs qu'ils attendent et produisent sont déterminées par leurs paramètres et types de retour. —Fin note

Je vous suggère de lire l'intégralité de la section 3.10 Lvalues ​​et rvalues .

Comment ces nouvelles catégories sont-elles liées aux catégories rvalue et lvalue existantes?

Encore:

Taxonomie

Les catégories rvalue et lvalue en C ++ 0x sont-elles les mêmes qu'en C ++ 03?

La sémantique des rvalues ​​a évolué en particulier avec l'introduction de la sémantique des mouvements.

Pourquoi ces nouvelles catégories sont-elles nécessaires?

Pour que la construction / affectation du mouvement puisse être définie et prise en charge.


54
J'aime le schéma ici. Je pense qu'il pourrait être utile de commencer la réponse par "Chaque expression appartient exactement à l'une des classifications fondamentales de cette taxonomie: lvalue, xvalue ou prvalue." Ensuite, il est facile d'utiliser le diagramme pour montrer que ces trois classes fondamentales sont combinées pour faire glvalue et rvalue.
Aaron McDaid du

2
"is glvalue" est équivalent à "is not prvalue", et "is rvalue" est équivalent à "is not lvalue".
Vladimir Reshetnikov

2
Celui-ci m'a le plus aidé: bajamircea.github.io/assets/2016-04-07-move-forward/… (diagramme de Venn des catégories de valeur)
John P

1
@AaronMcDaid Bonjour, question rapide si vous / quelqu'un pouvez répondre ... Pourquoi ne pas nommer au glvaluefur lvalueet à lvaluemesure plvalue, pour être cohérent?
Vijay Chavda

184

Je vais commencer par votre dernière question:

Pourquoi ces nouvelles catégories sont-elles nécessaires?

La norme C ++ contient de nombreuses règles qui traitent de la catégorie de valeur d'une expression. Certaines règles font une distinction entre lvalue et rvalue. Par exemple, en matière de résolution de surcharge. D'autres règles font une distinction entre glvalue et prvalue. Par exemple, vous pouvez avoir une valeur gl avec un type incomplet ou abstrait mais il n'y a pas de valeur avec un type incomplet ou abstrait. Avant d'avoir cette terminologie, les règles qui devaient réellement faire la distinction entre glvalue / prvalue référées à lvalue / rvalue étaient soit involontairement erronées soit contenaient beaucoup d'explications et d'exceptions à la règle a la "... à moins que la rvalue ne soit due à un nom rvalue reference ... ". Donc, cela semble être une bonne idée de simplement donner aux concepts de glvalues ​​et prvalues ​​leur propre nom.

Quelles sont ces nouvelles catégories d'expressions? Comment ces nouvelles catégories sont-elles liées aux catégories rvalue et lvalue existantes?

Nous avons toujours les termes lvalue et rvalue qui sont compatibles avec C ++ 98. Nous venons de diviser les rvalues ​​en deux sous-groupes, xvalues ​​et prvalues, et nous appelons lvalues ​​et xvalues ​​comme glvalues. Les Xvalues ​​sont un nouveau type de catégorie de valeur pour les références rvalue sans nom. Chaque expression est l'une de ces trois: lvalue, xvalue, prvalue. Un diagramme de Venn ressemblerait à ceci:

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

Exemples avec fonctions:

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

Mais n'oubliez pas non plus que les références rvalue nommées sont des lvalues:

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

165

Pourquoi ces nouvelles catégories sont-elles nécessaires? Les dieux du WG21 essaient-ils simplement de nous confondre de simples mortels?

Je ne pense pas que les autres réponses (bien que bon nombre d'entre elles le soient) capturent vraiment la réponse à cette question particulière. Oui, ces catégories et autres existent pour permettre la sémantique des mouvements, mais la complexité existe pour une raison. C'est la seule règle inviolable pour déplacer des éléments en C ++ 11:

Tu ne bougeras que s'il est incontestablement sûr de le faire.

C'est pourquoi ces catégories existent: pouvoir parler de valeurs là où il est sûr de s'en éloigner, et parler de valeurs là où il n'y en a pas.

Dans la première version des références de valeur r, le mouvement s'est produit facilement. Aussi facilement. Assez facilement qu'il y avait beaucoup de potentiel pour déplacer implicitement des choses lorsque l'utilisateur n'en avait pas vraiment l'intention.

Voici les circonstances dans lesquelles il est sécuritaire de déplacer quelque chose:

  1. Lorsqu'il s'agit d'un objet temporaire ou secondaire. (valeur)
  2. Lorsque l'utilisateur a explicitement dit de le déplacer .

Si tu fais ça:

SomeType &&Func() { ... }

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

Qu'est-ce que cela fait? Dans les anciennes versions de la spécification, avant l'entrée des 5 valeurs, cela provoquait un mouvement. Bien sûr que oui. Vous avez passé une référence rvalue au constructeur, et donc il se lie au constructeur qui prend une référence rvalue. Cela est évident.

Il y a juste un problème avec cela; vous n'avez pas demandé de le déplacer. Oh, vous pourriez dire que cela &&aurait dû être un indice, mais cela ne change pas le fait qu'il a enfreint la règle. valn'est pas temporaire car les temporaires n'ont pas de nom. Vous pouvez avoir prolongé la durée de vie du temporaire, mais cela signifie qu'il n'est pas temporaire ; c'est comme n'importe quelle autre variable de pile.

Si ce n'est pas temporaire et que vous n'avez pas demandé de le déplacer, alors c'est mal de bouger .

La solution évidente est de faire valune valeur l. Cela signifie que vous ne pouvez pas vous en éloigner. OK bien; il est nommé, donc c'est une valeur.

Une fois que vous faites cela, vous ne pouvez plus dire que cela SomeType&&signifie la même chose partout. Vous avez maintenant fait une distinction entre les références rvalue nommées et les références rvalue non nommées. Eh bien, les références de rvalue nommées sont des lvalues; c'était notre solution ci-dessus. Alors, comment appelons-nous les références rvalue sans nom (la valeur de retour Funcci-dessus)?

Ce n'est pas une lvalue, car vous ne pouvez pas passer d'une lvalue. Et nous devons pouvoir bouger en renvoyant un &&; Sinon, comment pourriez-vous explicitement dire de déplacer quelque chose? C'est ce qui std::moverevient, après tout. Ce n'est pas une valeur (à l'ancienne), car elle peut être du côté gauche d'une équation (les choses sont en fait un peu plus compliquées, voir cette question et les commentaires ci-dessous). Ce n'est ni une lvalue ni une rvalue; c'est un nouveau genre de chose.

Ce que nous avons est une valeur que vous pouvez traiter comme une valeur l, sauf qu'elle peut être implicitement déplacée de. Nous l'appelons une valeur x.

Notez que les valeurs x sont ce qui nous fait gagner les deux autres catégories de valeurs:

  • Une valeur n'est en réalité que le nouveau nom du type précédent de valeur r, c'est-à-dire que ce sont les valeurs r qui ne sont pas des valeurs x.

  • Les glvalues ​​sont l'union des valeurs x et des valeurs l dans un groupe, car elles partagent beaucoup de propriétés en commun.

Donc, vraiment, tout se résume aux valeurs x et à la nécessité de restreindre le mouvement à certains endroits seulement. Ces lieux sont définis par la catégorie rvalue; prvalues ​​sont les mouvements implicites et xvalues ​​sont les mouvements explicites ( std::moverenvoie une valeur x).


11
@Thomas: C'est un exemple; peu importe comment il crée la valeur de retour. Ce qui importe, c'est qu'il renvoie a &&.
Nicol Bolas

1
Remarque: les valeurs peuvent être sur le côté gauche d'une équation, également - comme dans X foo(); foo() = X;... Pour cette raison fondamentale, je ne peux pas tout à fait suivre l'excellente réponse ci-dessus jusqu'à la fin, car vous ne faites vraiment que la distinction entre la nouvelle xvalue et l'ancienne prvalue, basée sur le fait qu'elle peut être sur le lhs.
Dan Nissenbaum

1
Xêtre une classe; X foo();étant une déclaration de fonction, et foo() = X();étant une ligne de code. (J'ai laissé le deuxième ensemble de parenthèses dans foo() = X();mon commentaire ci-dessus.) Pour une question que je viens de publier avec cette utilisation mise en évidence, voir stackoverflow.com/questions/15482508/…
Dan Nissenbaum

1
@DanNissenbaum "xvalue ne peut pas être sur le côté gauche de l'expression d'affectation" - pourquoi pas? Voir ideone.com/wyrxiT
Mikhail

1
Réponse éclairante. C'est sans aucun doute la meilleure réponse ici. Cela m'a donné la justification de l'introduction des nouvelles catégories de valeur et de ce qui s'est passé avant.
Nikos

136

À mon humble avis, la meilleure explication de sa signification nous a donné Stroustrup + prendre en compte des exemples de Dániel Sándor et Mohan :

Stroustrup:

Maintenant j'étais sérieusement inquiet. De toute évidence, nous nous dirigions vers une impasse, un gâchis ou les deux. J'ai passé le déjeuner à faire une analyse pour voir lesquelles des propriétés (des valeurs) étaient indépendantes. Il n'y avait que deux propriétés indépendantes:

  • has identity - c'est-à-dire et adresse, un pointeur, l'utilisateur peut déterminer si deux copies sont identiques, etc.
  • can be moved from - c'est-à-dire que nous sommes autorisés à laisser à la source d'une "copie" dans un état indéterminé, mais valide

Cela m'a amené à la conclusion qu'il existe exactement trois types de valeurs (en utilisant l'astuce de notation regex d'utiliser une lettre majuscule pour indiquer un négatif - j'étais pressé):

  • iM: a une identité et ne peut pas être déplacé de
  • im: a une identité et peut être déplacé (par exemple, le résultat du transtypage d'une valeur l en une référence rvalue)
  • Im: n'a pas d'identité et peut être déplacé.

    La quatrième possibilité, IM(n'a pas d'identité et ne peut pas être déplacée) n'est pas utile dans C++(ou, je pense) dans aucune autre langue.

En plus de ces trois classifications fondamentales des valeurs, nous avons deux généralisations évidentes qui correspondent aux deux propriétés indépendantes:

  • i: a une identité
  • m: peut être déplacé de

Cela m'a amené à mettre ce schéma au tableau: entrez la description de l'image ici

Appellation

J'ai observé que nous n'avions qu'une liberté limitée de nommer: les deux points à gauche (étiquetés iMet i) sont ce que les gens avec plus ou moins de formalité ont appelés lvalueset les deux points à droite (étiquetés met Im) sont ce que les gens avec plus ou moins de formalité ont appelé rvalues. Cela doit se refléter dans notre nom. C'est-à-dire que la "jambe" gauche du Wdevrait avoir des noms liés à lvalueet la "jambe" droite du Wdevrait avoir des noms liés à rvalue.Je note que toute cette discussion / problème provient de l'introduction de références rvalue et de la sémantique de déplacement. Ces notions n'existent tout simplement pas dans le monde de Strachey consistant en juste rvalueset lvalues. Quelqu'un a observé que les idées

  • Tout valueest soit un lvalueou unrvalue
  • Un lvaluen'est pas un rvalueet un rvaluen'est pas unlvalue

sont profondément ancrées dans notre conscience, des propriétés très utiles, et des traces de cette dichotomie peuvent être trouvées partout dans le projet de norme. Nous avons tous convenu que nous devions préserver ces propriétés (et les préciser). Cela a encore limité nos choix de dénomination. J'ai observé que le libellé standard de la bibliothèque rvaluesignifie m(la généralisation), de sorte que pour préserver l'attente et le texte de la bibliothèque standard, le point inférieur droit Wdu rvalue.

Cela a conduit à une discussion ciblée sur la dénomination. Tout d'abord, nous devions décider de lvalue.Doit lvaluesignifier iMou la généralisation i? Sous la direction de Doug Gregor, nous avons énuméré les endroits dans le libellé de la langue de base où le mot lvalueétait qualifié pour signifier l'un ou l'autre. Une liste a été dressée et dans la plupart des cas et dans le texte le plus délicat / cassant lvaluesignifie actuellement iM. C'est le sens classique de lvalue parce que "dans l'ancien temps", rien n'a bougé; moveest une notion nouvelle en C++0x. De plus, nommer le point le plus haut de la W lvaluenous donne la propriété que chaque valeur est un lvalueou un rvalue, mais pas les deux.

Donc, le point en haut à gauche de l' West lvalueet le point en bas à droite est rvalue.Qu'est-ce que cela fait des points en bas à gauche et en haut à droite? Le point en bas à gauche est une généralisation de la valeur l classique, permettant le déplacement. C'est donc un generalized lvalue.Nous l'avons nommé glvalue.Vous pouvez chipoter sur l'abréviation, mais (je pense) pas avec la logique. Nous avons supposé qu'en cas d'utilisation sérieuse, il generalized lvalue serait de toute façon abrégé de toute façon, donc nous ferions mieux de le faire immédiatement (ou risquerions de confondre). Le point supérieur droit du W est moins général que le coin inférieur droit (maintenant, comme toujours, appelé rvalue). Ce point représente la notion pure d'origine d'un objet à partir duquel vous pouvez vous déplacer car il ne peut plus être référencé (sauf par un destructeur). J'ai aimé l'expression specialized rvaluecontrairement àgeneralized lvalue maispure rvalueabrégé en prvaluevainqueur (et probablement à juste titre). Ainsi, la jambe gauche du W est lvalueet glvalueet la jambe droite est prvalueet rvalue.Incidemment, chaque valeur est soit une valeur gl, soit une valeur, mais pas les deux.

Cela laisse le milieu de la partie supérieure du W: im; c'est-à-dire des valeurs qui ont une identité et qui peuvent être déplacées. Nous n'avons vraiment rien qui nous guide vers un bon nom pour ces bêtes ésotériques. Ils sont importants pour les personnes travaillant avec le (projet) de texte standard, mais il est peu probable qu'ils deviennent un nom familier. Nous n'avons trouvé aucune contrainte réelle sur la dénomination pour nous guider, alors nous avons choisi 'x' pour le centre, l'inconnu, l'étrange, le xpert uniquement, ou même le x-évalué.

Steve montre le produit final


14
oui, il vaut mieux lire les propositions et discussions originales du comité C ++, que la norme, si vous voulez comprendre ce qu'elles signifiaient: D
Ivan Kush

8
Les littéraux n'ont pas d'identité et ne peuvent pas être déplacés; ils sont néanmoins utiles.
DrPizza

Je veux juste clarifier une chose. int && f () {return 1; } et MyClass && g () {return MyClass (); } renvoie xvalue, non? Alors où puis-je trouver l'identité des expressions f (); et "g ();"? Ils ont une identité, car il y a une autre expression dans la déclaration de retour, qui fait référence au même objet auquel ils se réfèrent - est-ce que je comprends bien?
Dániel Sándor

6
@DrPizza Selon la norme: les littéraux de chaîne sont lvalues, tous les autres littéraux sont prvalues. À strictement parler, vous pourriez faire un argument pour dire que les littéraux non-chaîne devraient être immobiles, mais ce n'est pas ainsi que la norme est écrite.
Brian Vandenberg

59

INTRODUCTION

ISOC ++ 11 (officiellement ISO / IEC 14882: 2011) est la version la plus récente de la norme du langage de programmation C ++. Il contient de nouvelles fonctionnalités et concepts, par exemple:

  • références rvalue
  • Catégories de valeurs d'expression xvalue, glvalue, prvalue
  • déplacer la sémantique

Si nous voulons comprendre les concepts des nouvelles catégories de valeurs d'expression, nous devons être conscients qu'il existe des références rvalue et lvalue. Il est préférable de savoir que les valeurs r peuvent être transmises à des références rvalue non const.

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

Nous pouvons acquérir une certaine intuition des concepts de catégories de valeurs si nous citons la sous-section intitulée Lvalues ​​et rvalues ​​du projet de travail N3337 (le projet le plus similaire à la norme ISOC ++ 11 publiée).

3.10 Lvalues ​​et rvalues ​​[basic.lval]

1 Les expressions sont classées selon la taxonomie de la figure 1.

  • Une lvalue (ainsi appelée, historiquement, car des lvalues ​​peuvent apparaître sur le côté gauche d'une expression d'affectation) désigne une fonction ou un objet. [Exemple: si E est une expression de type pointeur, alors * E est une expression de valeur se référant à l'objet ou à la fonction vers laquelle E pointe. Comme autre exemple, le résultat de l'appel d'une fonction dont le type de retour est une référence lvalue est une lvalue. —Fin exemple]
  • Une valeur x (une valeur «eXpiring») fait également référence à un objet, généralement vers la fin de sa durée de vie (afin que ses ressources puissent être déplacées, par exemple). Une valeur x est le résultat de certains types d'expressions impliquant des références rvalue (8.3.2). [Exemple: Le résultat de l'appel d'une fonction dont le type de retour est une référence rvalue est une xvalue. —Fin exemple]
  • Une glvalue (lvalue «généralisée») est une lvalue ou une xvalue.
  • Une rvalue (ainsi appelée, historiquement, car les rvalues ​​pouvaient apparaître sur le côté droit d'une expression d'affectation) est une valeur x, un
    objet temporaire (12.2) ou un sous-objet de celui-ci, ou une valeur qui n'est pas
    associée à un objet.
  • Une valeur pr (valeur «pure») est une valeur r qui n'est pas une valeur x. [Exemple: Le résultat de l'appel d'une fonction dont le type de retour n'est pas une
    référence est une valeur. La valeur d'un littéral tel que 12, 7.3e5 ou
    true est également une valeur. —Fin exemple]

Chaque expression appartient exactement à l'une des classifications fondamentales de cette taxonomie: lvalue, xvalue ou prvalue. Cette propriété d'une expression est appelée sa catégorie de valeur.

Mais je ne suis pas sûr que cette sous-section soit suffisante pour comprendre clairement les concepts, car "généralement" n'est pas vraiment général, "vers la fin de sa durée de vie" n'est pas vraiment concret, "impliquant des références de valeur" n'est pas vraiment clair, et "Exemple: Le résultat de l'appel d'une fonction dont le type de retour est une référence rvalue est une valeur x." sonne comme un serpent se mord la queue.

CATÉGORIES DE VALEUR PRIMAIRE

Chaque expression appartient à exactement une catégorie de valeur principale. Ces catégories de valeurs sont les catégories lvalue, xvalue et prvalue.

lvaleurs

L'expression E appartient à la catégorie lvalue si et seulement si E fait référence à une entité qui a DÉJÀ une identité (adresse, nom ou alias) qui la rend accessible en dehors 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

L'expression E appartient à la catégorie xvalue si et seulement si elle est

- le résultat de l'appel d'une fonction, implicite ou explicite, dont le type de retour est une référence rvalue au type d'objet retourné, 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;
}

- un transtypage en une référence rvalue au type d'objet, 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;
}

- une expression d'accès de membre de classe désignant un membre de données non statique de type non référence dans lequel l'expression d'objet est une valeur 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;
}

- une expression pointeur sur membre dans laquelle le premier opérande est une valeur x et le second opérande est un pointeur sur un membre de données.

Notez que l'effet des règles ci-dessus est que les références rvalue nommées aux objets sont traitées comme des lvalues ​​et les références rvalue sans nom aux objets sont traitées comme des xvalues; Les références rvalue aux fonctions sont traitées comme des lvalues, qu'elles soient nommées ou non.

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

valeurs

L'expression E appartient à la catégorie prvalue si et seulement si E n'appartient ni à la lvalue ni à la catégorie 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;
}

CATÉGORIES À VALEUR MIXTE

Il existe deux autres catégories de valeurs mixtes importantes. Ces catégories de valeurs sont les catégories rvalue et glvalue.

rvalues

L'expression E appartient à la catégorie rvalue si et seulement si E appartient à la catégorie xvalue ou à la catégorie prvalue.

Notez que cette définition signifie que l'expression E appartient à la catégorie rvalue si et seulement si E fait référence à une entité qui n'a pas eu d'identité qui la rend accessible en dehors de E YET.

glvalues

L'expression E appartient à la catégorie glvalue si et seulement si E appartient à la catégorie lvalue ou à la catégorie xvalue.

UNE RÈGLE PRATIQUE

Scott Meyer a publié une règle empirique très utile pour distinguer les valeurs r des valeurs l.

  • Si vous pouvez prendre l'adresse d'une expression, l'expression est une valeur l.
  • Si le type d'une expression est une référence lvalue (par exemple, T & ou const T &, etc.), cette expression est une lvalue.
  • Sinon, l'expression est une valeur r. Conceptuellement (et généralement aussi en fait), les valeurs r correspondent à des objets temporaires, tels que ceux renvoyés par des fonctions ou créés par des conversions de types implicites. La plupart des valeurs littérales (par exemple, 10 et 5.3) sont également des valeurs r.

3
Tous les exemples pour lvalues ​​et tous les exemples pour xvalues ​​sont également des exemples pour glvalues. Merci d'avoir édité!
Dániel Sándor

1
Tu as raison. Les trois principales catégories de valeur sont suffisantes. La valeur n'est pas non plus nécessaire. Je pense que rvalue et glvalue sont dans la norme pour plus de commodité.
Dániel Sándor

1
A eu du mal à comprendre struct As{void f(){this;}}la thisvariable est une valeur. Je pensais que ça thisdevrait être une valeur. Jusqu'à ce que la norme 9.3.2 dise: Dans le corps d'une fonction membre non statique (9.3), le mot-clé this est une expression de valeur.
r0ng

3
@ r0ng thisest une valeur mais *thisest une valeur
Xeverous

1
"www" n'a pas toujours la même adresse. C'est une valeur l car c'est un tableau .
wally le

35

Les catégories de C ++ 03 sont trop restreintes pour capturer correctement l'introduction des références rvalue dans les attributs d'expression.

Avec leur introduction, il a été dit qu'une référence rvalue sans nom est évaluée comme une rvalue, de sorte que la résolution de surcharge préférerait les liaisons de référence rvalue, ce qui lui permettrait de sélectionner les constructeurs de déplacement plutôt que les constructeurs de copie. Mais il a été constaté que cela pose des problèmes tout autour, par exemple avec les types dynamiques et les qualifications.

Pour le montrer, considérez

int const&& f();

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

Sur les versions antérieures à xvalue, cela était autorisé, car en C ++ 03, les rvalues ​​de types non-classe ne sont jamais qualifiées par cv. Mais il est prévu que cela consts'applique dans le cas de référence rvalue, car ici nous faisons référence à des objets (= mémoire!), Et la suppression de const à partir de rvalues ​​non-classe est principalement due à l'absence d'objet.

Le problème des types dynamiques est de nature similaire. En C ++ 03, les valeurs r de type classe ont un type dynamique connu - c'est le type statique de cette expression. Parce que pour l'avoir d'une autre manière, vous avez besoin de références ou de déréférences, qui s'évaluent à une valeur l. Ce n'est pas vrai avec les références rvalue sans nom, mais elles peuvent montrer un comportement polymorphe. Donc, pour le résoudre,

  • les références rvalue sans nom deviennent des xvalues . Ils peuvent être qualifiés et potentiellement avoir un type dynamique différent. Ils préfèrent, comme prévu, les références rvalue pendant la surcharge, et ne se lient pas aux références lvalue non const.

  • Ce qui était auparavant une rvalue (littéraux, objets créés par des transtypages en types non référence) devient maintenant une prvalue . Ils ont la même préférence que les valeurs x pendant la surcharge.

  • Ce qui était auparavant une lvalue reste une lvalue.

Et deux regroupements sont effectués pour capturer ceux qui peuvent être qualifiés et peuvent avoir différents types dynamiques ( glvalues ) et ceux où la surcharge préfère la liaison de référence rvalue ( rvalues ).


1
la réponse est évidemment raisonnable. xvalue est juste rvalue qui peut être qualifié par cv et typé dynamique!
ligand

26

J'ai lutté avec cela pendant longtemps, jusqu'à ce que je tombe sur l'explication cppreference.com des catégories de valeur .

C'est en fait assez simple, mais je trouve qu'il est souvent expliqué d'une manière difficile à mémoriser. Ici, il est expliqué très schématiquement. Je vais citer quelques parties de la page:

Catégories primaires

Les catégories de valeurs primaires correspondent à deux propriétés d'expressions:

  • a une identité : il est possible de déterminer si l'expression fait référence à la même entité qu'une autre expression, par exemple en comparant les adresses des objets ou les fonctions qu'ils identifient (obtenues directement ou indirectement);

  • peut être déplacé de : constructeur de déplacement, opérateur d'affectation de déplacement, ou une autre surcharge de fonction qui implémente la sémantique de déplacement peut se lier à l'expression.

Expressions qui:

  • ont une identité et ne peuvent pas être déplacées sont appelées expressions lvalue ;
  • ont une identité et peuvent être déplacées sont appelées expressions xvalue ;
  • n'ont pas d'identité et peuvent être déplacées sont appelées expressions de valeur ;
  • n'ont pas d'identité et ne peuvent pas être déplacés ne sont pas utilisés.

lvalue

Une expression lvalue ("valeur gauche") est une expression qui a une identité et ne peut pas être déplacée .

rvalue (jusqu'à C ++ 11), prvalue (depuis C ++ 11)

Une expression prvalue ("pure rvalue") est une expression qui n'a pas d'identité et peut être déplacée .

xvalue

Une expression xvalue ("expiration de la valeur") est une expression qui a une identité et peut être déplacée .

glvalue

Une expression glvalue ("lvalue généralisée") est une expression qui est soit une lvalue soit une xvalue. Il a une identité . Il peut être déplacé ou non.

rvalue (depuis C ++ 11)

Une expression rvalue ("bonne valeur") est une expression qui est soit une prvalue soit une xvalue. Il peut être déplacé . Il peut ou non avoir une identité.


1
Dans certains livres, il est démontré que les valeurs x ont leur x provenant d '«expert» ou «exceptionnel»
noɥʇʎԀʎzɐɹƆ

Et plus important encore, leur liste d'exemples complète.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

19

Comment ces nouvelles catégories sont-elles liées aux catégories rvalue et lvalue existantes?

Une lvalue C ++ 03 est toujours une lvalue C ++ 11, tandis qu'une rvalue C ++ 03 est appelée une prvalue en C ++ 11.


14

Un addendum aux excellentes réponses ci-dessus, sur un point qui m'a dérouté même après avoir lu Stroustrup et pensé avoir compris la distinction rvalue / lvalue. Quand tu vois

int&& a = 3,

il est très tentant de lire le en int&&tant que type et de conclure que ac'est une valeur. Ce n'est pas:

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

aa un nom et est ipso facto une valeur l. Ne pensez pas au &&comme faisant partie du type de a; c'est juste quelque chose qui vous dit à quoi vous pouvez vous alier.

Cela est particulièrement important pour T&&les arguments de type dans les constructeurs. Si vous écrivez

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

vous allez copier _tdans t. Vous avez besoin

Foo::Foo(T&& _t) : t{std::move(_t)} {}si vous voulez bouger. Est-ce que mon compilateur m'a prévenu quand j'ai laissé de côté move!


1
Je pense que cette réponse pourrait être clarifiée. "Ce qui aest autorisé à se lier à": Bien sûr, mais aux lignes 2 et 3, vos variables sont c et b, et ce n'est pas un qui se lie, et le type de an'est pas pertinent ici, n'est-ce pas? Les lignes seraient les mêmes si aétait déclaré int a. La principale différence réelle ici est que dans la ligne 1, il ne doit pas nécessairement être const
lié

12

Comme les réponses précédentes couvraient de manière exhaustive la théorie derrière les catégories de valeur, il y a juste une autre chose que j'aimerais ajouter: vous pouvez réellement jouer avec et tester.

Pour une expérimentation pratique avec les catégories de valeurs, vous pouvez utiliser le spécificateur decltype . Son comportement distingue explicitement les trois principales catégories de valeurs (xvalue, lvalue et prvalue).

L'utilisation du préprocesseur nous évite de taper ...

Catégories primaires:

#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

Catégories mixtes:

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

Maintenant, nous pouvons reproduire (presque) tous les exemples de cppreference sur la catégorie de valeur .

Voici quelques exemples avec C ++ 17 (pour 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)); 

Les catégories mixtes sont un peu ennuyeuses une fois que vous avez compris la catégorie principale.

Pour plus d'exemples (et d'expérimentation), consultez le lien suivant sur l'explorateur de compilateur . Ne vous embêtez pas à lire l'assemblage, cependant. J'ai ajouté beaucoup de compilateurs juste pour m'assurer qu'il fonctionne sur tous les compilateurs courants.


Je pense qu'il #define IS_GLVALUE(X) IS_LVALUE(X) || IS_XVALUE(X)faudrait en fait #define IS_GLVALUE(X) (IS_LVALUE(X) || IS_XVALUE(X))regarder autrement ce qui se passe si vous &&deux IS_GLVALUE.
Gabriel Devillers
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.