Categorías de valor
Cada expresión en 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 no-referencia, y pertenece exactamente a una de las tres categorías de valor primarias: pr-valor, x-valor, y l-valor.
- un gl-valor (un l-valor “generalizado”) es una expresión cuya evaluación determina la identidad de un objeto o función;
- un pr-valor (un r-valor “puro”) es una expresión cuya evaluación
- calcula el valor del operando de un operador integrado (dicho pr-valor no tiene un objeto resultado), o
- inicializa un objeto (dicho pr-valor se dice que tiene un objeto resultado)
- El objeto resultado puede ser una variable, un objeto creado por la expresión-new, un temporal creado por la materialización temporal, o un miembro del mismo. Ten en cuenta que las expresiones no-void descartadas tienen un objeto resultado (el temporal materializado). También, toda clase y array de categoría pr-valor tiene un objeto resultado, excepto cuando es el operando de
decltype
.
- un x-valor (un valor “que eXpira”) es un gl-valor que denota un objeto cuyos recursos pueden reutilizarse;
- un l-valor es un gl-valor que no es un x-valor;
Contenido extendido |
---|
Así llamado históricamente porque los l-valores podrían aparecer en el lado izquierdo de una expresión de asignación. En general, no siempre es el caso: void foo(); void baz() { int a; // La expresión `a` es un l-valor a = 4; // OK, podía aparecer en el lado izquierdo de una expresión de asignación int &b{a}; // La expresión `b` es un l-valor b = 5; // OK, podía aparecer en el lado izquierdo de una expresión de asignación const int &c{a}; // La expresión `c` es un l-valor c = 6; // mal formada, asignación de una referencia de solo lectura // La expresión `foo` es un l-valor // la dirección puede tomarse por el operador integrado dirección-de (&) void (*p)() = &foo; foo = baz; // mal formada, asignación de una función } |
- Un r-valor es un pr-valor o un x-valor.
Contenido extendido |
---|
Así llamado históricamente porque los r-valores podrían aparecer en el lado derecho de una expresión de asignación. En general, no siempre es el caso: Ejecuta este código #include <iostream> struct S { S() : m{42} {} S(int a) : m{a} {} int m; }; int main() { S s; // La expresión `S{}` es un pr-valor // Puede aparecer en el lado derecho de una expresión de asignación s = S{}; std::cout << s.m << '\n'; // La expresión `S{}` es un pr-valor // También puede usarse en lado izquierdo std::cout << (S{} = S{7}).m << '\n'; } Salida: 42 7 |
Nota: esta taxonomía pasó por cambios significativos con las revisiones de C++ estándar anteriores. Véase Historia posteriormente para más detalles.
Contenido extendido |
---|
A pesar de sus nombres, estos términos clasifican expresiones, no valores. Ejecuta 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)}; // La expresión `42` es un pr-valor static_assert(is_prvalue<decltype((42))>::value); // La expresión `a` es un l-valor static_assert(is_lvalue<decltype((a))>::value); // La expresión `b` es un l-valor static_assert(is_lvalue<decltype((b))>::value); // La expresión `std::move(a)` es un x-valor static_assert(is_xvalue<decltype((std::move(a)))>::value); // Type of variable `r` es una referencia a r-valor static_assert(std::is_rvalue_reference<decltype(r)>::value); // Type of variable `b` es un referencia a l-valor static_assert(std::is_lvalue_reference<decltype(b)>::value); // La expresión `r` es un l-valor static_assert(is_lvalue<decltype((r))>::value); }
|
Contenido |
[editar] Categorías primarias
[editar] l-valor
Son expresiones l-valor las siguientes:
- el nombre de una variable, una función, un objeto parámetro de plantilla (desde C++20), o un dato miembro, independientemente del tipo, como std::cin o std::endl. Incluso si el tipo de la variable es una referencia a r-valor, la expresión que consiste en su nombre es una expresión l-valor (pero véase Expresiones elegibles para movimiento);
Contenido extendido |
---|
void foo() {} void baz() { // `foo` es un l-valor // la dirección puede tomarse por el operador integrado dirección-de (&) void (*p)() = &foo; } struct foo {}; template <foo a> void baz() { const foo* obj = &a; // `a` es un l-valor, objeto parámetro de plantilla } |
- una expresión de llamada a función o de operador sobrecargado, cuyo tipo de retorno es una referencia a l-valor, 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 un l-valor, llamada a función cuyo tipo de retorno es una referencia a l-valor } |
- a = b, a += b, a %= b, y todas las demás expresiones integradas de asignación y asignación compuesta;
- ++a anyd --a, las expresiones de preincremento y predecremento integradas;
- *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 array de categoría l-valor (desde C++11);
- a.m la expresión miembro de objeto, excepto donde
m
es un miembro enumerador o una función miembro no estática, o donde a es un r-valor ym
es un dato miembro no estático tipo objeto;
Contenido extendido |
---|
struct foo { enum bar { m // miembro enumerador }; }; void baz() { foo a; a.m = 42; // mal formada, se requiere un l-valor como el operando izquierdo de la asignación } struct foo { void m() {} // función miembro no estática }; void baz() { foo a; // `a.m` es un pr-valor, por lo tando la dirección no se puede tomar // por el operador dirección-de integrado void (foo::*p1)() = &a.m; // mal formada 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 l-valor void (*p2)() = &foo::m; // lo mismo } |
- p->m, la expresión miembro de puntero integrada, excepto donde
m
es un miembro enumerador o una función miembro no estática; - a.*mp la expresión puntero a miembro de objeto integrada, donde
a
es un l-valor ymp
es un puntero a dato miembro; - p->*mp, la expresión puntero a miembro de puntero integrada, donde
mp
es un puntero a dato miembro; - a, b, la expresión coma integrada, donde b es un l-valor;
- a ? b : c, la expresión ternaria condicional para ciertas b y c (por ejemplo, cuando ambas son l-valores del mismo tipo, pero véase la definición para más detalles);
- un literal de cadena, como "Hola, mundo";
- una expresión de conversión a tipo referencia a l-valor, como static_cast<int&>(x) o static_cast<void(&)(int)>(x);
- un parámetro de plantilla de no tipo de un tipo referencia a l-valor;
template <int& v> void set() { v = 5; // el parámetro de plantilla es un l-valor } int a{3}; // variable estática, la dirección fija se conoce en tiempo de compilación void foo() { set<a>(); }
|
(desde C++11) |
Propiedades:
- Las mismas que un gl-valor (ver más abajo):
- Puede tomarse la dirección de un l-valor por medio del operador integrado dirección-de: &++i[1] y &std::endl son expresiones válidas.
- Se puede usar un l-valor modificable como el operando izquierdo de los operadores de asignación y asignación compuesta integrados.
- Se puede utilizar un l-valor para inicializar una referencia a l-valor; esto asocia un nuevo nombre con el objeto identificado por la expresión.
[editar] pr-valor
Son expresiones pr-valor las siguientes:
- un literal (excepto los literales de cadena), como 42, true o nullptr;
- una expresión de llamada a función o de operador sobrecargado, cuyo tipo de retorno no es una referencia, como str.substr(1, 2), str1 + str2, o it++;
- a++ y a--, las expresiones de posincremento y posdecremento integradas;
- a + b, a % b, a & b, a << b, y todas las otras expresiones aritméticas integradas;
- a && b, a || b, !a, las expresiones lógicas integradas.
- a < b, a == b, a >= b, y todas las demás expresiones de comparación integradas.
- &a, la expresión dirección-de;
- a.m, la expresión de miembro de objeto, donde
m
es un miembro enumerador o una función miembro no estática[2]; - p->m, la expresión miembro de puntero integrada, donde
m
es un miembro enumerador o una función miembro no estática[2]; - a.*mp, la expresión puntero a miembro de objeto, donde
mp
es un puntero a una función miembro[2] - p->*mp, la expresión de puntero a miembro de puntero integrada, donde
mp
es un puntero a una función miembro[2]; - a, b, la expresión coma integrada, donde b es un pr-valor;
- a ? b : c, la expresión ternaria condicional para algunos b y c (véase la definición para más 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 de no-tipo, de un tipo escalar;
template <int v> void foo() { // no es un l-valor, `v` es un parámetro de plantilla de tipo escalar int const int* a = &v; // mal formada v = 3; // mal formada: se requiere un l-valor como el operando izquierdo de la asignación }
|
(desde C++11) |
|
(desde C++20) |
Propiedades:
- Las mismas que un r-valor (más abajo).
- Un pr-valor no puede ser polimórfico: el tipo dinámico del objeto que denota siempre es el tipo de la expresión.
- Un pr-valor no-clase y no-array no puede ser calificado-const volatile, a menos que se materialice para poder ser vinculado a una referencia a un tipo calificado-const volatile (desde C++17). (Nota: una expresión de llamada a función o de conversión puede resultar en un pr-valor de tipo no-clase calificado-const volatile, pero generalmente el calificador-const volatile se elimina inmediatamente).
- Un pr-valor no puede tener un tipo incompleto (excepto para el tipo void, véase abajo, o cuando se usa en el especificador
decltype
). - Un pr-valor no puede tener un tipo clase abstracta o un array de los mismos.
[editar] x-valor
Las siguientes son expresiones x-valor:
a.m, la expresión de miembro de objeto, donde a es un r-valor y m
es un dato miembro no estático de un tipo objeto;
- a.*mp, la expresión de puntero a miembro de objeto, donde a es un r-valor y
mp
es un puntero a dato miembro; - a, b, la expresión comma integrada, donde b es un x-valor;
- a ? b : c, la expresión ternaria condicional para algunas b y c (véase la definición para más detalles);
|
(desde C++11) |
|
(desde C++17) |
(desde C++23) |
Propiedades:
- Las mismas que r-valor (ver más abajo).
- Las mismas que gl-valor (ver más abajo).
En particular, como todos los r-valores, los x-valores se vinculan a las referencias a r-valor, y como todos los gl-valores, los x-valores pueden ser polimórficos, y los x-valores no-clase pueden estar calificados-const volatile.
Contenido extendido |
---|
Ejecuta 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 de C++23: 7.2.1 Value category [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 r-valor es x-valor static_assert(is_xvalue<decltype( (f()) )>::value); // Expresión miembro de objeto, objeto es x-valor, `m` es un dato miembro no estático static_assert(is_xvalue<decltype( (f().m) )>::value); // Una expresión de conversión a referencia r-valor static_assert(is_xvalue<decltype( (static_cast<A&&>(a)) )>::value); // Expresión de operador, cuyo tipo de retorno es referencia r-valor a objeto static_assert(is_xvalue<decltype( (a + a) )>::value); // Expresión `ar` es l-valor, `&ar` es válida static_assert(is_lvalue<decltype( (ar) )>::value); [[maybe_unused]] A* ap = &ar; }
|
[editar] Categorías mixtas
[editar] gl-valor
Una expresión gl-valor es o bien un l-valor o un x-valor.
Propiedades:
- Un gl-valor puede ser convertido implícitamente a un pr-valor con la conversión implícita de l-valor a r-valor, de array a puntero, o de función a puntero.
- Un gl-valor puede ser polimórfico: el tipo dinámico del objeto que identifica no es necesariamente el tipo estático de la expresión.
- Un gl-valor puede tener un tipo incompleto donde se permita en la expresión.
[editar] r-valor
Una expresión r-valor es o bien un pr-valor o un x-valor.
Propiedades:
- La dirección de un r-valor no se puede obtener mediante el uso del operador dirección-de: &int(), &i++[3], &42, y &std::move(x) son inválidas.
- No se puede utilizar un r-valor como el operando del lado izquierdo de los operadores de asignación o asignación compuesta integrados.
- Un r-valor se puede utilizar para inicializar una referencia a l-valor constante , en cuyo caso el tiempo de vida del objeto identificado por el r-valor se extiende hasta el final del ámbito de la referencia.
|
(desde C++11) |
[editar] Categorías especiales
[editar] Llamada a función miembro pendiente
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 pr-valor, pero no se pueden usar para inicializar referencias, como argumentos de función, o para cualquier propósito en absoluto, excepto como argumento del lado izquierdo del operador de llamada a función, por ejemplo, (p->*pmf)(args).
[editar] Expresiones void
Las expresiones de llamada a función que devuelven void, expresiones de conversión a void, y expresiones-throw se clasifican como expresiones pr-valor, pero no se pueden utilizar para inicializar referencias o como argumentos de función. Se pueden usar en contextos de valor descartado (por ejemplo, en una línea propia, como el operando del lado izquierdo del operador coma, etc.) y en la instrucción return en una función que devuelve void. Además, las expresiones-throw se pueden usar como el segundo y el tercer operando del operador condicional ?:.
Las expresiones void no tienen un objeto resultado. |
(desde C++17) |
[editar] Campos de bits
Una expresión que designa un campo de bits (por ejemplo, a.m, donde a es un l-valor de tipo struct A { int m: 3; }) es una expresión gl-valor: se puede usar como el operando del lado izquierdo del operador de asignación, pero no se puede tomar su dirección y una referencia a l-valor no-const no se puede vincular a ella. Una referencia a l-valor const o una referencia a r-valor se puede inicializar a partir de un campo de bits gl-valor, pero se creará 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 del-valor, dicha expresión puede ser elegible para movimiento si aparece como el operando de
Si una expresión es elegible para movimiento, se trata o bien como un r-valor o como un l-valor (hasta C++23)como un r-valor (desde C++23) para fines de resolución de sobrecarga (por lo tanto, puede seleccionar el constructor de movimiento). Véase Movimiento automático desde variables locales y parámetros para más detalles. |
(desde C++11) |
[editar] Historia
[editar] CPL
El lenguaje de programación CPL fue el primero en introducir las categorías de valor para expresiones: todas las expresiones CPL se pueden evaluar en el modo de lado derecho, pero solamente ciertas clases de expresiones son significativas en el modo de lado izquierdo. Cuando se evalúa en el modo de lado derecho, una expresión se considera una regla para el cálculo de un valor (el valor del lado derecho, o r-valor). Cuando se evalúa en el modo de lado izquierdo, una expresión efectivamente produce una dirección (el valor del lado izquierdo, o l-valor). Izquierda y Derecha aquí representaban a la izquierda de la asignación y a la derecha de la asignación.
[editar] C
El lenguaje de programación C siguió una taxonomía similar, excepto que el rol de asignación ya no era significativo: las expresiones en C se dividen entre expresiones l-valor y otras (funciones y valores no objeto), donde l-valor significa una expresión que identifica a un objeto, un "valor de localizador"[4].
[editar] C++98
C++, previo al 2011, siguió el modelo de C, pero restauró el nombre r-valor para las expresiones no l-valor, hizo a las funciones l-valores, y añadió la regla por la que las referencias se pueden vincular a l-valores, pero solamente las referencias a const se pueden vincular a r-valores. Varias expresiones no l-valor en C se convirtieron en expresiones l-valor en C++.
[editar] C++11
Con la introducción de la semántica de movimiento en C++11, se redefinieron las categorías de valor para caracterizar dos propiedades independientes de las expresiones[5]:
- tiene identidad: es posible determinar si la expresión se refiere a la misma entidad como otra expresión, como al comparar las direcciones de los objetos o las funciones que identifican (obtenidas directa o indirectamente).
- se puede mover: el constructor de movimiento, el operador de asignación de movimiento, u otra función sobrecargada que implementa la semántica de movimiento puede vincularse a la expresión.
En C++11, las expresiones que:
- tienen identidad y no se pueden mover se llaman expresiones l-valor;
- tienen identidad y se pueden mover se llaman expresiones x-valor;
- no tienen identidad y se pueden mover se llaman expresiones pr-valor ("r-valor puro");
- no tienen identidad y no se pueden mover no se usan[6].
Las expresiones que tienen identidad se llaman "expresiones gl-valor" ("l-valor generalizado"). Tanto los l-valores como los x-valores son expresiones gl-valor.
Las expresiones que se pueden mover se llaman "expresiones r-valor". Tanto los r-valores como los x-valores son expresiones r-valor.
[editar] C++17
En C++17, la elisión de copia se hizo obligatoria en algunas situaciones, y eso requirió la separación de las expresiones pr-valor de los objetos temporales inicializados por ellas, resultando el sistema que tenemos hoy. Ten en cuenta que, en contraste con el esquema de C++11, no se puede mover a partir de los pr-valores.
[editar] Notas al pie
- ↑ asumiendo que i tiene un tipo integrado o el operador de preincremento está sobrecargado para que devuelva una referencia a l-valor.
- ↑ 2,0 2,1 2,2 2,3 Categoría r-valor especial, véase la llamada a función miembro pendiente.
- ↑ Asumiendo que i tiene un tipo integrado o el operador de posincremento no está sobrecargado para que devuelva una referencia a l-valor.
- ↑ "Hay diferentes opiniones en la comunidad de C respecto al significado de l-valor, unos consideran que un l-valor puede ser cualquier clase de localizador de objeto, mientras otros sostienen que un l-valor es significativo en el lado izquierdo de un operador de asignación. El Comité C89 adoptó la definición de l-valor como un localizador de objeto." -- ANSI C Rationale, 6.3.2.1/10.
- ↑ "Nueva" Terminología de Valor por Bjarne Stroustrup, 2010.
- ↑ los pr-valores const (solamente permitidos para tipos clase) y los x-valores const no vinculan a las sobrecargas de T&&, pero vinculan sobrecargas de const T&&, que también están clasificados como "constructor de movimiento" y "operador de asignación de movimiento" por el estándar, satisfaciendo la definición de "se puede mover" a los efectos 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 pr-valores const y los x-valores const se vinculan a las sobrecargas de const T&.
[editar] Referencias
- El estándar C++23 (ISO/IEC 14882:2023):
- 7.2.1 Value category [basic.lval]
- El estándar C++20 (ISO/IEC 14882:2020):
- 7.2.1 Value category [basic.lval]
- El estándar C++17 (ISO/IEC 14882:2017):
- 6.10 Lvalues and rvalues [basic.lval]
- El estándar C++14 (ISO/IEC 14882:2014):
- 3.10 Lvalues and rvalues [basic.lval]
- El estándar C++11 (ISO/IEC 14882:2011):
- 3.10 Lvalues and rvalues [basic.lval]
- El estándar C++98 (ISO/IEC 14882:1998):
- 3.10 Lvalues and rvalues [basic.lval]
[editar] Informes de defectos
Los siguientes informes de defectos de cambio de comportamiento se aplicaron de manera retroactiva a los estándares de C++ publicados anteriormente.
ID | Aplicado a | Comportamiento según lo publicado | Comportamiento correcto |
---|---|---|---|
CWG 616 | C++11 | El acceso a miembro y acceso a miembro con puntero a miembro de un r-valor daba como resultado un pr-valor. |
Reclasificado como un x-valor. |
CWG 1213 | C++11 | El subíndice de un r-valor array daba como resultado un l-valor. | Reclasificado como un x-valor |
[editar] External links
Categorías de valor y decltype en C++ demistificados — David Mazières, 2021 | |||
Determinar empíricamente la categoría de valor de una expresión — StackOverflow
[editar] Véase también
| |||