web-dev-qa-db-fra.com

Conversion implicite de lvalue en rvalue

Je vois le terme "conversion lvalue-to-rvalue" utilisé à de nombreux endroits dans la norme C++. Ce type de conversion se fait souvent implicitement, pour autant que je sache.

Une caractéristique inattendue (pour moi) du phrasé de la norme est qu'ils décident de traiter lvalue-to-rvalue comme une conversion. Et s'ils avaient dit qu'une valeur gl est toujours acceptable au lieu d'une valeur. Cette phrase aurait-elle réellement un sens différent? Par exemple, nous lisons que lvalues ​​et xvalues ​​sont des exemples de glvalues. Nous ne lisons pas que les valeurs l et les valeurs x sont convertibles en valeurs gl. Y a-t-il une différence de sens?

Avant ma première rencontre avec cette terminologie, j'avais l'habitude de modéliser mentalement les valeurs et les valeurs r comme suit: "Les valeurs sont toujours capables d'agir comme valeurs, mais en plus peuvent apparaître sur le côté gauche d'un =, et à droite d'un & ".

Pour moi, c'est le comportement intuitif que si j'ai un nom de variable, je peux mettre ce nom partout où j'aurais mis un littéral. Ce modèle semble cohérent avec la terminologie des conversions implicites lvalue à rvalue utilisée dans la norme, tant que cette conversion implicite est garantie de se produire.

Mais, comme ils utilisent cette terminologie, j'ai commencé à me demander si la conversion implicite lvalue-à-rvalue pouvait ne pas se produire dans certains cas. Autrement dit, peut-être que mon modèle mental est mauvais ici. Voici une partie pertinente de la norme: (merci aux commentateurs).

Chaque fois qu'une valeur gl apparaît dans un contexte où une valeur est attendue, la valeur gl est convertie en valeur pr; voir 4.1, 4.2 et 4.3. [Remarque: Une tentative de lier une référence rvalue à une lvalue n'est pas un tel contexte; voir 8.5.3. - note de fin]

Je comprends que ce qu'ils décrivent dans la note est le suivant:

int x = 1;
int && y = x; //in this declaration context, x won't bind to y.
// but the literal 1 would have bound, so this is one context where the implicit 
// lvalue to rvalue conversion did not happen.  
// The expression on right is an lvalue. if it had been a prvalue, it would have bound.
// Therefore, the lvalue to prvalue conversion did not happen (which is good). 

Donc, ma question est (sont):

1) Quelqu'un pourrait-il clarifier les contextes où cette conversion peut se produire implicitement? Plus précisément, mis à part le contexte de la liaison à une référence rvalue, existe-t-il d'autres cas où les conversions lvalue-rvalue ne se produisent pas implicitement?

2) De plus, la parenthèse [Note:...] dans la clause, il semble que nous aurions pu le comprendre à partir de la phrase précédente. Quelle partie de la norme serait-ce?

3) Cela signifie-t-il que la liaison rvalue-reference n'est pas un contexte dans lequel nous attendons une expression prvalue (à droite)?

4) Comme les autres conversions, la conversion glvalue-to-prvalue implique-t-elle un travail à l'exécution qui me permettrait de l'observer?

Mon objectif ici n'est pas de demander s'il est souhaitable de permettre une telle conversion. J'essaie d'apprendre à m'expliquer le comportement de ce code en utilisant la norme comme point de départ.

Une bonne réponse passerait par la citation que j'ai placée ci-dessus et expliquerait (basée sur l'analyse du texte) si la note qu'elle contient est également implicite de son texte. Il ajouterait alors peut-être d'autres citations qui me feraient connaître les autres contextes dans lesquels cette conversion pourrait ne pas se produire implicitement, ou expliquer qu'il n'y a plus de tels contextes. Peut-être une discussion générale sur la raison pour laquelle glvalue to prvalue est considérée comme une conversion.

41
orm

Je pense que la conversion de lvalue en rvalue est plus que juste utilisez une lvalue où une rvalue est requise. Il peut créer une copie d'une classe et donne toujours une valeur, pas un objet.

J'utilise n3485 pour "C++ 11" et n1256 pour "C99".


Objets et valeurs

La description la plus concise se trouve dans C99/3.14:

objet

région de stockage de données dans l'environnement d'exécution, dont le contenu peut représenter des valeurs

Il y a aussi un peu en C++ 11/[intro.object]/1

Certains objets sont polymorphes; l'implémentation génère des informations associées à chacun de ces objets qui permettent de déterminer le type de cet objet lors de l'exécution du programme. Pour les autres objets, l'interprétation des valeurs qui s'y trouvent est déterminée par le type des expressions utilisées pour y accéder.

Un objet contient donc une valeur (peut contenir).


Catégories de valeur

