En C ++ 03, une expression est soit une rvalue soit une lvalue .
En C ++ 11, une expression peut être:
- rvalue
- lvalue
- xvalue
- glvalue
- 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?
c++
expression
c++-faq
c++11
James McNellis
la source
la source
string("hello") = string("world")
.Réponses:
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 :
E
est une expression de type pointeur, alors*E
est une expression lvalue faisant référence à l'objet ou à la fonction vers laquelleE
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.]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.
la source
Le FCD (n3092) a une excellente description:
Je vous suggère de lire l'intégralité de la section 3.10 Lvalues et rvalues .
Encore:
La sémantique des rvalues a évolué en particulier avec l'introduction de la sémantique des mouvements.
Pour que la construction / affectation du mouvement puisse être définie et prise en charge.
la source
glvalue
furlvalue
et àlvalue
mesureplvalue
, pour être cohérent?Je vais commencer par votre dernière question:
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.
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:
Exemples avec fonctions:
Mais n'oubliez pas non plus que les références rvalue nommées sont des lvalues:
la source
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:
Si tu fais ça:
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.val
n'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
val
une 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 retourFunc
ci-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 quistd::move
revient, 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::move
renvoie une valeur x).la source
&&
.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.X
être une classe;X foo();
étant une déclaration de fonction, etfoo() = X();
étant une ligne de code. (J'ai laissé le deuxième ensemble de parenthèses dansfoo() = X();
mon commentaire ci-dessus.) Pour une question que je viens de publier avec cette utilisation mise en évidence, voir stackoverflow.com/questions/15482508/…À 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:
la source
lvalue
s, tous les autres littéraux sontprvalue
s. À 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.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:
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.
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).
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.
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
- un transtypage en une référence rvalue au type d'objet, ou
- 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
- 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.
valeurs
L'expression E appartient à la catégorie prvalue si et seulement si E n'appartient ni à la lvalue ni à la catégorie xvalue.
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.
la source
struct As{void f(){this;}}
lathis
variable est une valeur. Je pensais que çathis
devrait ê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.this
est une valeur mais*this
est une valeur"www"
n'a pas toujours la même adresse. C'est une valeur l car c'est un tableau .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
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
const
s'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 ).
la source
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:
la source
Une lvalue C ++ 03 est toujours une lvalue C ++ 11, tandis qu'une rvalue C ++ 03 est appelée une prvalue en C ++ 11.
la source
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 quea
c'est une valeur. Ce n'est pas:a
a un nom et est ipso facto une valeur l. Ne pensez pas au&&
comme faisant partie du type dea
; c'est juste quelque chose qui vous dit à quoi vous pouvez vousa
lier.Cela est particulièrement important pour
T&&
les arguments de type dans les constructeurs. Si vous écrivezFoo::Foo(T&& _t) : t{_t} {}
vous allez copier
_t
danst
. Vous avez besoinFoo::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
!la source
a
est 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 dea
n'est pas pertinent ici, n'est-ce pas? Les lignes seraient les mêmes sia
était déclaréint a
. La principale différence réelle ici est que dans la ligne 1, il ne doit pas nécessairement êtreconst
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:
Catégories mixtes:
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):
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.
la source
#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&&
deuxIS_GLVALUE
.