Value categories
Chaque expression C++ (un opérateur avec ses opérandes, un littéral, un nom de variable, etc.) est caractérisée par deux propriétés indépendantes : un type et une catégorie de valeur . Chaque expression possède un type non-référence, et chaque expression appartient exactement à l'une des trois catégories de valeur primaires : prvalue , xvalue , et lvalue .
- un glvalue (lvalue « généralisé ») est une expression dont l'évaluation détermine l'identité d'un objet ou d'une fonction ;
- un prvalue (rvalue « pur ») est une expression dont l'évaluation
-
- calcule la valeur d'un opérande d'un opérateur intégré (une telle prvalue n'a pas d'objet résultat ), ou
- initialise un objet (une telle prvalue est dite avoir un objet résultat ).
-
L'objet résultat peut être une variable, un objet créé par
new-expression
, un temporaire créé par
materialisation temporaire
, ou un membre de ceux-ci. Notez que les expressions non-
void
rejetées
ont un objet résultat (le temporaire matérialisé). De plus, toute prvalue de classe et de tableau a un objet résultat sauf lorsqu'elle est l'opérande de
decltype;
- un xvalue (une valeur « eXpiring ») est une glvalue qui désigne un objet dont les ressources peuvent être réutilisées ;
- un lvalue est une glvalue qui n'est pas un xvalue ;
| Contenu étendu |
|---|
|
Appelés ainsi historiquement car les lvalues pouvaient apparaître à gauche d'une expression d'assignation. En général, ce n'est pas toujours le cas : void foo(); void baz() { int a; // Expression `a` is lvalue a = 4; // OK, could appear on the left-hand side of an assignment expression int &b{a}; // Expression `b` is lvalue b = 5; // OK, could appear on the left-hand side of an assignment expression const int &c{a}; // Expression `c` is lvalue c = 6; // ill-formed, assignment of read-only reference // Expression `foo` is lvalue // address may be taken by built-in address-of operator void (*p)() = &foo; foo = baz; // ill-formed, assignment of function } |
- une rvalue est une prvalue ou une xvalue ;
| Contenu étendu |
|---|
|
Appelés ainsi historiquement car les rvalues pouvaient apparaître sur le côté droit d'une expression d'affectation. En général, ce n'est pas toujours le cas :
Exécuter ce code
#include <iostream> struct S { S() : m{42} {} S(int a) : m{a} {} int m; }; int main() { S s; // Expression `S{}` is prvalue // May appear on the right-hand side of an assignment expression s = S{}; std::cout << s.m << '\n'; // Expression `S{}` is prvalue // Can be used on the left-hand side too std::cout << (S{} = S{7}).m << '\n'; } Sortie : 42 7 |
Note : cette taxonomie a subi des changements significatifs avec les révisions passées de la norme C++, voir Historique ci-dessous pour plus de détails.
| Contenu étendu |
|---|
|
Malgré leurs noms, ces termes classifient les expressions, pas les valeurs.
Exécuter ce code
#include <type_traits> #include <utility> template <class T> struct is_prvalue : std::true_type {}; template <class T> struct is_prvalue<T&> : std::false_type {}; template <class T> struct is_prvalue<T&&> : std::false_type {}; template <class T> struct is_lvalue : std::false_type {}; template <class T> struct is_lvalue<T&> : std::true_type {}; template <class T> struct is_lvalue<T&&> : std::false_type {}; template <class T> struct is_xvalue : std::false_type {}; template <class T> struct is_xvalue<T&> : std::false_type {}; template <class T> struct is_xvalue<T&&> : std::true_type {}; int main() { int a{42}; int& b{a}; int&& r{std::move(a)}; // Expression `42` is prvalue static_assert(is_prvalue<decltype((42))>::value); // Expression `a` is lvalue static_assert(is_lvalue<decltype((a))>::value); // Expression `b` is lvalue static_assert(is_lvalue<decltype((b))>::value); // Expression `std::move(a)` is xvalue static_assert(is_xvalue<decltype((std::move(a)))>::value); // Type of variable `r` is rvalue reference static_assert(std::is_rvalue_reference<decltype(r)>::value); // Type of variable `b` is lvalue reference static_assert(std::is_lvalue_reference<decltype(b)>::value); // Expression `r` is lvalue static_assert(is_lvalue<decltype((r))>::value); } |
Table des matières |
Catégories principales
lvalue
Les expressions suivantes sont des expressions lvalue :
- le nom d'une variable, d'une fonction , d'un objet paramètre de template (depuis C++20) , ou d'un membre de données, quel que soit son type, tel que std:: cin ou std:: hex . Même si le type de la variable est une référence rvalue, l'expression constituée de son nom est une expression lvalue (mais voir expressions éligibles au déplacement );
| Contenu étendu |
|---|
void foo() {} void baz() { // `foo` est une lvalue // l'adresse peut être prise par l'opérateur d'adresse intégré void (*p)() = &foo; } struct foo {}; template <foo a> void baz() { const foo* obj = &a; // `a` est une lvalue, objet paramètre de template } |
- un appel de fonction ou une expression d'opérateur surchargé, dont le type de retour est une référence lvalue, tel que std:: getline ( std:: cin , str ) , std:: cout << 1 , str1 = str2 , ou ++ it ;
| Contenu étendu |
|---|
int& a_ref() { static int a{3}; return a; } void foo() { a_ref() = 5; // `a_ref()` est une lvalue, appel de fonction dont le type de retour est une référence lvalue } |
- a = b , a + = b , a % = b , et toutes les autres expressions d'assignation et d'assignation composée intégrées ;
- ++ a et -- a , les expressions de pré-incrémentation et pré-décrémentation intégrées ;
- * p , l'expression d'indirection intégrée ;
- a [ n ] et p [ n ] , les expressions d'indice intégrées , où un opérande dans a [ n ] est une lvalue de tableau (depuis C++11) ;
-
a.
m
, l'expression
d'accès membre d'objet
, sauf lorsque
mest un membre énumérateur ou une fonction membre non statique, ou lorsque a est une rvalue etmest un membre de données non statique de type objet ;
| Contenu étendu |
|---|
struct foo { enum bar { m // membre énumérateur }; }; void baz() { foo a; a.m = 42; // mal formé, lvalue requis comme opérande gauche d'assignation } struct foo { void m() {} // fonction membre non statique }; void baz() { foo a; // `a.m` est une prvalue, donc l'adresse ne peut pas être prise par l'opérateur // d'adresse intégré void (foo::*p1)() = &a.m; // mal formé void (foo::*p2)() = &foo::m; // OK : pointeur vers fonction membre } struct foo { static void m() {} // fonction membre statique }; void baz() { foo a; void (*p1)() = &a.m; // `a.m` est une lvalue void (*p2)() = &foo::m; // identique } |
-
p
-
>
m
, l'expression intégrée
d'accès au membre par pointeur
, sauf lorsque
mest un membre énumérateur ou une fonction membre non statique ; -
a.
*
mp
, l'expression
d'accès au membre par pointeur sur objet
, où
a
est une lvalue et
mpest un pointeur sur membre de données ; -
p
-
>
*
mp
, l'expression intégrée
d'accès au membre par pointeur sur pointeur
, où
mpest un pointeur sur membre de données ; - a, b , l'expression intégrée de virgule , où b est une lvalue ;
- a ? b : c , l'expression conditionnelle ternaire pour certains b et c (par exemple, lorsque les deux sont des lvalues du même type, mais voir la définition pour les détails) ;
- un littéral de chaîne , tel que "Hello, world!" ;
- une expression de cast vers un type référence lvalue, telle que static_cast < int & > ( x ) ou static_cast < void ( & ) ( int ) > ( x ) ;
- un paramètre de template constant de type référence lvalue ;
template <int& v> void set() { v = 5; // le paramètre template est une lvalue } int a{3}; // variable statique, adresse fixe connue à la compilation void foo() { set<a>(); }
|
(depuis C++11) |
Propriétés :
- Identique à glvalue (ci-dessous).
- L'adresse d'une lvalue peut être prise par l'opérateur d'adresse intégré : & ++ i [1] et & std:: hex sont des expressions valides.
- Une lvalue modifiable peut être utilisée comme opérande gauche des opérateurs d'affectation et d'affectation composée intégrés.
- Une lvalue peut être utilisée pour initialiser une référence lvalue ; cela associe un nouveau nom à l'objet identifié par l'expression.
prvalue
Les expressions suivantes sont des expressions prvalue :
- un littéral (sauf pour les littéraux de chaîne ), comme 42 , true ou nullptr ;
- un appel de fonction ou une expression d'opérateur surchargé, dont le type de retour est non-référence, comme str. substr ( 1 , 2 ) , str1 + str2 , ou it ++ ;
- a ++ et a -- , les expressions natives de post-incrément et post-décrément ;
- a + b , a % b , a & b , a << b , et toutes les autres expressions natives arithmétiques ;
- a && b , a || b , ! a , les expressions natives logiques ;
- a < b , a == b , a >= b , et toutes les autres expressions natives de comparaison ;
- & a , l'expression native d' adresse-de ;
-
a.
m
, l'expression de
membre d'objet
, où
mest un membre énumérateur ou une fonction membre non-statique [2] ; -
p
-
>
m
, l'expression native de
membre de pointeur
, où
mest un membre énumérateur ou une fonction membre non-statique [2] ; -
a.
*
mp
, l'expression de
pointeur vers membre d'objet
, où
mpest un pointeur vers fonction membre [2] ; -
p
-
>
*
mp
, l'expression native de
pointeur vers membre de pointeur
, où
mpest un pointeur vers fonction membre [2] ; - a, b , l'expression native de virgule , où b est une prvalue ;
- a ? b : c , l'expression conditionnelle ternaire pour certains b et c (voir la définition pour les détails) ;
- une expression de conversion vers un type non-référence, comme static_cast < double > ( x ) , std:: string { } , ou ( int ) 42 ;
-
le pointeur
this; - un énumérateur ;
- un paramètre de template constant de type scalaire ;
template <int v> void foo() { // pas une lvalue, `v` est un paramètre de template de type scalaire int const int* a = &v; // mal formé v = 3; // mal formé : lvalue requise comme opérande gauche d'assignation }
|
(depuis C++11) |
|
(depuis C++20) |
Propriétés :
- Identique à rvalue (ci-dessous).
- Une prvalue ne peut pas être polymorphe : le type dynamique de l'objet qu'elle désigne est toujours le type de l'expression.
- Une prvalue non-classe non-tableau ne peut pas être cv-qualifiée , sauf si elle est matérialisée pour être liée à une référence vers un type cv-qualifié (depuis C++17) . (Note : un appel de fonction ou une conversion peut produire une prvalue de type non-classe cv-qualifié, mais le qualificatif cv est généralement immédiatement supprimé.)
-
Une prvalue ne peut pas avoir de
type incomplet
(sauf pour le type
void
, voir ci-dessous, ou lorsqu'elle est utilisée dans un spécificateur
decltype). - Une prvalue ne peut pas avoir de type classe abstraite ou de tableau de celui-ci.
xvalue
Les expressions suivantes sont des expressions xvalue :
-
a.
m
, l'expression
membre d'objet
, où
a
est une rvalue et
mest un membre de données non statique d'un type objet ; -
a.
*
mp
, l'expression
pointeur vers membre d'objet
, où
a
est une rvalue et
mpest un pointeur vers membre de données ; - a, b , l'expression virgule intégrée, où b est une xvalue ;
- a ? b : c , l'expression conditionnelle ternaire pour certains b et c (voir la définition pour les détails) ;
|
(depuis C++11) |
|
(depuis C++17) |
| (depuis C++23) |
Propriétés :
- Identique à rvalue (ci-dessous).
- Identique à glvalue (ci-dessous).
En particulier, comme toutes les rvalues, les xvalues se lient aux références de rvalue, et comme toutes les glvalues, les xvalues peuvent être polymorphes , et les xvalues non-classes peuvent être qualifiées cv .
| Contenu étendu |
|---|
|
Exécuter ce code
#include <type_traits> template <class T> struct is_prvalue : std::true_type {}; template <class T> struct is_prvalue<T&> : std::false_type {}; template <class T> struct is_prvalue<T&&> : std::false_type {}; template <class T> struct is_lvalue : std::false_type {}; template <class T> struct is_lvalue<T&> : std::true_type {}; template <class T> struct is_lvalue<T&&> : std::false_type {}; template <class T> struct is_xvalue : std::false_type {}; template <class T> struct is_xvalue<T&> : std::false_type {}; template <class T> struct is_xvalue<T&&> : std::true_type {}; // Exemple tiré de la norme C++23 : 7.2.1 Catégorie de valeur [basic.lval] struct A { int m; }; A&& operator+(A, A); A&& f(); int main() { A a; A&& ar = static_cast<A&&>(a); // Appel de fonction avec type de retour référence rvalue est xvalue static_assert(is_xvalue<decltype( (f()) )>::value); // Membre d'une expression objet, l'objet est xvalue, `m` est un membre de données non statique static_assert(is_xvalue<decltype( (f().m) )>::value); // Expression de cast vers référence rvalue static_assert(is_xvalue<decltype( (static_cast<A&&>(a)) )>::value); // Expression d'opérateur, dont le type de retour est référence rvalue vers objet static_assert(is_xvalue<decltype( (a + a) )>::value); // L'expression `ar` est lvalue, `&ar` est valide static_assert(is_lvalue<decltype( (ar) )>::value); [[maybe_unused]] A* ap = &ar; } |
Catégories mixtes
glvalue
Une expression glvalue est soit une lvalue, soit une xvalue.
Propriétés :
- Une glvalue peut être implicitement convertie en prvalue avec une conversion implicite lvalue-à-rvalue, tableau-à-pointeur, ou fonction-à-pointeur conversion implicite .
- Une glvalue peut être polymorphe : le type dynamique de l'objet qu'elle identifie n'est pas nécessairement le type statique de l'expression.
- Une glvalue peut avoir un type incomplet , lorsque cela est permis par l'expression.
rvalue
Une expression rvalue est soit une prvalue soit une xvalue.
Propriétés :
- L'adresse d'une rvalue ne peut pas être prise par l'opérateur d'adresse intégré : & int ( ) , & i ++ [3] , & 42 , et & std :: move ( x ) sont invalides.
- Une rvalue ne peut pas être utilisée comme opérande gauche des opérateurs d'assignation ou d'assignation composée intégrés.
- Une rvalue peut être utilisée pour initialiser une référence lvalue const , auquel cas la durée de vie de l'objet temporaire identifié par la rvalue est étendue jusqu'à la fin de la portée de la référence.
|
(depuis C++11) |
Catégories spéciales
Appel de fonction membre en attente
Les expressions
a.
mf
et
p
-
>
mf
, où
mf
est une
fonction membre non statique
, et les expressions
a.
*
pmf
et
p
-
>
*
pmf
, où
pmf
est un
pointeur vers une fonction membre
, sont classées comme des expressions prvalue, mais elles ne peuvent pas être utilisées pour initialiser des références, comme arguments de fonction, ou à aucune autre fin, sauf comme opérande gauche de l'opérateur d'appel de fonction, par exemple
(
p
-
>
*
pmf
)
(
args
)
.
Expressions de type void
Les expressions d'appel de fonction retournant void , les expressions de cast vers void , et les expressions throw sont classées comme expressions prvalue, mais elles ne peuvent pas être utilisées pour initialiser des références ou comme arguments de fonction. Elles peuvent être utilisées dans des contextes à valeur ignorée (par exemple sur une ligne seule, comme opérande gauche de l'opérateur virgule, etc.) et dans l'instruction return d'une fonction retournant void . De plus, les expressions throw peuvent être utilisées comme deuxième et troisième opérandes de l' opérateur conditionnel ?: .
|
Les expressions de type void n'ont pas d' objet résultat . |
(since C++17) |
Champs de bits
Une expression qui désigne un bit-field (par exemple a. m , où a est une lvalue de type struct A { int m : 3 ; } ) est une expression glvalue : elle peut être utilisée comme opérande gauche de l'opérateur d'affectation, mais son adresse ne peut pas être prise et une référence non-const lvalue ne peut pas lui être liée. Une référence const lvalue ou une référence rvalue peut être initialisée à partir d'une glvalue de bit-field, mais une copie temporaire du bit-field sera créée : elle ne se liera pas directement au bit-field.
Expressions éligibles au déplacementBien qu'une expression constituée du nom d'une variable soit une expression lvalue, une telle expression peut être éligible au déplacement si elle apparaît comme opérande de Si une expression est éligible au déplacement, elle est traitée soit comme une rvalue soit comme une lvalue (jusqu'à C++23) comme une rvalue (depuis C++23) pour la résolution de surcharge (elle peut donc sélectionner le constructeur de déplacement ). Voir Déplacement automatique depuis les variables locales et paramètres pour plus de détails. |
(depuis C++11) |
Historique
CPL
Le langage de programmation CPL fut le premier à introduire les catégories de valeur pour les expressions : toutes les expressions CPL peuvent être évaluées en "mode droite", mais seuls certains types d'expressions sont significatifs en "mode gauche". Lorsqu'elle est évaluée en mode droite, une expression est considérée comme étant une règle pour le calcul d'une valeur (la valeur droite, ou rvalue ). Lorsqu'elle est évaluée en mode gauche, une expression donne effectivement une adresse (la valeur gauche, ou lvalue ). "Gauche" et "Droite" représentaient ici "à gauche de l'affectation" et "à droite de l'affectation".
C
Le langage de programmation C a suivi une taxonomie similaire, sauf que le rôle de l'affectation n'était plus significatif : les expressions C sont catégorisées entre "expressions lvalue" et autres (fonctions et valeurs non-objets), où "lvalue" signifie une expression qui identifie un objet, une "valeur localisateur" [4] .
C++98
Le C++ d'avant 2011 suivait le modèle C, mais a restauré le nom "rvalue" pour les expressions non-lvalue, a transformé les fonctions en lvalues, et a ajouté la règle que les références peuvent se lier aux lvalues, mais seules les références vers const peuvent se lier aux rvalues. Plusieurs expressions C non-lvalue sont devenues des expressions lvalue en C++.
C++11
Avec l'introduction de la sémantique de déplacement en C++11, les catégories de valeur ont été redéfinies pour caractériser deux propriétés indépendantes des expressions [5] :
- a une identité : il est possible de déterminer si l'expression se réfère à la même entité qu'une autre expression, par exemple en comparant les adresses des objets ou des fonctions qu'elles identifient (obtenues directement ou indirectement) ;
- peut être déplacé : move constructor , move assignment operator , ou une autre surcharge de fonction implémentant la sémantique de déplacement peut se lier à l'expression.
En C++11, les expressions qui :
- ont une identité et ne peuvent pas être déplacés sont appelés lvalue expressions ;
- ont une identité et peuvent être déplacés sont appelés xvalue expressions ;
- n'ont pas d'identité et peuvent être déplacés sont appelés prvalue ("pure rvalue") expressions ;
- n'ont pas d'identité et ne peuvent pas être déplacés ne sont pas utilisés [6] .
Les expressions qui ont une identité sont appelées "expressions glvalue" (glvalue signifie "lvalue généralisée"). Les lvalues et les xvalues sont toutes deux des expressions glvalue.
Les expressions qui peuvent être déplacées sont appelées « expressions rvalue ». Les prvalues et les xvalues sont toutes deux des expressions rvalue.
C++17
En C++17, l'élision de copie est devenue obligatoire dans certaines situations, ce qui a nécessité la séparation des expressions prvalue des objets temporaires initialisés par celles-ci, aboutissant au système que nous avons aujourd'hui. Notez que, contrairement au schéma C++11, les prvalues ne sont plus déplacées.
Notes de bas de page
- ↑ En supposant que i ait un type natif ou que l'opérateur de pré-incrémentation soit surchargé pour renvoyer par référence lvalue.
- ↑ 2.0 2.1 2.2 2.3 Catégorie rvalue spéciale, voir appel de fonction membre en attente .
- ↑ En supposant que i ait un type natif ou que l'opérateur de post-incrémentation ne soit pas surchargé pour renvoyer par référence lvalue.
- ↑ "Une divergence d'opinion au sein de la communauté C concernait la signification de lvalue, un groupe considérant une lvalue comme tout type de localisateur d'objet, un autre groupe estimant qu'une lvalue est significative du côté gauche d'un opérateur d'affectation. Le comité C89 a adopté la définition de lvalue comme localisateur d'objet." -- ANSI C Rationale, 6.3.2.1/10.
- ↑ Terminologie des valeurs "nouvelle" par Bjarne Stroustrup, 2010.
-
↑
Les prvalues constantes (autorisées uniquement pour les types classe) et les xvalues constantes ne se lient pas aux surcharges
T&&, mais elles se lient aux surcharges const T && , qui sont également classées comme "constructeur de déplacement" et "opérateur d'affectation de déplacement" par la norme, satisfaisant la définition de "pouvant être déplacé" aux fins de cette classification. Cependant, de telles surcharges ne peuvent pas modifier leurs arguments et ne sont pas utilisées en pratique ; en leur absence, les prvalues constantes et les xvalues constantes se lient aux surcharges const T & .
Références
- Norme C++23 (ISO/IEC 14882:2024) :
-
- 7.2.1 Catégorie de valeur [basic.lval]
- Norme C++20 (ISO/CEI 14882:2020) :
-
- 7.2.1 Catégorie de valeur [basic.lval]
- Norme C++17 (ISO/CEI 14882:2017) :
-
- 6.10 Lvalues et rvalues [basic.lval]
- Norme C++14 (ISO/CEI 14882:2014) :
-
- 3.10 Lvalues et rvalues [basic.lval]
- Norme C++11 (ISO/IEC 14882:2011) :
-
- 3.10 Lvalues et rvalues [basic.lval]
- Norme C++98 (ISO/CEI 14882:1998) :
-
- 3.10 Lvalues et rvalues [basic.lval]
Rapports de défauts
Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.
| DR | Appliqué à | Comportement publié | Comportement corrigé |
|---|---|---|---|
| CWG 616 | C++11 |
l'accès membre et l'accès membre via
un pointeur sur membre d'une rvalue donnait une prvalue |
reclassifié en xvalue |
| CWG 1059 | C++11 | les prvalue de tableau ne pouvaient pas être cv-qualifiées | autorisé |
| CWG 1213 | C++11 | l'indexation d'une rvalue de tableau donnait une lvalue | reclassifiée en xvalue |
Voir aussi
|
Documentation C
pour
les catégories de valeur
|
Liens externes
| 1. | Catégories de valeur C++ et decltype démystifiés — David Mazières, 2021 | |
| 2. |
Déterminer empiriquement la catégorie de valeur d'une expression
— StackOverflow
|