1492 votes

Que sont les valeurs r, les valeurs l, les valeurs x, les valeurs gl et les valeurs pr ?

En C++03, une expression est soit un rvaleur ou un valeur .

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

  1. rvaleur
  2. valeur
  3. valeur x
  4. valeur de la gl
  5. valeur pr

Deux catégories sont devenues cinq catégories.

  • Quelles sont ces nouvelles catégories d'expressions ?
  • Comment ces nouvelles catégories s'articulent-elles avec les catégories rvalue et lvalue existantes ?
  • Les catégories rvalue et lvalue sont-elles les mêmes en C++0x et en C++03 ?
  • Pourquoi ces nouvelles catégories sont-elles nécessaires ? Les GT21 Les dieux essaient-ils de nous embrouiller, nous, simples mortels ?

0 votes

Les valeurs r et l s'excluent-elles mutuellement ? L'expression xx es int peut être utilisé comme valeur l ou valeur r.

9 votes

@Philip Potter : En C++03 ? Oui. Une lvalue peut être utilisée comme rvalue parce qu'il y a une conversion standard de lvalue à rvalue.

1 votes

@Philip (en C++03) La façon dont il est utilisé n'a pas d'importance ; il ne peut s'agir que de l'un des deux. Si vous pouvez l'assigner, c'est une lvalue, sinon, c'est une rvalue. Le fait d'être une valeur l ne signifie pas que vous ne pouvez pas l'utiliser du côté droit d'une expression, et le fait d'être utilisé du côté droit d'une expression ne fait pas en soi de quelque chose une valeur r.

687voto

Kornel Kisielewicz Points 26556

Je pense que ce document pourrait servir d'introduction plus ou moins brève : n3055

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

D'après ce que je devine en me basant sur le projet, la distinction entre la valeur r/l reste la même, mais c'est seulement dans le contexte du déplacement que les choses se gâtent.

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

Citation n3055 :

  • Un valeur (c'est-à-dire, historiquement, parce que les valeurs l pouvaient apparaître à gauche d'une affectation d'une expression d'affectation) désigne une fonction ou un objet. [Exemple : Si E est un est une expression de type pointeur, alors *E est une expression lvalue faisant référence à l'objet ou la fonction à laquelle E points. Autre exemple, le résultat de l'appel d'une fonction résultat de l'appel d'une fonction dont le est une référence lvalue est une lvaleur].
  • Un valeur x (un valeur "eXpirante") se réfère également à un objet, généralement proche de la fin de sa 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'opérations impliquant une valeur r . [Exemple : Le résultat de résultat de l'appel d'une fonction dont le est une référence rvalue est une valeur x].
  • A valeur de la gl (valeur l "généralisée") est une valeur valeur ou un valeur x .
  • Un rvaleur (dit, historiquement, parce que les valeurs r apparaître du côté droit d'une expression d'affectation d'une expression d'affectation) est une valeur x, un objet temporaire ou ou un sous-objet temporaire de celui-ci, ou une valeur qui n'est pas associée à un objet.
  • A valeur pr ("pure" rvalue) est une rvalue qui n'est pas une valeur x. [Exemple : Le résultat de l'appel d'une fonction dont le n'est pas une référence est une prvalue].

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

0 votes

Merci, cette réponse est très 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 prvalue, et le retour par valeur me donne une xvalue. Est-ce que vous les avez mélangés, ou est-ce que mon banc d'essai est cassé ? J'ai essayé avec GCC 4.6.1, clang (à partir de svn) et MSVC, et ils montrent tous le même comportement.

0 votes

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

5 votes

J'utilise les macros de ce site pour tester diverses expressions : stackoverflow.com/a/6114546/96963 Il se peut qu'ils fassent des erreurs de diagnostic.

356voto

dirkgently Points 56879

Quelles sont ces nouvelles catégories d'expressions ?

En FCD (n3092) a une excellente description :

- Une valeur l (appelée ainsi, historiquement, parce que les valeurs l pouvaient figurer sur l'écran 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 lvalue faisant référence à l'objet ou à la fonction vers lequel E pointe. Autre exemple, le résultat de l'appel d'une fonction dont le type de retour est une référence lvalue est une expression lvalue. -Fin de l'exemple ]

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

- Une valeur gl ("généralisée") ) est une valeur l ou une valeur x.

