web-dev-qa-db-fra.com

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

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

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

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

Deux catégories sont devenues cinq catégories.

  • Quelles sont ces nouvelles catégories d'expressions?
  • Quel est le lien entre ces nouvelles catégories et les catégories rvalue et lvalue existantes?
  • Les catégories rvalue et lvalue dans C++ 0x sont-elles les mêmes que dans C++ 03?
  • Pourquoi ces nouvelles catégories sont-elles nécessaires? Les dieux WG21 essaient-ils de nous confondre, simples mortels?
1244
James McNellis

Je suppose que ce document pourrait constituer une introduction pas si courte: n3055

Tout le massacre a commencé avec la sémantique du mouvement. Une fois que nous avons des expressions qui peuvent être déplacées et non copiées, il est soudainement facile de saisir les règles qui exigent une distinction entre les expressions pouvant être déplacées et dans quelle direction.

D'après ce que je suppose d'après le brouillon, la distinction entre les valeurs de r/l reste la même, mais uniquement lorsque les choses bougent.

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.

Citations n3055 :

  • Une lvalue (appelée ainsi, historiquement, car des valeurs pourraient apparaître à 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 sur laquelle E pointe. Autre exemple, l'appel d'une fonction dont le type de retour est une référence à lvalue est une lvalue.]
  • Une xvalue (une valeur "expirante") fait également référence à un objet, généralement vers la fin de sa vie (de sorte que ses ressources puissent être déplacées, par exemple). 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 un xvalue .
  • Une valeur (ainsi appelé, historiquement, car les valeurs peuvent apparaître à droite d'une expression d'affectation) est une valeur x, un objet temporaire ou sous-objet, ou une valeur qui n'est pas associée à un objet.
  • A prvalue (“pure” rvalue) is an rvalue that is not an xvalue. [Example: The result of calling a function whose return type is not a reference is a prvalue]

Le document en question est une excellente référence pour cette question, car il indique les modifications exactes apportées à la norme suite à l’introduction de la nouvelle nomenclature.

584
Kornel Kisielewicz

Quelles sont ces nouvelles catégories d'expressions?

Le FCD (n3092) a une excellente description:

- Une lvalue (ainsi appelée historiquement, car des valeurs pourraient apparaître à 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 sur laquelle E pointe. Autre exemple, 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 "expirante") fait également référence à un objet, généralement vers la fin de sa durée de vie (pour 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 valeur x. —Fin exemple]

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

- Une rvalue (ainsi appelée historiquement parce que des rvalues ​​peuvent apparaître à droite des expressions d'affectation) est une xvalue, 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 (valeur "pure") est une valeur 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 s'appelle 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 gauche soit une valeur et que l'opérande droit soit une valeur et génère une valeur comme résultat. Les opérateurs définis par l'utilisateur sont des fonctions et les catégories de valeurs qu'ils attendent et qu'elles génèrent sont déterminées par leurs types de paramètre et de retour. —Fin note

Je vous suggère cependant de lire toute la section 3.10 Lvalues ​​et rvalues ​​.

Quel est le lien entre ces nouvelles catégories et les catégories rvalue et lvalue existantes?

Encore:

Taxonomy

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

La sémantique des valeurs a particulièrement évolué avec l'introduction de la sémantique des déplacements.

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

Donc, cette construction/assignation de déménagement pourrait être définie et supportée.

325
dirkgently

Je commencerai 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 distinguent glvalue et prvalue. Par exemple, vous pouvez avoir une valeur glvalue avec un type incomplet ou abstrait, mais aucune variable prvalue avec un type incomplet ou abstrait. Avant d’avoir cette terminologie, les règles qui devaient réellement distinguer glvalue/prvalue désignaient lvalue/rvalue et elles étaient soit involontairement fausses, soit comportaient de nombreuses explications et exceptions à la règle "... sauf si la valeur est due à un nom référence rvalue ... ". Donc, il semble être une bonne idée de simplement donner aux concepts de glvalues ​​et de prvalues ​​leur propre nom.

Quelles sont ces nouvelles catégories d'expressions? Quel est le lien entre ces nouvelles catégories et les catégories rvalue et lvalue existantes?

Nous avons toujours les termes lvalue et rvalue compatibles avec C++ 98. Nous venons de diviser les rvalues ​​en deux sous-groupes, xvalues ​​et prvalues, et nous appelons lvalues ​​et xvalues ​​en tant que glvalues. Les valeurs X constituent un nouveau type de catégorie de valeur pour les références rvalue non nommées. Chaque expression est l'une de ces trois valeurs: 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 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
}
172
sellibitze