Malgré son nom, catégories de valeur classifie les expressions, pas les valeurs. lvalue-expressions even ne peut pas être considéré comme des valeurs.

La taxonomie/catégorisation complète peut être trouvée dans [basic.lval]; voici une discussion StackOverflow .

Voici les parties sur les objets:

  • Un lvalue ([...]) désigne une fonction ou un objet. [...]
  • Un xvalue (une valeur "eXpiring") fait également référence à un objet [...]
  • A glvalue (lvalue "généralisée") est une lvalue ou une xvalue.
  • Un rvalue ([...]) est une valeur x, un objet temporaire 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. [...]

Notez l'expression "une valeur qui n'est pas associée à un objet". Notez également que les xvalue-expressions se référant aux objets, true values doit toujours apparaître comme prvalue-expressions.


La conversion de lvalue en rvalue

Comme l'indique la note de bas de page 53, elle devrait maintenant être appelée "conversion de valeur en valeur". Tout d'abord, voici la citation:

1 Une valeur gl d'un type non fonction et non tableau T peut être convertie en valeur pr. Si T est un type incomplet, un programme qui nécessite cette conversion est mal formé. Si l'objet auquel se réfère la glvalue n'est pas un objet de type T et n'est pas un objet d'un type dérivé de T, ou si l'objet n'est pas initialisé, un programme qui nécessite cette conversion a un comportement indéfini. Si T est un type non-classe, le type de la valeur est la version non qualifiée cv de T. Sinon, le type de la valeur est T.

Ce premier paragraphe spécifie les exigences et le type de conversion résultant. Il n'est pas encore concerné par les effets de la conversion (autre que le comportement indéfini).

2 Lorsqu'une conversion de lvalue en rvalue se produit dans un opérande non évalué ou une sous-expression de celui-ci, la valeur contenue dans l'objet référencé n'est pas accessible. Sinon, si la glvalue a un type de classe, la conversion initialise en copie un temporaire de type T à partir de la glvalue et le résultat de la conversion est une valeur pour le temporaire. Sinon, si la valeur glvalue est de type (éventuellement qualifié cv) std::nullptr_t, Le résultat prvalue est une constante de pointeur nul. Sinon, la valeur contenue dans l'objet indiqué par la glvalue est le résultat prvalue.

Je dirais que vous verrez la conversion lvalue-à-rvalue le plus souvent appliquée aux types non-classe. Par exemple,

struct my_class { int m; };

my_class x{42};
my_class y{0};

x = y;

L'expression x = y Fait pas applique la conversion lvalue-à-rvalue à y (ce qui créerait un my_class Temporaire, soit dit en passant) ). La raison en est que x = y Est interprété comme x.operator=(y), ce qui prend y par défaut par référence, pas par valeur (pour la liaison de référence, voir ci-dessous; il ne peut pas lier une valeur r, car ce serait un objet temporaire différent de y). Cependant, la définition par défaut de my_class::operator= Applique la conversion lvalue-to-rvalue à x.m.

Par conséquent, la partie la plus importante pour moi semble être

Sinon, la valeur contenue dans l'objet indiqué par la glvalue est le résultat prvalue.

Donc, généralement, une conversion de lvalue en rvalue ne fera que lire la valeur d'un objet. Ce n'est pas seulement une conversion sans opération entre les catégories de valeur (expression); il peut même créer un temporaire en appelant un constructeur de copie. Et la conversion de lvalue en rvalue renvoie toujours une valeur pr valeur, pas un (temporaire) objet.

Notez que la conversion de lvalue en rvalue n'est pas la seule conversion qui convertit une lvalue en prvalue: il y a aussi la conversion de tableau en pointeur et la conversion de fonction en pointeur.


valeurs et expressions

La plupart des expressions ne produisent pas d'objets[[citation requise]]. Cependant, un id-expression peut être un identifiant, ce qui dénote une entité. Un objet est une entité, il existe donc des expressions qui produisent des objets:

int x;
x = 5;

Le côté gauche de l'expression affectationx = 5 Doit également être une expression. x voici un id-expression, car x est un identifiant. Le résultat de cette id-expression est l'objet désigné par x.

Les expressions appliquent des conversions implicites: [expr]/9

Chaque fois qu'une expression glvalue apparaît en tant qu'opérande d'un opérateur qui attend une valeur pour cet opérande, les conversions standard lvalue en rvalue, tableau en pointeur ou fonction en pointeur sont appliquées pour convertir l'expression en valeur.

Et/10 sur conversions arithmétiques habituelles ainsi que/3 sur les conversions définies par l'utilisateur.

J'aimerais maintenant citer un opérateur qui "attend une valeur pour cet opérande", mais ne peut en trouver que des transtypages. Par exemple, [expr.dynamic.cast]/2 "Si T est un type de pointeur, v [l'opérande] doit être une valeur d'un pointeur pour compléter le type de classe".

