Value categories
Cada expresión de C++ (un operador con sus operandos, un literal, un nombre de variable, etc.) se caracteriza por dos propiedades independientes: un tipo y una categoría de valor . Cada expresión tiene algún tipo que no es referencia, y cada expresión pertenece exactamente a una de las tres categorías de valor primarias: prvalue , xvalue , y lvalue .
- un glvalue (lvalue "generalizado") es una expresión cuya evaluación determina la identidad de un objeto o función;
- un prvalue (rvalue "puro") es una expresión cuya evaluación
-
- calcula el valor de un operando de un operador incorporado (dicho prvalue no tiene objeto resultado ), o
- inicializa un objeto (dicho prvalue se dice que tiene un objeto resultado ).
-
El objeto resultado puede ser una variable, un objeto creado por
new-expression
, un temporal creado por
temporary materialization
, o un miembro de los mismos. Nótese que las expresiones no-
void
descartadas
tienen un objeto resultado (el temporal materializado). Además, todo prvalue de clase y array tiene un objeto resultado excepto cuando es el operando de
decltype;
- un xvalue (un valor "eXpiring") es un glvalue que denota un objeto cuyos recursos pueden reutilizarse;
- un lvalue es un glvalue que no es un xvalue;
| Contenido extendido |
|---|
|
Denominados históricamente así porque los lvalues podían aparecer en el lado izquierdo de una expresión de asignación. En general, este no es siempre el caso: 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 } |
- un rvalue es un prvalue o un xvalue;
| Contenido extendido |
|---|
|
Denominados así históricamente porque los rvalues podían aparecer en el lado derecho de una expresión de asignación. En general, este no es siempre el caso:
Ejecutar este código
#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'; } Salida: 42 7 |
Nota: esta taxonomía experimentó cambios significativos con revisiones pasadas del estándar C++, consulte Historial a continuación para más detalles.
| Contenido extendido |
|---|
|
A pesar de sus nombres, estos términos clasifican expresiones, no valores.
Ejecutar este código
#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); } |
Contenidos |
Categorías principales
lvalue
Las siguientes expresiones son lvalue expressions :
- el nombre de una variable, una función , un objeto de parámetro de plantilla (desde C++20) , o un miembro de datos, independientemente del tipo, como std:: cin o std:: hex . Incluso si el tipo de la variable es referencia a valor derecho, la expresión que consiste en su nombre es una expresión de valor izquierdo (pero véase Expresiones elegibles para movimiento );
| Contenido extendido |
|---|
void foo() {} void baz() { // `foo` es lvalue // la dirección puede ser tomada por el operador address-of incorporado void (*p)() = &foo; } struct foo {}; template <foo a> void baz() { const foo* obj = &a; // `a` es un lvalue, objeto de parámetro de plantilla } |
- una llamada a función o una expresión de operador sobrecargado, cuyo tipo de retorno es referencia lvalue, como std:: getline ( std:: cin , str ) , std:: cout << 1 , str1 = str2 , o ++ it ;
| Contenido extendido |
|---|
int& a_ref() { static int a{3}; return a; } void foo() { a_ref() = 5; // `a_ref()` es lvalue, llamada a función cuyo tipo de retorno es referencia lvalue } |
- a = b , a + = b , a % = b , y todas las demás expresiones de asignación y asignación compuesta integradas;
- ++ a y -- a , las expresiones integradas de preincremento y predecremento ;
- * p , la expresión de indirección integrada;
- a [ n ] y p [ n ] , las expresiones de subíndice integradas , donde un operando en a [ n ] es un valor-l de arreglo (desde C++11) ;
-
a.
m
, la expresión de
miembro de objeto
, excepto cuando
mes un miembro enumerador o una función miembro no estática, o cuando a es un valor-r ymes un miembro de datos no estático de tipo objeto;
| Contenido extendido |
|---|
struct foo { enum bar { m // miembro enumerador }; }; void baz() { foo a; a.m = 42; // incorrecto, se requiere un lvalue como operando izquierdo de asignación } struct foo { void m() {} // función miembro no estática }; void baz() { foo a; // `a.m` es un prvalue, por lo tanto no se puede tomar su dirección con el operador // de dirección incorporado void (foo::*p1)() = &a.m; // incorrecto void (foo::*p2)() = &foo::m; // OK: puntero a función miembro } struct foo { static void m() {} // función miembro estática }; void baz() { foo a; void (*p1)() = &a.m; // `a.m` es un lvalue void (*p2)() = &foo::m; // lo mismo } |
-
p
-
>
m
, la expresión incorporada de
miembro de puntero
, excepto cuando
mes un enumerador miembro o una función miembro no estática; -
a.
*
mp
, la expresión de
puntero a miembro de objeto
, donde
a
es un lvalue y
mpes un puntero a miembro de datos; -
p
-
>
*
mp
, la expresión incorporada de
puntero a miembro de puntero
, donde
mpes un puntero a miembro de datos; - a, b , la expresión incorporada de coma , donde b es un lvalue;
- a ? b : c , la expresión condicional ternaria para ciertos b y c (por ejemplo, cuando ambos son lvalues del mismo tipo, pero consulte la definición para más detalles);
- un literal de cadena , como "Hello, world!" ;
- una expresión de conversión a tipo de referencia lvalue, como static_cast < int & > ( x ) o static_cast < void ( & ) ( int ) > ( x ) ;
- un parámetro de plantilla constante de tipo referencia lvalue;
template <int& v> void set() { v = 5; // el parámetro de plantilla es un lvalue } int a{3}; // variable estática, su dirección fija es conocida en tiempo de compilación void foo() { set<a>(); }
|
(desde C++11) |
Propiedades:
- Igual que glvalue (abajo).
- La dirección de un lvalue puede obtenerse mediante el operador incorporado de dirección: & ++ i [1] y & std:: hex son expresiones válidas.
- Un lvalue modificable puede usarse como operando izquierdo de los operadores incorporados de asignación y asignación compuesta.
- Un lvalue puede usarse para inicializar una referencia lvalue ; esto asocia un nuevo nombre con el objeto identificado por la expresión.
prvalue
Las siguientes expresiones son expresiones prvalue :
- un literal (excepto los string literals ), como 42 , true o nullptr ;
- una llamada a función o expresión de operador sobrecargado, cuyo tipo de retorno es no referencia, como str. substr ( 1 , 2 ) , str1 + str2 , o it ++ ;
- a ++ y a -- , las expresiones incorporadas de post-incremento y post-decremento ;
- a + b , a % b , a & b , a << b , y todas las demás expresiones aritméticas incorporadas;
- a && b , a || b , ! a , las expresiones lógicas incorporadas;
- a < b , a == b , a >= b , y todas las demás expresiones de comparación incorporadas;
- & a , la expresión incorporada de dirección-de ;
-
a.
m
, la expresión de
miembro de objeto
, donde
mes un enumerador miembro o una función miembro no estática [2] ; -
p
-
>
m
, la expresión incorporada de
miembro de puntero
, donde
mes un enumerador miembro o una función miembro no estática [2] ; -
a.
*
mp
, la expresión de
puntero a miembro de objeto
, donde
mpes un puntero a función miembro [2] ; -
p
-
>
*
mp
, la expresión incorporada de
puntero a miembro de puntero
, donde
mpes un puntero a función miembro [2] ; - a, b , la expresión coma incorporada, donde b es un prvalue;
- a ? b : c , la expresión condicional ternaria para ciertos b y c (ver definición para detalles);
- una expresión de conversión a tipo no referencia, como static_cast < double > ( x ) , std:: string { } , o ( int ) 42 ;
-
el puntero
this; - un enumerador ;
- un parámetro de plantilla constante de tipo escalar;
template <int v> void foo() { // no es un lvalue, `v` es un parámetro de plantilla de tipo escalar int const int* a = &v; // incorrecto v = 3; // incorrecto: se requiere lvalue como operando izquierdo de asignación }
|
(desde C++11) |
|
(desde C++20) |
Propiedades:
- Igual que rvalue (abajo).
- Un prvalue no puede ser polimórfico : el tipo dinámico del objeto que denota es siempre el tipo de la expresión.
- Un prvalue no-clase no-array no puede ser cv-qualified , a menos que sea materializado para ser vinculado a una referencia a un tipo cv-qualified (desde C++17) . (Nota: una llamada a función o expresión de conversión puede resultar en un prvalue de tipo no-clase cv-qualified, pero el calificador cv generalmente se elimina inmediatamente.)
-
Un prvalue no puede tener
tipo incompleto
(excepto para el tipo
void
, ver abajo, o cuando se usa en el especificador
decltype). - Un prvalue no puede tener tipo de clase abstracta o un array de la misma.
xvalue
Las siguientes expresiones son xvalue expressions :
-
a.
m
, la expresión de
miembro de objeto
, donde
a
es un rvalue y
mes un miembro de datos no estático de un tipo objeto; -
a.
*
mp
, la expresión de
puntero a miembro de objeto
, donde
a
es un rvalue y
mpes un puntero a miembro de datos; - a, b , la expresión coma incorporada, donde b es un xvalue;
- a ? b : c , la expresión condicional ternaria para ciertos b y c (consulte la definición para más detalles);
|
(desde C++11) |
|
(desde C++17) |
|
(desde C++23) |
Propiedades:
- Igual que rvalue (a continuación).
- Igual que glvalue (a continuación).
En particular, como todos los valores-r, los valores-x se enlazan a referencias de valor-r, y como todos los valores-gl, los valores-x pueden ser polimórficos , y los valores-x no-clase pueden tener calificadores cv .
| Contenido extendido |
|---|
|
Ejecutar este código
#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 {}; // Ejemplo del estándar C++23: 7.2.1 Categoría de valor [basic.lval] struct A { int m; }; A&& operator+(A, A); A&& f(); int main() { A a; A&& ar = static_cast<A&&>(a); // Llamada a función con tipo de retorno referencia a rvalue es xvalue static_assert(is_xvalue<decltype( (f()) )>::value); // Miembro de expresión de objeto, el objeto es xvalue, `m` es un miembro de datos no estático static_assert(is_xvalue<decltype( (f().m) )>::value); // Expresión de conversión a referencia a rvalue static_assert(is_xvalue<decltype( (static_cast<A&&>(a)) )>::value); // Expresión de operador, cuyo tipo de retorno es referencia a rvalue a objeto static_assert(is_xvalue<decltype( (a + a) )>::value); // La expresión `ar` es lvalue, `&ar` es válido static_assert(is_lvalue<decltype( (ar) )>::value); [[maybe_unused]] A* ap = &ar; } |
Categorías mixtas
glvalue
Una expresión glvalue es lvalue o xvalue.
Propiedades:
- Un glvalue puede convertirse implícitamente a un prvalue mediante conversión implícita de lvalue-a-rvalue, array-a-puntero o función-a-puntero implicit conversion .
- Un glvalue puede ser polymorphic : el dynamic type del objeto que identifica no necesariamente coincide con el tipo estático de la expresión.
- Un glvalue puede tener incomplete type , donde lo permita la expresión.
rvalue
Una expresión rvalue es prvalue o xvalue.
Propiedades:
- La dirección de un rvalue no puede tomarse mediante el operador de dirección incorporado: & int ( ) , & i ++ [3] , & 42 , y & std :: move ( x ) son inválidos.
- Un rvalue no puede usarse como operando izquierdo de los operadores de asignación incorporados o de asignación compuesta.
- Un rvalue puede usarse para inicializar una referencia lvalue constante , en cuyo caso el tiempo de vida del objeto temporal identificado por el rvalue se extiende hasta que finaliza el ámbito de la referencia.
|
(desde C++11) |
Categorías especiales
Llamada pendiente de función miembro
Las expresiones
a.
mf
y
p
-
>
mf
, donde
mf
es una
función miembro no estática
, y las expresiones
a.
*
pmf
y
p
-
>
*
pmf
, donde
pmf
es un
puntero a función miembro
, se clasifican como expresiones prvalue, pero no pueden utilizarse para inicializar referencias, como argumentos de función, o para ningún propósito en absoluto, excepto como argumento del lado izquierdo del operador de llamada a función, por ejemplo
(
p
-
>
*
pmf
)
(
args
)
.
Expresiones void
Las expresiones de llamada a función que retornan void , las expresiones de conversión a void , y las expresiones throw se clasifican como expresiones prvalue, pero no pueden utilizarse para inicializar referencias o como argumentos de función. Pueden emplearse en contextos de valor descartado (por ejemplo, en una línea independiente, como operando izquierdo del operador coma, etc.) y en la return statement en una función que retorna void . Adicionalmente, las expresiones throw pueden utilizarse como segundo y tercer operando del operador condicional ?: .
|
Las expresiones void no tienen objeto de resultado . |
(since C++17) |
Campos de bits
Una expresión que designa un campo de bits (por ejemplo a. m , donde a es un lvalue de tipo struct A { int m : 3 ; } ) es una expresión glvalue: puede usarse como operando izquierdo del operador de asignación, pero no se puede tomar su dirección y no se puede vincular una referencia de lvalue no constante a ella. Una referencia constante de lvalue o referencia de rvalue puede inicializarse desde un glvalue de campo de bits, pero se hará una copia temporal del campo de bits: no se vinculará directamente al campo de bits.
Expresiones elegibles para movimientoAunque una expresión que consiste en el nombre de cualquier variable es una expresión lvalue, dicha expresión puede ser elegible para movimiento si aparece como operando de Si una expresión es elegible para movimiento, se trata ya sea como rvalue o como lvalue (hasta C++23) como rvalue (desde C++23) para el propósito de resolución de sobrecarga (por lo tanto puede seleccionar el move constructor ). Consulte Movimiento automático desde variables locales y parámetros para más detalles. |
(desde C++11) |
Historia
CPL
El lenguaje de programación CPL fue el primero en introducir categorías de valor para expresiones: todas las expresiones CPL pueden evaluarse en "modo derecho", pero solo ciertos tipos de expresión son significativos en "modo izquierdo". Cuando se evalúa en modo derecho, una expresión se considera como una regla para el cálculo de un valor (el valor derecho, o rvalue ). Cuando se evalúa en modo izquierdo, una expresión efectivamente proporciona una dirección (el valor izquierdo, o lvalue ). "Izquierdo" y "Derecho" aquí representaban "izquierda de asignación" y "derecha de asignación".
C
El lenguaje de programación C siguió una taxonomía similar, excepto que el rol de la asignación ya no era significativo: las expresiones de C se categorizan entre "expresiones lvalue" y otras (funciones y valores no objeto), donde "lvalue" significa una expresión que identifica un objeto, un "valor localizador" [4] .
C++98
El C++ anterior a 2011 seguía el modelo de C, pero restauró el nombre "rvalue" para las expresiones no lvalue, convirtió las funciones en lvalues, y añadió la regla de que las referencias pueden enlazarse a lvalues, pero solo las referencias a const pueden enlazarse a rvalues. Varias expresiones C no lvalue se convirtieron en expresiones lvalue en C++.
C++11
Con la introducción de la semántica de movimiento en C++11, las categorías de valor se redefinieron para caracterizar dos propiedades independientes de las expresiones [5] :
- tiene identidad : es posible determinar si la expresión se refiere a la misma entidad que otra expresión, como comparando direcciones de los objetos o las funciones que identifican (obtenidas directa o indirectamente);
- puede ser movido : move constructor , move assignment operator , u otra sobrecarga de función que implemente semánticas de movimiento puede vincularse a la expresión.
En C++11, las expresiones que:
- tienen identidad y no pueden ser movidos se denominan lvalue expressions;
- tienen identidad y pueden ser movidos se denominan xvalue expressions;
- no tienen identidad y pueden ser movidos se denominan prvalue ("pure rvalue") expressions;
- no tienen identidad y no pueden ser movidos no se utilizan [6] .
Las expresiones que tienen identidad se denominan "expresiones glvalue" (glvalue significa "lvalue generalizado"). Tanto los lvalues como los xvalues son expresiones glvalue.
Las expresiones que pueden ser movidas se denominan "expresiones rvalue". Tanto los prvalues como los xvalues son expresiones rvalue.
C++17
En C++17, copy elision se hizo obligatorio en algunas situaciones, y eso requirió la separación de las expresiones prvalue de los objetos temporales inicializados por ellas, resultando en el sistema que tenemos hoy. Nótese que, en contraste con el esquema de C++11, los prvalues ya no se mueven desde.
Notas al pie
- ↑ Asumiendo que i tiene tipo incorporado o el operador de pre-incremento está sobrecargado para devolver por referencia de lvalue.
- ↑ 2.0 2.1 2.2 2.3 Categoría especial de rvalue, ver llamada a función miembro pendiente .
- ↑ Asumiendo que i tiene tipo incorporado o el operador de post-incremento no está sobrecargado para devolver por referencia de lvalue.
- ↑ "Una diferencia de opinión dentro de la comunidad C se centró en el significado de lvalue, un grupo consideraba que un lvalue era cualquier tipo de localizador de objeto, otro grupo sostenía que un lvalue era significativo en el lado izquierdo de un operador de asignación. El Comité C89 adoptó la definición de lvalue como localizador de objeto." -- ANSI C Rationale, 6.3.2.1/10.
- ↑ Nueva Terminología de Valores por Bjarne Stroustrup, 2010.
-
↑
Los prvalues constantes (solo permitidos para tipos clase) y los xvalues constantes no se vinculan a las sobrecargas
T&&, pero se vinculan a las sobrecargas const T && , que también están clasificadas como "constructor de movimiento" y "operador de asignación de movimiento" por el estándar, satisfaciendo la definición de "puede ser movido" para el propósito de esta clasificación. Sin embargo, tales sobrecargas no pueden modificar sus argumentos y no se usan en la práctica; en su ausencia los prvalues constantes y xvalues constantes se vinculan a las sobrecargas const T & .
Referencias
- Estándar C++23 (ISO/IEC 14882:2024):
-
- 7.2.1 Categoría de valor [basic.lval]
- Estándar C++20 (ISO/IEC 14882:2020):
-
- 7.2.1 Categoría de valor [basic.lval]
- Estándar C++17 (ISO/IEC 14882:2017):
-
- 6.10 Lvalues y rvalues [basic.lval]
- Estándar C++14 (ISO/IEC 14882:2014):
-
- 3.10 Lvalues y rvalues [basic.lval]
- Estándar C++11 (ISO/IEC 14882:2011):
-
- 3.10 Lvalues y rvalues [basic.lval]
- Estándar C++98 (ISO/IEC 14882:1998):
-
- 3.10 Lvalues y rvalues [basic.lval]
Informes de defectos
Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares publicados anteriormente de C++.
| DR | Aplicado a | Comportamiento publicado | Comportamiento correcto |
|---|---|---|---|
| CWG 616 | C++11 |
el acceso a miembro y acceso a miembro a través de
puntero a miembro de un rvalue resultaba en prvalue |
reclasificado como xvalue |
| CWG 1059 | C++11 | los prvalues de array no podían ser cv-calificados | permitido |
| CWG 1213 | C++11 | la indexación de un array rvalue resultaba en lvalue | reclasificado como xvalue |
Véase también
|
Documentación de C
para
categorías de valor
|
Enlaces externos
| 1. | C++ value categories and decltype demystified — David Mazières, 2021 | |
| 2. |
Empirically determine value category of expression
— StackOverflow
|