Pourquoi ces nouvelles catégories sont-elles nécessaires? Est-ce que les dieux du WG21 essaient juste de nous confondre, simples mortels?

Je ne pense pas que les autres réponses (même si bon nombre d’entre elles) reflètent réellement la réponse à cette question particulière. Oui, ces catégories et telles existent pour permettre la sémantique de déplacement, mais la complexité existe pour une raison. C’est la seule règle inviolable de déplacer des éléments en C++ 11:

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

C’est la raison pour laquelle ces catégories existent: pouvoir parler de valeurs où il est sans danger de s’en éloigner et de parler de valeurs où ce n’est pas.

Dans la version la plus ancienne des références de valeur r, le mouvement se faisait facilement. Trop facilement. Assez facilement, il y avait beaucoup de potentiel pour déplacer implicitement des choses lorsque l'utilisateur ne le voulait pas vraiment.

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

  1. Quand c'est un objet temporaire ou secondaire. (prvalue)
  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 que les 5 valeurs ne soient entrées, cela provoquerait un déplacement. Bien sûr que si. Vous avez passé une référence rvalue au constructeur, qui est donc lié au constructeur qui prend une référence rvalue. Cela est évident.

Il y a juste un problème avec ceci; vous n'avez pas demandé de le déplacer. Oh, vous pourriez 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 temporaire car les temporaires n'ont pas de nom. Vous avez peut-être prolongé la durée de vie du temporaire, mais cela signifie que ce 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é à le déplacer, alors déplacer est faux.

La solution évidente consiste à définir val en lvalue. Cela signifie que vous ne pouvez pas en sortir. OK bien; c'est nommé, donc c'est une valeur.

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 rvalue nommées et les références rvalue non nommées. Eh bien, les références rvalue nommées sont des lvalues; C'était notre solution ci-dessus. Alors, comment appelle-t-on les références rvalue sans nom (la valeur de retour de Func ci-dessus)?

Ce n'est pas une lvalue, car vous ne pouvez pas passer d'une lvalue. Et nous avons besoin pour pouvoir nous déplacer en renvoyant un &&; Comment pourriez-vous explicitement dire de déplacer quelque chose? C’est ce que std::move renvoie, après tout. Ce n'est pas une rvalue (style ancien), parce que cela peut être du côté gauche d'une équation (les choses sont en réalité un peu plus compliquées, voir cette question et les commentaires ci-dessous). Ce n'est ni une valeur ni une valeur; c'est un nouveau genre de chose.

Ce que nous avons est une valeur que vous pouvez traiter comme une lvalue, , sauf à partir de laquelle elle est implicitement déplaçable. Nous appelons cela une valeur x.

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

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

  • Les glvalues ​​sont l'union de xvalues ​​et de lvalues ​​dans un groupe, car elles partagent un grand nombre de propriétés communes.

Donc, vraiment, tout se résume à xvalues ​​et à la nécessité de restreindre le mouvement à des endroits précis et seulement. Ces lieux sont définis par la catégorie rvalue; prvalues ​​sont les déplacements implicites et xvalues ​​sont les déplacements explicites (std::move renvoie une valeur x).

151
Nicol Bolas