- Une valeur r (appelée ainsi, historiquement, parce que les valeurs r pouvaient apparaître sur le droite d'une expression d'affectation d'affectation) est une valeur x, un objet temporaire 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 r "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 pr. La valeur d'un littéral tel que 12, 7.3e5 ou true est également une également une prvaleur. -Fin de l'exemple ]

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

Je vous suggère de lire l'intégralité de la section 3.10 Valeurs L et valeurs r mais

Comment ces nouvelles catégories s'articulent-elles avec les catégories rvalue et lvalue existantes ?

Encore une fois :

Taxonomy

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

La sémantique des valeurs r a évolué, notamment avec l'introduction de la sémantique des déplacements.

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

Ainsi, la construction et l'affectation des mouvements pourraient être définies et soutenues.

67 votes

J'aime bien ce schéma. Je pense qu'il serait utile de commencer la réponse par "Chaque expression appartient exactement à l'une des classifications fondamentales de cette taxonomie : lvalue, xvalue ou prvalue". Il est alors facile d'utiliser le diagramme pour montrer que ces trois classes fondamentales sont combinées pour former glvalue et rvalue.

4 votes

La mention "is glvalue" est équivalente à "is not prvalue", et la mention "is rvalue" est équivalente à "is not lvalue".

5 votes

C'est celui qui m'a le plus aidé : bajamircea.github.io/assets/2016-04-07-move-forward/ (Diagramme de Venn des catégories de valeurs)

198voto

sellibitze Points 13607

Je commencerai par votre dernière question :

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

La norme C++ contient de nombreuses règles relatives à la catégorie de valeur d'une expression. Certaines règles font une distinction entre lvalue et rvalue. Par exemple, lorsqu'il s'agit de la résolution des surcharges. D'autres règles font une distinction entre glvalue et prvalue. Par exemple, vous pouvez avoir une glvalue avec un type incomplet ou abstrait mais il n'y a pas de prvalue avec un type incomplet ou abstrait. Avant que nous ne disposions de cette terminologie, les règles qui devaient réellement faire la distinction entre glvalue/prvalue se référaient à lvalue/rvalue et elles étaient soit involontairement erronées, soit contenaient de nombreuses explications et exceptions à la règle, comme "...sauf si la rvalue est due à une référence à une rvalue sans nom...". Il semble donc que ce soit une bonne idée de donner aux concepts de glvalue et de prvalue leur propre nom.

Quelles sont ces nouvelles catégories ? Comment ces nouvelles catégories s'articulent-elles avec les catégories rvalue et lvalue existantes ?

Nous disposons toujours des termes lvalue et rvalue qui sont compatibles avec C++98. Nous avons simplement divisé les rvalues en deux sous-groupes, xvalues et prvalues, et nous nous référons aux lvalues et xvalues en tant que glvalues. Les xvaleurs sont un nouveau type de catégorie de valeurs pour les références rvaleurs sans nom. Chaque expression est l'une de ces trois catégories : 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 nommées rvalue sont des lvalues :

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

184voto

Nicol Bolas Points 133791

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

Je n'ai pas l'impression que les autres réponses (aussi bonnes soient-elles pour la plupart) reflètent vraiment la réponse à cette question particulière. Oui, ces catégories et autres existent pour permettre la sémantique des déplacements, mais la complexité existe pour une seule raison. C'est la seule règle inviolable pour déplacer des objets en C++11 :

Tu ne dois te déplacer que lorsque la sécurité est incontestable.

C'est la raison d'être de ces catégories : pouvoir parler de valeurs là où l'on peut s'en écarter en toute sécurité, et parler de valeurs là où ce n'est pas le cas.

Dans la première version des références aux valeurs r, les mouvements se produisaient facilement. Trop facilement. Suffisamment facilement pour qu'il y ait beaucoup de possibilités de déplacer implicitement des choses sans que l'utilisateur le veuille vraiment.

Voici les circonstances dans lesquelles il est possible de déplacer un objet en toute sécurité :

  1. Lorsqu'il s'agit d'un objet temporaire ou d'un sous-objet de celui-ci. (prvalue)
  2. Lorsque l'utilisateur a a explicitement dit de le déplacer .

Si vous le faites :

SomeType &&Func() { ... }

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

À quoi cela sert-il ? Dans les anciennes versions de la spécification, avant l'introduction des 5 valeurs, cela provoquait un mouvement. Bien sûr, c'est le cas. Vous avez passé une référence rvalue au constructeur, et donc il se lie au constructeur qui prend une référence rvalue. C'est évident.

Il n'y a qu'un seul problème : vous n'avez pas demander pour le déplacer. Oh, on pourrait dire que le && aurait dû être un indice, mais cela ne change rien au fait qu'il a enfreint la règle. val n'est pas un temporaire car les temporaires n'ont pas de nom. Vous avez peut-être prolongé la durée de vie du temporaire, mais cela signifie qu'il ne s'agit pas d'un temporaire. temporaire ; c'est comme n'importe quelle autre variable de la pile.

S'il ne s'agit pas d'une mesure temporaire et que vous n'avez pas demandé à être déplacé, alors le déplacement est une mesure temporaire. erronée.

La solution la plus évidente est de faire val une valeur l. Cela signifie que vous ne pouvez pas vous en éloigner. OK, d'accord ; il est nommé, donc c'est une lvaleur.

Une fois que vous avez fait cela, vous ne pouvez plus dire que SomeType&& signifie la même chose partout. Vous avez maintenant fait la distinction entre les références de valeurs nominatives et les références de valeurs non nominatives. Eh bien, les références à des valeurs r nommées sont des valeurs l ; c'est la solution que nous avons trouvée plus haut. Alors comment appelons-nous les références à des valeurs r non nommées (la valeur de retour de Func ci-dessus) ?

Ce n'est pas une valeur l, car on ne peut pas se déplacer à partir d'une valeur l. Et nous besoin pour pouvoir se déplacer en renvoyant un && Sinon, comment pourrait-on dire explicitement qu'il faut déplacer quelque chose ? C'est ce que std::move des retours, en fin de compte. Ce n'est pas une rvalue (à l'ancienne), car elle peut se trouver à gauche d'une équation (les choses sont en fait un peu plus compliquées, voir cette question et les commentaires ci-dessous). Il ne s'agit ni d'une valeur l, ni d'une valeur r ; c'est un nouveau type de chose.