Les conversions arithmétiques habituelles requises par de nombreux opérateurs arithmétiques invoquent une conversion lvalue-to-rvalue indirectement via la conversion standard utilisée. Toutes les conversions standard sauf les trois qui convertissent de lvalues ​​en rvalues ​​attendent des prvalues.

Cependant, l'affectation simple n'invoque pas les conversions arithmétiques habituelles. Il est défini dans [expr.ass]/2 comme:

Dans l'affectation simple (=), La valeur de l'expression remplace celle de l'objet référencé par l'opérande de gauche.

Ainsi, bien qu'il ne nécessite pas explicitement une expression de valeur sur le côté droit, il nécessite une valeur. Il n'est pas clair pour moi si cela strictement nécessite la conversion lvalue-to-rvalue. Il y a un argument selon lequel l'accès à la valeur d'une variable non initialisée devrait toujours invoquer un comportement non défini (voir aussi CWG 616 ), que ce soit en affectant sa valeur à un objet ou en ajoutant sa valeur à une autre valeur. Mais ce comportement indéfini n'est requis que pour une conversion lvalue en rvalue (AFAIK), qui devrait alors être le seul moyen d'accéder à la valeur stockée dans un objet.

Si cette vue plus conceptuelle est valide, que nous avons besoin de la conversion lvalue en rvalue pour accéder à la valeur à l'intérieur d'un objet, il serait alors beaucoup plus facile de comprendre où elle est (et doit être) appliquée.


Initialisation

Comme pour l'affectation simple, il y a discussion si la conversion lvalue-to-rvalue est requise pour initialiser un autre objet:

int x = 42; // initializer is a non-string literal -> prvalue
int y = x;  // initializer is an object / lvalue

Pour les types fondamentaux, la dernière puce [dcl.init]/17 indique:

Sinon, la valeur initiale de l'objet en cours d'initialisation est la valeur (éventuellement convertie) de l'expression d'initialisation. Des conversions standard seront utilisées, si nécessaire, pour convertir l'expression d'initialisation en la version non qualifiée cv du type de destination; aucune conversion définie par l'utilisateur n'est prise en compte. Si la conversion ne peut pas être effectuée, l'initialisation est incorrecte.

Cependant, il a également mentionné la valeur de l'expression d'initialisation. Semblable à l'expression d'affectation simple, nous pouvons prendre cela comme une invocation indirecte de la conversion lvalue-to-rvalue.


Reliure de référence

Si nous voyons la conversion lvalue-to-rvalue comme un moyen d'accéder à la valeur d'un objet (plus la création d'un temporaire pour les opérandes de type classe), nous comprenons que c'est pas appliqué généralement pour la liaison à une référence: Une référence est une valeur l, elle fait toujours référence à un objet. Donc, si nous lions des valeurs à des références, nous devons créer des objets temporaires contenant ces valeurs. Et c'est en effet le cas si l'expression-initialiseur d'une référence est une valeur (qui est une valeur ou un objet temporaire):

int const& lr = 42; // create a temporary object, bind it to `r`
int&& rv = 42;      // same

La liaison d'une valeur à une référence lvalue est interdite, mais les valeurs de types de classe avec des fonctions de conversion qui produisent des références lvalue peuvent être liées aux références lvalue du type converti.

La description complète de la liaison de référence dans [dcl.init.ref] est plutôt longue et plutôt hors sujet. Je pense que l'essentiel de celui-ci concernant cette question est que les références se réfèrent aux objets, donc pas de conversion glvalue-to-prvalue (object-to-value).

31
dyp

Sur les glvalues: Une glvalue (lvalue "généralisée") est une expression qui est soit une lvalue soit une xvalue. Une glvalue peut être implicitement convertie en prvalue avec une conversion implicite de lvalue en rvalue, de tableau en pointeur ou de fonction en pointeur.

Les transformations Lvalue sont appliquées lorsque l'argument lvalue (par exemple une référence à un objet) est utilisé dans un contexte où rvalue (par exemple un nombre) est attendu.

conversion Lvalue en rvalue
Une valeur de gl de tout type T non fonctionnel et non tableau peut être implicitement convertie en valeur pr du même type. Si T est un type non-classe, cette conversion supprime également les qualificateurs cv. Sauf si elle est rencontrée dans un contexte non évalué (dans un opérande de sizeof, typeid, noexcept ou decltype), cette conversion copie-construit efficacement un objet temporaire de type T en utilisant la glvalue d'origine comme argument du constructeur, et cet objet temporaire est renvoyé en tant que valeur . Si la valeur de gl a le type std :: nullptr_t, la valeur résultante est la constante de pointeur null nullptr.

2
callisto