IMHO, la meilleure explication sur 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. Clairement, nous nous dirigions vers une impasse ou un gâchis ou les deux. J'ai passé la pause déjeuner à faire une analyse pour voir quelles propriétés (de valeurs) étaient indépendantes. Il n'y avait que deux propriétés indépendantes:

  • has identity - c’est-à-dire une 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 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 le truc de notation regex consistant à utiliser une majuscule pour indiquer un négatif - j'étais pressé):

  • iMname__: a une identité et ne peut pas être déplacé de
  • imname__: a une identité et peut être déplacé de (par exemple, le résultat de la conversion d'une valeur lvalue vers une référence rvalue)
  • Imname__: n'a pas d'identité et peut être déplacé de.

    La quatrième possibilité, IMname__, (n’a pas d’identité et ne peut pas être déplacée) n’est pas utile dans C++ (ni, je pense, dans une autre langue).

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

  • iname__: a une identité
  • mname__: peut être déplacé de

Cela m’a amené à mettre ce diagramme au tableau: enter image description here

Appellation

J'ai remarqué que nous n'avions qu'une liberté de nommer limitée: les deux points à gauche (nommés iMet iname__) correspondent à ce que les personnes ayant plus ou moins de formalités ont appelé lvalueset les deux points à droite (nommés met Imname__) avec plus ou moins de formalités ont appelé rvaluesname__. Cela doit être reflété dans notre nom. C'est-à-dire que la "jambe" gauche du Wdevrait avoir des noms liés à lvalueet que la "jambe" droite du Wdevrait avoir des noms liés à rvalue.. . Ces notions n’existent tout simplement pas dans le monde de Strachey composé uniquement de rvalueset de lvaluesname__. Quelqu'un a observé que les idées qui

  • Chaque valueest soit un lvalueou un rvaluename__
  • lvaluen'est pas un rvalueet un rvaluen'est pas un lvaluename__

sont profondément ancrés dans notre conscience, des propriétés très utiles et des traces de cette dichotomie se retrouvent partout dans le projet de norme. Nous avons tous convenu que nous devrions préserver ces propriétés (et les préciser). Cela a davantage limité nos choix de nommage. J'ai observé que le libellé de la bibliothèque standard utilise rvaluepour signifier m(la généralisation), de sorte que pour conserver l'espérance et le texte de la bibliothèque standard, le point bas de droite de Wdevrait être nommé rvalue.

Cela a conduit à une discussion ciblée sur la dénomination. Tout d'abord, nous devions choisir lvalue.A lvaluedevrait-il signifier iMou la généralisation iname__? Sous la direction de Doug Gregor, nous avons répertorié les endroits dans le langage de base où Word lvalueétait qualifié pour signifier l'un ou l'autre. Une liste a été faite et dans la plupart des cas et dans le texte le plus compliqué/fragile lvaluesignifie actuellement iMname__. C’est le sens classique de lvalue car "jadis", rien n’était déplacé; moveest une nouvelle notion dans C++0x. De plus, nommer le point le plus haut de Wname__lvaluenous donne la propriété que chaque valeur est un lvalueou un rvaluename__, mais pas les deux.

Ainsi, le point en haut à gauche de West lvalueet le point en bas à droite est rvalue.. Qu'est-ce que cela fait en bas à gauche et en haut à droite? Le point en bas à gauche est une généralisation de la valeur classique, permettant un déplacement. Donc, c'est un generalized lvalue. Nous l'avons nommé glvalue. Vous pouvez interroger l'abréviation, mais (je pense) pas avec la logique. Nous avons supposé qu'en cas d'utilisation sérieuse, generalized lvalue serait en quelque sorte abrégé de toute façon, aussi ferions-nous mieux de le faire immédiatement (sinon nous risquerions une confusion). Le point en haut à droite du W est moins général que le coin en bas à droite (maintenant, comme toujours, appelé rvaluename__). Ce point représente la notion pure originale d'objet à partir duquel vous pouvez vous déplacer, car il ne peut plus y être fait référence (sauf par un destructeur). J'ai aimé l'expression specialized rvalue par opposition à generalized lvalue mais pure rvalue en abrégé prvaluea été jugée gagnante (et probablement à juste titre). Ainsi, la jambe gauche du W est lvalueet glvalueet la jambe droite est prvalueet rvalue. Incidemment, chaque valeur est une valeur glvalue ou prvalue, mais pas les deux.

Cela laisse la partie supérieure supérieure du Wname__: imname__; 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 qui travaillent 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, nous avons donc choisi "x" pour le centre, l’inconnu, l’étrange, le xpert uniquement, voire même le x-rated.

Steve showing off the final product

117
Ivan Kush

INTRODUCTION

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

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

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

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

Nous pouvons avoir une idée des concepts de catégories de valeur en citant la sous-section intitulée Lvalues ​​et rvalues ​​du brouillon N3337 (le brouillon le plus semblable à la norme publiée ISOC++ 11).

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 valeurs pourraient apparaître à 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 sur laquelle E pointe. Autre exemple, 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 "expirante") fait également référence à un objet, généralement vers la fin de sa vie (pour 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 valeur x. —Fin exemple]
  • Une glvalue (lvalue "généralisée") est une lvalue ou une xvalue.
  • Une valeur (appelée ainsi, historiquement, car des valeurs peuvent apparaître à droite d'une expression d'affectation) est une valeur x, une valeur
    objet temporaire (12.2) ou sous-objet de celui-ci, ou une valeur qui n'est pas
    associé à un objet.
  • Une valeur (valeur "pure") est une valeur qui n'est pas une valeur x. [Exemple: résultat de l’appel d’une fonction dont le type de retour n’est pas un
    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 s'appelle sa catégorie de valeur.

