Namespaces
Variants

Value categories

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Value categories
Order of evaluation
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

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
}
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:

#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.

#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 :

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 m es un miembro enumerador o una función miembro no estática, o cuando a es un valor-r y m es 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 m es 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 mp es un puntero a miembro de datos;
  • p - > * mp , la expresión incorporada de puntero a miembro de puntero , donde mp es 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>();
}
  • una llamada a función o una expresión de operador sobrecargado, cuyo tipo de retorno es referencia a rvalue a función;
  • una expresión de conversión a tipo referencia a rvalue a función, tal como static_cast < void ( && ) ( int ) > ( x ) .
(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 :

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:

xvalue

Las siguientes expresiones son xvalue expressions :

  • una llamada a función o una expresión de operador sobrecargado, cuyo tipo de retorno es referencia a valor derecho a objeto, como std :: move ( x ) ;
  • a [ n ] , la expresión de subíndice incorporada, donde un operando es un valor derecho de array;
  • una expresión de conversión a tipo de referencia a valor derecho a objeto, como static_cast < char && > ( x ) ;
(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
#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.
  • Un rvalue puede utilizarse para inicializar una referencia a rvalue , en cuyo caso el tiempo de vida del objeto temporal identificado por el rvalue es extendido hasta que finalice el ámbito de la referencia.
  • Cuando se utiliza como argumento de función y cuando dos sobrecargas de la función están disponibles, una que toma parámetro de referencia a rvalue y la otra que toma referencia a lvalue a parámetro const, un rvalue se vincula a la sobrecarga de referencia a rvalue (así, si tanto el constructor de copia como el de movimiento están disponibles, un argumento rvalue invoca el move constructor , y de manera similar con los operadores de asignación de copia y movimiento).
(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 movimiento

Aunque 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

  1. Asumiendo que i tiene tipo incorporado o el operador de pre-incremento está sobrecargado para devolver por referencia de lvalue.
  2. 2.0 2.1 2.2 2.3 Categoría especial de rvalue, ver llamada a función miembro pendiente .
  3. Asumiendo que i tiene tipo incorporado o el operador de post-incremento no está sobrecargado para devolver por referencia de lvalue.
  4. "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.
  5. Nueva Terminología de Valores por Bjarne Stroustrup, 2010.
  6. 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