Ce que nous avons, c'est une valeur que l'on peut traiter comme une valeur l, sauf qu'il est implicitement déplaçable. Nous l'appelons une valeur x.

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

  • Une valeur pr est en fait simplement le nouveau nom du type précédent de valeur r, c'est-à-dire qu'il s'agit des valeurs r que l'on ne peut pas utiliser. ne sont pas xvaleurs.

  • Les valeurs gl sont l'union des valeurs x et des valeurs l en un seul groupe, parce qu'elles ont beaucoup de propriétés en commun.

En fait, tout se résume aux valeurs x et à la nécessité de restreindre les mouvements à certains endroits précis et seulement à certains endroits. Ces endroits sont définis par la catégorie rvalue ; les prvalues sont les déplacements implicites, et les xvalues sont les déplacements explicites ( std::move renvoie une valeur x).

0 votes

C'est intéressant mais est-ce que ça compile ? Ne devrait-on pas Func ont une déclaration de retour ?

12 votes

@Thomas : Il s'agit d'un exemple ; la façon dont il crée la valeur de retour n'a pas d'importance. Ce qui compte, c'est qu'il renvoie un && .

1 votes

Remarque : les valeurs pr peuvent également se trouver du côté gauche d'une équation, comme dans l'exemple suivant X foo(); foo() = X; ... Pour cette raison fondamentale, je ne peux pas suivre l'excellente réponse ci-dessus jusqu'au bout, parce que vous ne faites vraiment la distinction entre la nouvelle valeur x et l'ancienne valeur pr, sur la base du fait qu'elle peut être sur le lhs.

35voto

Les catégories du C++03 sont trop restreintes pour permettre l'introduction correcte de références à des rvaleurs dans les attributs d'expression.

Avec l'introduction de ces références, il a été dit qu'une référence rvalue sans nom est évaluée à une rvalue, de sorte que la résolution de surcharge préférerait les liaisons de référence rvalue, ce qui l'amènerait à sélectionner les constructeurs de déplacement plutôt que les constructeurs de copie. Mais il s'est avéré que cela posait des problèmes à tous les niveaux, par exemple avec Types dynamiques et avec des qualifications.

Pour le démontrer, considérons

int const&& f();

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

Dans les versions antérieures à xvalue, cela était autorisé, parce qu'en C++03, les rvalues de types non-classes ne sont jamais qualifiées par cv. Mais il est prévu que const s'applique dans le cas de la référence à une valeur, car ici nous faire font référence à des objets (= mémoire !), et l'abandon de const pour les valeurs r hors classe est principalement dû au fait qu'il n'y a pas d'objet dans les environs.

Le problème des types dynamiques est de même nature. En C++03, les valeurs de type classe ont un type dynamique connu - c'est le type statique de cette expression. En effet, pour que les choses se passent autrement, il faut des références ou des déréférences, qui évaluent une lvaleur. Ce n'est pas le cas des références rvalue sans nom, qui peuvent pourtant présenter un comportement polymorphe. Donc, pour résoudre ce problème,

  • les références de valeurs non nommées deviennent xvaleurs . Ils peuvent être qualifiés et avoir un type de dynamique différent. Ils préfèrent, comme prévu, les références à des valeurs r lors de la surcharge, et ne se lient pas à des références à des valeurs l non-const.

  • Ce qui était auparavant une valeur r (littéraux, objets créés par des casts vers des types non-référentiels) devient maintenant une valeur valeur pr . Elles ont la même préférence que les valeurs x lors de la surcharge.

  • Ce qui était auparavant une valeur l reste une valeur l.

Deux regroupements sont effectués pour capturer les personnes qui peuvent être qualifiées et qui peuvent avoir différents types de dynamique ( glvalues ) et ceux où la surcharge préfère la liaison de référence à la valeur r ( rvalues ).

1 votes

La réponse est évidemment raisonnable. xvalue est juste rvalue qui peut être qualifiée cv et typée dynamique !

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X