Mais je ne suis pas tout à fait sûr que cette sous-section soit suffisante pour bien comprendre les concepts, car "généralement" n’est pas vraiment général, "sa fin de vie" n’est pas vraiment concret, "impliquant des références rvalue" 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 à une seule catégorie de valeur primaire. Ces catégories de valeur sont les catégories lvalue, xvalue et prvalue.

lvalues ​​

L'expression E appartient à la catégorie lvalue si et seulement si E fait référence à une entité pour laquelle ALREADY a déjà une identité (adresse, nom ou pseudonyme) la rendant 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 c'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 renvoyé, o

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

- une conversion sur une référence rvalue vers un type d'objet, o

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 xvalue, o

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 de pointeur à membre dans laquelle le premier opérande est une valeur x et le deuxième opérande est un pointeur sur un membre de données.

Notez que les règles ci-dessus ont pour effet que les références rvalue nommées aux objets sont traitées comme des lvalues ​​et que les références rvalue non nommées 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;
}

prvalues ​​

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 DE VALEURS MIXTES

Il existe deux autres catégories de valeurs mixtes importantes. Ces catégories de valeur 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 aucune identité la rendant 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 très utile pour distinguer les valeurs des valeurs.

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

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

Lors de leur introduction, il a été dit qu'une référence de valeur non nommée est évaluée à une valeur de telle sorte que la résolution de surcharge préfère les liaisons de référence de valeur, ce qui l'obligerait à sélectionner des constructeurs de déplacement par rapport aux constructeurs de copie. Mais il a été constaté que cela posait des problèmes, par exemple avec types dynamiques et avec des qualifications.

Pour montrer cela, considérons

int const&& f();

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

Cela a été autorisé pour les brouillons pré-xvalue, car en C++ 03, les valeurs de type non-classe ne sont jamais qualifiées de cv Mais il est prévu que const s'applique dans le cas rvalue-reference, car ici nous faisons nous référons à des objets (= memory!), Et abandonner const de rvalues ​​de non-classe est principalement destiné à la raison pour laquelle il n'y a pas d'objet autour.

Le problème pour les types dynamiques est de même nature. En C++ 03, les rvalues ​​de type classe ont un type dynamique connu: il s'agit du type statique de cette expression. Parce que pour le faire autrement, vous avez besoin de références ou de déréférences, qui sont évaluées à une valeur. Ce n'est pas vrai avec les références rvalue sans nom, pourtant elles peuvent montrer un comportement polymorphe. Alors pour le résoudre,

  • les références rvalue non nommées deviennent xvalues ​​. Ils peuvent être qualifiés et potentiellement avoir leur type dynamique différent. Comme prévu, ils préfèrent les références rvalue lors de la surcharge et ne se lient pas aux références non constantes.

  • Ce qui était auparavant une rvalue (littéraux, objets créés par des conversions vers des types non référencés) devient maintenant une prvalue . Ils ont la même préférence que xvalues ​​lors d'une 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 pour lesquels la surcharge préfère la liaison de référence rvalue ( rvalues ​​).

34

Cela fait longtemps que je lutte avec cela, jusqu'à ce que je tombe sur l'explication de cppreference.com de catégories de valeur .

C'est en fait assez simple, mais je trouve que cela est souvent expliqué de manière difficile à mémoriser. Ici, il est expliqué très schématiquement. Je citerai 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 identifiées. (obtenus directement ou indirectement);

  • peut être déplacé de : constructeur de déplacement, opérateur d'affectation de déplacement ou toute autre surcharge de fonction implémentant une sémantique de déplacement pouvant être liée à l'expression.

Des expressions qui:

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

lvalue

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

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 de .

xvalue

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

glvalue

Une expression glvalue ("generalized lvalue") est une expression qui est une lvalue ou une xvalue. Il a une identité . Il peut ou ne peut pas être déplacé de.

rvalue (depuis C++ 11)

Une expression rvalue ("valeur correcte") est une expression qui est une valeur ou une valeur. Il peut être déplacé de . Il peut ou peut ne pas avoir d'identité.

24
Felix Dombek

Quel est le lien entre ces nouvelles catégories et les catégories rvalue et lvalue existantes?

Une valeur C++ 03 est toujours une valeur C++ 11, alors qu'une valeur C++ 03 est appelée une valeur en C++ 11.

16
fredoverflow

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

int&& a = 3,

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

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

a a un nom et est ipso facto une lvalue. Ne pensez pas que le && fait partie du type de a; c'est simplement quelque chose qui vous dit à quoi a est autorisé à se lier.

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

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

vous allez copier _t dans t. Vous avez besoin

Foo::Foo(T&& _t) : t{std::move(_t)} {} si vous souhaitez vous déplacer. Mon compilateur m’avait-il averti quand j’aurais oublié le move!

14
Mohan

Comme les réponses précédentes couvraient de manière exhaustive la théorie sous-jacente aux catégories de valeur, j'aimerais ajouter une autre chose: vous pouvez réellement jouer avec et tester.

Pour certaines expériences pratiques avec les catégories de valeur, vous pouvez utiliser le spécificateur decltype . Son comportement distingue explicitement les trois catégories de valeur principales (xvalue, lvalue et prvalue).

L'utilisation du préprocesseur nous évite d'avoir à 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)

Nous pouvons maintenant reproduire (presque) tous les exemples de référence à 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(trunk). Clang 7.0.0 and msvc 19.16 are doing fine.
static_assert(IS_XVALUE(S().x)); 

Les catégories mixtes sont assez ennuyeuses une fois que vous avez déterminé la catégorie principale.

Pour quelques exemples supplémentaires (et expérimentation), jetez un œil à ce qui suit lien sur l'explorateur du compilateur Ne prenez pas la peine de lire l'Assemblée, cependant. J'ai ajouté de nombreux compilateurs uniquement pour m'assurer que cela fonctionne sur tous les compilateurs courants.

2
thebrandre