dev - c++17
¿Qué son rvalues, lvalues, xvalues, glvalues y prvalues? (10)
En C ++ 03, una expresión es un rvalue o un lvalue .
En C ++ 11, una expresión puede ser una:
- valor
- valor
- xvalor
- glvalue
- prvalue
Dos categorías se han convertido en cinco categorías.
- ¿Cuáles son estas nuevas categorías de expresiones?
- ¿Cómo se relacionan estas nuevas categorías con las categorías rvalue y lvalue existentes?
- ¿Las categorías rvalue y lvalue en C ++ 0x son las mismas que en C ++ 03?
- ¿Por qué son necesarias estas nuevas categorías? ¿Están los dioses del WG21 tratando de confundirnos como simples mortales?
¿Cómo se relacionan estas nuevas categorías con las categorías rvalue y lvalue existentes?
Un valor de C ++ 03 sigue siendo un valor de C ++ 11, mientras que un valor de C ++ 03 se denomina prvalue en C ++ 11.
¿Cuáles son estas nuevas categorías de expresiones?
El FCD (n3092) tiene una excelente descripción:
- Un lvalue (llamado así, históricamente, porque los valores pueden aparecer en el lado izquierdo de una expresión de asignación) designa una función o un objeto. [Ejemplo: Si E es una expresión de tipo puntero, entonces * E es una expresión de valor l que se refiere al objeto o función a la que E apunta. Como otro ejemplo, el resultado de llamar a una función cuyo tipo de retorno es una referencia de valor l es un valor l. —En ejemplo]
- Un xvalue (un valor "eXpiring") también se refiere a un objeto, generalmente cerca del final de su vida útil (por ejemplo, para poder mover sus recursos). Un xvalue es el resultado de ciertos tipos de expresiones que involucran referencias de rvalue (8.3.2). [Ejemplo: el resultado de llamar a una función cuyo tipo de retorno es una referencia rvalue es un xvalue. —En ejemplo]
- Un glvalue (lvalue "generalizado") es un lvalue o un xvalue.
- Un rvalue (llamado así, históricamente, porque los rvalues podrían aparecer en el lado derecho de una expresión de asignación) es un xvalue, un objeto temporal (12.2) o un subobjeto del mismo, o un valor que no está asociado con un objeto.
- Un prvalue (rvalue "puro") es un rvalue que no es un xvalue. [Ejemplo: el resultado de llamar a una función cuyo tipo de retorno no es una referencia es un prvalue. El valor de un literal como 12, 7.3e5 o true también es un prvalue. —En ejemplo]
Cada expresión pertenece exactamente a una de las clasificaciones fundamentales en esta taxonomía: lvalue, xvalue o prvalue. Esta propiedad de una expresión se llama su categoría de valor. [Nota: la discusión de cada operador incorporado en la Cláusula 5 indica la categoría del valor que produce y las categorías de valor de los operandos que espera. Por ejemplo, los operadores de asignación incorporados esperan que el operando de la izquierda sea un lvalor y que el derecho sea un prvalue y produzca un lvalue como resultado. Los operadores definidos por el usuario son funciones, y las categorías de valores que esperan y producen están determinadas por sus parámetros y tipos de retorno. Nota final
Le sugiero que lea toda la sección 3.10 Valores y valores .
¿Cómo se relacionan estas nuevas categorías con las categorías rvalue y lvalue existentes?
Otra vez:
¿Las categorías rvalue y lvalue en C ++ 0x son las mismas que en C ++ 03?
La semántica de los valores ha evolucionado particularmente con la introducción de la semántica del movimiento.
¿Por qué son necesarias estas nuevas categorías?
Para que se pueda definir y apoyar la construcción / asignación de movimientos.
¿Por qué son necesarias estas nuevas categorías? ¿Están los dioses del WG21 tratando de confundirnos como simples mortales?
No creo que las otras respuestas (aunque muchas de ellas son buenas) realmente capturen la respuesta a esta pregunta en particular. Sí, estas categorías y tales existen para permitir la semántica de movimiento, pero la complejidad existe por una razón. Esta es la única regla inviolable de mover cosas en C ++ 11:
Debes moverte solo cuando sea indudablemente seguro hacerlo.
Es por eso que existen estas categorías: poder hablar sobre valores donde es seguro moverse de ellos, y hablar sobre valores donde no es.
En la versión más antigua de las referencias de valor r, el movimiento sucedió fácilmente. Demasiado facil Tan fácilmente que existía un gran potencial para mover cosas implícitamente cuando el usuario realmente no quería hacerlo.
Aquí están las circunstancias bajo las cuales es seguro mover algo:
- Cuando se trata de un objeto temporal o subobjetivo del mismo. (prvalue)
- Cuando el usuario ha dicho explícitamente que lo mueva .
Si haces esto:
SomeType &&Func() { ... }
SomeType &&val = Func();
SomeType otherVal{val};
¿Qué hace esto? En versiones anteriores de la especificación, antes de que entraran los 5 valores, esto provocaría un movimiento. Claro que lo hace. Pasó una referencia rvalue al constructor y, por lo tanto, se enlaza al constructor que toma una referencia rvalue. Eso es obvio.
Hay solo un problema con esto; No pediste moverlo. Oh, podrías decir que el &&
debería haber sido una pista, pero eso no cambia el hecho de que rompió la regla. val
no es temporal porque los temporarios no tienen nombre. Es posible que haya extendido la vida útil de lo temporal, pero eso significa que no es temporal ; Es como cualquier otra variable de pila.
Si no es temporal, y no pidió moverlo, entonces moverlo es incorrecto.
La solución obvia es hacer de val
un valor. Esto significa que no puedes moverte de eso. Está bien; se llama así que es un valor
Una vez que haces eso, ya no puedes decir que SomeType&&
significa lo mismo en todas partes. Ahora ha hecho una distinción entre las referencias de rvalue con nombre y las referencias de rvalue sin nombre. Bueno, las referencias de rvalue nombradas son lvalues; Esa fue nuestra solución arriba. Entonces, ¿a qué llamamos referencias de rvalor sin nombre (el valor de retorno de Func
arriba)?
No es un lvalor, porque no puedes moverte de un lvalor. Y necesitamos poder movernos devolviendo un &&
; ¿De qué otra manera podrías decir explícitamente para mover algo? Eso es lo que devuelve std::move
, después de todo. No es un valor (estilo antiguo), porque puede estar en el lado izquierdo de una ecuación (las cosas son un poco más complicadas, consulte esta pregunta y los comentarios a continuación). No es ni un valor ni un valor; Es un nuevo tipo de cosas.
Lo que tenemos es un valor que puede tratar como un valor l, excepto que se puede mover de forma implícita. Lo llamamos un xvalor.
Tenga en cuenta que los valores de x son los que nos hacen ganar las otras dos categorías de valores:
Un prvalue es realmente el nuevo nombre para el tipo anterior de rvalue, es decir, son los rvalues que no son xvalues.
Los glvalores son la unión de xvalues y lvalues en un grupo, porque comparten muchas propiedades en común.
Así que, en realidad, todo se reduce a valores de x y la necesidad de restringir el movimiento exactamente y solo en ciertos lugares. Esos lugares están definidos por la categoría de valor; los prvalores son los movimientos implícitos y los xvalues son los movimientos explícitos ( std::move
devuelve un xvalue).
INTRODUCCIÓN
ISOC ++ 11 (oficialmente ISO / IEC 14882: 2011) es la versión más reciente del estándar del lenguaje de programación C ++. Contiene algunas características y conceptos nuevos, por ejemplo:
- referencias de valor
- xvalue, glvalue, prvalue expresión valor categorías
- mover semantica
Si queremos entender los conceptos de las nuevas categorías de valores de expresión, debemos ser conscientes de que hay referencias de rvalue y lvalue. Es mejor saber que los valores se pueden pasar a referencias de valores no constantes.
int& r_i=7; // compile error
int&& rr_i=7; // OK
Podemos obtener cierta intuición de los conceptos de las categorías de valor si citamos la subsección titulada Valores y valores del borrador de trabajo N3337 (el borrador más similar al estándar ISOC ++ 11 publicado).
3.10 Lvalues y rvalues [basic.lval]
1 Las expresiones se clasifican de acuerdo con la taxonomía en la Figura 1.
- Un lvalue (llamado así, históricamente, porque los valores pueden aparecer en el lado izquierdo de una expresión de asignación) designa una función o un objeto. [Ejemplo: Si E es una expresión de tipo puntero, entonces * E es una expresión de valor l que se refiere al objeto o función a la que E apunta. Como otro ejemplo, el resultado de llamar a una función cuyo tipo de retorno es una referencia de valor l es un valor l. —En ejemplo]
- Un xvalue (un valor "eXpiring") también se refiere a un objeto, generalmente cerca del final de su vida útil (por ejemplo, para poder mover sus recursos). Un xvalue es el resultado de ciertos tipos de expresiones que involucran referencias de rvalue (8.3.2). [Ejemplo: el resultado de llamar a una función cuyo tipo de retorno es una referencia rvalue es un xvalue. —En ejemplo]
- Un glvalue (lvalue "generalizado") es un lvalue o un xvalue.
- Un rvalue (llamado así, históricamente, porque los rvalues podrían aparecer en el lado derecho de una expresión de asignación) es un xvalue, un
objeto temporal (12.2) o subobjeto del mismo, o un valor que no es
asociado a un objeto.- Un prvalue (rvalue "puro") es un rvalue que no es un xvalue. [Ejemplo: el resultado de llamar a una función cuyo tipo de retorno no es un
La referencia es un prvalue. El valor de un literal como 12, 7.3e5, o
true es también un prvalue. —En ejemplo]Cada expresión pertenece exactamente a una de las clasificaciones fundamentales en esta taxonomía: lvalue, xvalue o prvalue. Esta propiedad de una expresión se llama su categoría de valor.
Pero no estoy muy seguro de que esta subsección sea suficiente para entender los conceptos claramente, porque "generalmente" no es realmente general, "cerca del final de su vida útil" no es realmente concreto ", lo que implica referencias de valor" no es realmente claro, y "Ejemplo: el resultado de llamar a una función cuyo tipo de retorno es una referencia rvalue es un xvalue". Suena como si una serpiente estuviera mordiendo su cola.
CATEGORÍAS DE VALOR PRIMARIO
Cada expresión pertenece exactamente a una categoría de valor principal. Estas categorías de valores son las categorías lvalue, xvalue y prvalue.
valores
La expresión E pertenece a la categoría lvalue si y solo si E se refiere a una entidad que YA ha tenido una identidad (dirección, nombre o alias) que la hace accesible fuera de E.
#include <iostream>
int i=7;
const int& f(){
return i;
}
int main()
{
std::cout<<&"www"<<std::endl; // This address ...
std::cout<<&"www"<<std::endl; // ... and this address are the same.
"www"; // The expression "www" in this row is an lvalue expression, because it refers to the same entity ...
"www"; // ... as the entity the expression "www" in this row refers to.
i; // The expression i in this row is an lvalue expression, because it refers to the same entity ...
i; // ... as the entity the expression i in this row refers to.
int* p_i=new int(7);
*p_i; // The expression *p_i in this row is an lvalue expression, because it refers to the same entity ...
*p_i; // ... as the entity the expression *p_i in this row refers to.
const int& r_I=7;
r_I; // The expression r_I in this row is an lvalue expression, because it refers to the same entity ...
r_I; // ... as the entity the expression r_I in this row refers to.
f(); // The expression f() in this row is an lvalue expression, because it refers to the same entity ...
i; // ... as the entity the expression f() in this row refers to.
return 0;
}
valores de x
La expresión E pertenece a la categoría xvalue si y solo si es
- el resultado de llamar a una función, ya sea implícita o explícitamente, cuyo tipo de retorno es una referencia rvalue al tipo de objeto que se devuelve, o
int&& f(){
return 3;
}
int main()
{
f(); // The expression f() belongs to the xvalue category, because f() return type is an rvalue reference to object type.
return 0;
}
- una conversión a una referencia rvalue al tipo de objeto, o
int main()
{
static_cast<int&&>(7); // The expression static_cast<int&&>(7) belongs to the xvalue category, because it is a cast to an rvalue reference to object type.
std::move(7); // std::move(7) is equivalent to static_cast<int&&>(7).
return 0;
}
- una expresión de acceso de miembro de clase que designa un miembro de datos no estáticos de tipo no de referencia en el que la expresión de objeto es un valor x, o
struct As
{
int i;
};
As&& f(){
return As();
}
int main()
{
f().i; // The expression f().i belongs to the xvalue category, because As::i is a non-static data member of non-reference type, and the subexpression f() belongs to the xvlaue category.
return 0;
}
- una expresión de puntero a miembro en la que el primer operando es un valor x y el segundo es un puntero al miembro de datos.
Tenga en cuenta que el efecto de las reglas anteriores es que las referencias de rvalue con nombre a los objetos se tratan como lvalues y las referencias de rvalue sin nombre a los objetos se tratan como xvalues; Las referencias de rvalue a las funciones se tratan como lvalues ya sea nombrados o no.
#include <functional>
struct As
{
int i;
};
As&& f(){
return As();
}
int main()
{
f(); // The expression f() belongs to the xvalue category, because it refers to an unnamed rvalue reference to object.
As&& rr_a=As();
rr_a; // The expression rr_a belongs to the lvalue category, because it refers to a named rvalue reference to object.
std::ref(f); // The expression std::ref(f) belongs to the lvalue category, because it refers to an rvalue reference to function.
return 0;
}
prvalores
La expresión E pertenece a la categoría prvalue si y solo si E no pertenece ni a lvalue ni a xvalue.
struct As
{
void f(){
this; // The expression this is a prvalue expression. Note, that the expression this is not a variable.
}
};
As f(){
return As();
}
int main()
{
f(); // The expression f() belongs to the prvalue category, because it belongs neither to the lvalue nor to the xvalue category.
return 0;
}
CATEGORÍAS DE VALOR MIXTO
Hay otras dos categorías importantes de valores mixtos. Estas categorías de valores son categorías de valor y glvalor.
valores
La expresión E pertenece a la categoría rvalue si y solo si E pertenece a la categoría xvalue, o a la categoría prvalue.
Tenga en cuenta que esta definición significa que la expresión E pertenece a la categoría rvalue si y solo si E se refiere a una entidad que no ha tenido ninguna identidad que la haga accesible fuera de E YET.
glvalores
La expresión E pertenece a la categoría glvalue si y solo si E pertenece a la categoría lvalue, o a la categoría xvalue.
UNA REGLA PRÁCTICA
Scott Meyer ha published una regla general muy útil para distinguir los valores de los valores de l.
- Si puede tomar la dirección de una expresión, la expresión es un valor l.
- Si el tipo de una expresión es una referencia de valor l (por ejemplo, T & o const T &, etc.), esa expresión es un valor l.
- De lo contrario, la expresión es un rvalor. Conceptualmente (y generalmente también de hecho), los valores corresponden a objetos temporales, como los que se devuelven desde funciones o se crean a través de conversiones de tipo implícitas. La mayoría de los valores literales (por ejemplo, 10 y 5.3) también son valores r.
Empezaré con tu última pregunta:
¿Por qué son necesarias estas nuevas categorías?
El estándar de C ++ contiene muchas reglas que tratan con la categoría de valor de una expresión. Algunas reglas hacen una distinción entre lvalue y rvalue. Por ejemplo, cuando se trata de resolución de sobrecargas. Otras reglas hacen una distinción entre glvalue y prvalue. Por ejemplo, puede tener un valor predeterminado con un tipo incompleto o abstracto, pero no hay un valor predeterminado con un tipo incompleto o abstracto. Antes de que tuviéramos esta terminología, las reglas que realmente necesitan distinguir entre glvalue / prvalue se referían a lvalue / rvalue y estaban equivocadas o contenían muchas explicaciones y excepciones a la regla a la ... "a menos que el valor se deba a un sin nombre referencia rvalue ... ". Entonces, parece una buena idea simplemente dar a los conceptos de glvalues y prvalues su propio nombre.
¿Cuáles son estas nuevas categorías de expresiones? ¿Cómo se relacionan estas nuevas categorías con las categorías rvalue y lvalue existentes?
Todavía tenemos los términos lvalue y rvalue que son compatibles con C ++ 98. Acabamos de dividir los valores en dos subgrupos, valores y valores, y nos referimos a valores y valores como valores. Los valores de X son un nuevo tipo de categoría de valor para las referencias de valores sin nombre. Cada expresión es uno de estos tres: lvalue, xvalue, prvalue. Un diagrama de Venn se vería así:
______ ______
/ X /
/ / / /
| l | x | pr |
/ / / /
/______X______/
gl r
Ejemplos con funciones:
int prvalue();
int& lvalue();
int&& xvalue();
Pero también no olvide que las referencias de rvalue nombradas son lvalues:
void foo(int&& t) {
// t is initialized with an rvalue expression
// but is actually an lvalue expression itself
}
En mi humilde opinión, la mejor explicación sobre su significado nos dio a stroustrup.com/terminology.pdf + tener en cuenta ejemplos de Dániel Sándor y Mohan :
Stroustrup:
Ahora estaba seriamente preocupado. Claramente nos dirigimos a un callejón sin salida o un lío o ambos. Pasé la hora del almuerzo haciendo un análisis para ver cuáles de las propiedades (de los valores) eran independientes. Sólo había dos propiedades independientes:
has identity
, es decir, una dirección, un puntero, el usuario puede determinar si dos copias son idénticas, etc.can be moved from
- es decir, se nos permite salir al origen de una "copia" en algún estado indeterminado, pero válidoEsto me llevó a la conclusión de que hay exactamente tres tipos de valores (usando el truco de la expresión regular de uso de una letra mayúscula para indicar un negativo - tenía prisa):
iM
: tiene identidad y no se puede mover desdeim
: tiene identidad y se puede mover desde (por ejemplo, el resultado de convertir un lvalue en una referencia de rvalue)Im
: no tiene identidad y se puede mover desde la cuarta posibilidad (IM
: no tiene identidad y no se puede mover) no es útil enC++
(o, creo) en ningún otro idioma.Además de estas tres clasificaciones fundamentales de valores, tenemos dos generalizaciones obvias que corresponden a las dos propiedades independientes:
i
: tiene identidadm
: se puede mover deEsto me llevó a poner este diagrama en la pizarra:
Nombrar
Observé que solo teníamos una libertad limitada para nombrar: los dos puntos a la izquierda (etiquetados como
iM
ei
) son lo que las personas con más o menos formalidad han llamadolvalues
y los dos puntos a la derecha (etiquetados comom
yIm
) son lo que las personas Con más o menos formalidad se han llamadorvalues
. Esto debe reflejarse en nuestro nombramiento. Es decir, la "pierna" izquierda de laW
debe tener nombres relacionados con el valorlvalue
y la "pierna" derecha de laW
debe tener nombres relacionados con el valorrvalue.
Observo que toda esta discusión / problema surge de la introducción de referencias de valores y la semántica de movimientos. Estas nociones simplemente no existen en el mundo de Strachey y consisten solo enlvalues
ylvalues
. Alguien observó que las ideas que
- Cada
value
es unrvalue
o unrvalue
- Un
rvalue
no es unrvalue
y unrvalue
no es unlvalue
están profundamente arraigados en nuestra conciencia, propiedades muy útiles y se pueden encontrar rastros de esta dicotomía en todo el borrador de la norma. Todos estuvimos de acuerdo en que deberíamos preservar esas propiedades (y hacerlas precisas). Esto restringió aún más nuestras opciones de nombres.
rvalue
que la redacción estándar de la biblioteca usarvalue
para significarm
(la generalización), de modo que para preservar la expectativa y el texto de la biblioteca estándar, el punto inferior derecho de laW
debe llamarservalue.
Esto llevó a una discusión enfocada de nombrar. Primero, necesitamos decidir el valor
lvalue.
¿Debelvalue
significariM
o la generalizacióni
? Dirigidos por Doug Gregor, enumeramos los lugares en la redacción del lenguaje central donde la palabralvalue
fue calificada para significar el uno o el otro. Se hizo una lista y en la mayoría de los casos, y en el valor más complicado / quebradizo,lvalue
actualmente significaiM
. Este es el significado clásico de lvalue porque "en los viejos tiempos" no se movía nada;move
es una noción novedosa enC++0x
. Además, nombrar el punto de topleft del valor deW
lvalue
nos da la propiedad de que cada valor es un valorrvalue
orvalue
, pero no ambos.Por lo tanto, el punto superior izquierdo de
W
eslvalue
y el punto inferior derecho esrvalue.
¿Qué hace que los puntos de la parte inferior izquierda y superior derecha? El punto inferior izquierdo es una generalización del lvalor clásico, que permite el movimiento. Por eso es un valorgeneralized lvalue.
Loglvalue.
Puede objetar sobre la abreviatura, pero (creo) no con la lógica. Asumimos que, en uso serio, el valorgeneralized lvalue
se abreviaría de alguna manera, por lo que es mejor que lo hagamos de inmediato (o arriesguemos a confusión) El punto superior derecho de la W es menos general que el derecho inferior (ahora, como siempre, llamadorvalue
). Ese punto representa la noción pura original de un objeto desde el que puedes moverte porque no se puede hacer referencia a él nuevamente (excepto por un destructor). Me gustó la frasespecialized rvalue
en contraste conspecialized rvalue
generalized lvalue
peropure rvalue
abreviado aprvalue
won out (y probablemente con razón). Entonces, la pierna izquierda de W esglvalue
yglvalue
y la pierna derecha esprvalue
yrvalue.
Incidentalmente, cada valor es un valor de glvalue o prvalue, pero no ambos.Esto deja la mitad superior de la
W
:im
; Es decir, valores que tienen identidad y pueden ser movidos. Realmente no tenemos nada que nos guíe a un buen nombre para esas bestias esotéricas. Son importantes para las personas que trabajan con el texto estándar (borrador), pero es poco probable que se conviertan en un nombre familiar. No encontramos ninguna limitación real en los nombres para guiarnos, así que elegimos ''x'' para el centro, lo desconocido, lo extraño, solo el xpert, o incluso la calificación x.
Las categorías de C ++ 03 están demasiado restringidas para capturar la introducción de las referencias de rvalue correctamente en los atributos de expresión.
Con la introducción de ellos, se dijo que una referencia de rvalor sin nombre se evalúa como un valor de r, de modo que la resolución de sobrecarga preferiría los enlaces de referencia de rvalue, lo que haría que seleccionara mover constructores en lugar de copiar constructores. Pero se encontró que esto causa problemas en todo, por ejemplo con los tipos dinámicos y con las calificaciones.
Para mostrar esto, considere
int const&& f();
int main() {
int &&i = f(); // disgusting!
}
En los borradores pre-xvalor, esto estaba permitido, porque en C ++ 03, los valores de los tipos que no son de clase nunca se califican como CV. Pero se pretende que const
aplique en el caso de rvalue-reference, porque aquí nos referimos a objetos (¡= memoria!), Y descartar const de valores no de clase se debe principalmente a que no hay ningún objeto alrededor.
El problema para los tipos dinámicos es de naturaleza similar. En C ++ 03, los valores de tipo de clase tienen un tipo dinámico conocido: es el tipo estático de esa expresión. Porque para tenerlo de otra manera, necesita referencias o desreferencias, que se evalúan a un valor l. Eso no es cierto con referencias de valores sin nombre, pero pueden mostrar un comportamiento polimórfico. Así que para resolverlo,
Las referencias de rvalue sin nombre se convierten en xvalues . Pueden ser calificados y potencialmente tener su tipo dinámico diferente. Ellos, como se pretende, prefieren las referencias de valor r durante la sobrecarga, y no se unirán a las referencias de valor no const.
Lo que anteriormente era un rvalue (literales, objetos creados por conversiones a tipos que no son de referencia) ahora se convierte en un prvalue . Tienen la misma preferencia que los valores de x durante la sobrecarga.
Lo que antes era un lvalor permanece un lvalor.
Y se realizan dos agrupamientos para capturar aquellos que pueden calificarse y pueden tener diferentes tipos dinámicos ( valores de glúteos ) y aquellos en los que la sobrecarga prefiere el enlace de referencia de valores ( valores ).
Supongo que este documento podría servir como una introducción no tan breve: n3055
Toda la masacre comenzó con la semántica del movimiento. Una vez que tenemos expresiones que se pueden mover y no copiar, de repente, las reglas exigieron una distinción entre las expresiones que se pueden mover y en qué dirección.
Por lo que supongo que en base al borrador, la distinción de valor r / l permanece igual, solo en el contexto de cosas en movimiento que se ensucian.
¿Son necesarios? Probablemente no si deseamos renunciar a las nuevas funcionalidades. Pero para permitir una mejor optimización probablemente deberíamos abrazarlos.
Citando n3055 :
- Un lvalue (llamado así, históricamente, porque lvalues podrían aparecer en el lado izquierdo de una expresión de asignación) designa una función o un objeto. [Ejemplo: Si
E
es una expresión de tipo puntero, entonces*E
es una expresión de valor l que se refiere al objeto o función a la queE
apunta. Como otro ejemplo, el resultado de llamar a una función cuyo tipo de retorno es una referencia lvalue es un lvalue.] - Un xvalue (un valor "eXpiring") también se refiere a un objeto, generalmente cerca del final de su vida útil (por ejemplo, para poder mover sus recursos). Un xvalue es el resultado de ciertos tipos de expresiones que involucran referencias de rvalue. [Ejemplo: el resultado de llamar a una función cuyo tipo de retorno es una referencia rvalue es un xvalue.]
- Un glvalue ( lvalue "generalizado") es un lvalue o un xvalue .
- Un rvalue (llamado, históricamente, porque los rvalues pueden aparecer en el lado derecho de una expresión de asignación) es un xvalue, un objeto temporal o un subobjeto del mismo, o un valor que no está asociado con un objeto.
- Un prvalue ( rvalue "puro") es un rvalue que no es un xvalue. [Ejemplo: el resultado de llamar a una función cuyo tipo de retorno no es una referencia es un prvalue]
El documento en cuestión es una gran referencia para esta pregunta, ya que muestra los cambios exactos en el estándar que han ocurrido como resultado de la introducción de la nueva nomenclatura.
He luchado con esto durante mucho tiempo, hasta que encontré la explicación de cppreference.com de las categorías de valor .
En realidad es bastante simple, pero me parece que a menudo se explica de una manera que es difícil de memorizar. Aquí se explica muy esquemáticamente. Citaré algunas partes de la página:
Categorias primarias
Las principales categorías de valor corresponden a dos propiedades de las expresiones:
tiene identidad : es posible determinar si la expresión se refiere a la misma entidad que otra expresión, como al comparar direcciones de los objetos o las funciones que identifican (obtenidas directa o indirectamente);
se puede mover desde : mover constructor, mover operador de asignación, u otra función de sobrecarga que implemente mover la semántica puede enlazar a la expresión.
Expresiones que:
- tienen identidad y no se pueden mover de ellas se llaman expresiones de valor l ;
- tienen identidad y se pueden mover desde se llaman expresiones xvalue ;
- no tienen identidad y se pueden mover desde ellas se denominan expresiones prvalue ;
- No tienen identidad y no se pueden mover desde que no se usan.
valor
Una expresión lvalue ("valor izquierdo") es una expresión que tiene identidad y no se puede mover .
rvalue (hasta C ++ 11), prvalue (desde C ++ 11)
Una expresión prvalue ("pure rvalue") es una expresión que no tiene identidad y se puede mover desde .
xvalor
Una expresión xvalue ("valor que expira") es una expresión que tiene identidad y se puede mover desde .
glvalue
Una expresión glvalue ("lvalue generalizado") es una expresión que es un lvalue o un xvalue. Tiene identidad . Puede o no puede ser movido de.
valor (desde C ++ 11)
Una expresión de rvalue ("valor correcto") es una expresión que es un prvalue o un xvalue. Se puede mover desde . Puede o no tener identidad.
Una adición a las excelentes respuestas anteriores, sobre un punto que me confundió incluso después de haber leído Stroustrup y pensé que entendía la distinción entre valor / valor. Cuando veas
int&& a = 3
,
es muy tentador leerlo int&&
como un tipo y concluir que a
es un valor. No es:
int&& a = 3;
int&& c = a; //error: cannot bind ''int'' lvalue to ''int&&''
int& b = a; //compiles
a
tiene un nombre y es ipso facto un lvalue. No pienses en el &&
como parte del tipo de a
; Es solo algo que te dice a qué a
está permitido unirse.
Esto es particularmente importante para los T&&
argumentos de tipo en los constructores. Si tú escribes
Foo::Foo(T&& _t) : t{_t} {}
va a copiar _t
en t
. Necesitas
Foo::Foo(T&& _t) : t{std::move(_t)} {}
si quieres moverte ¡Ojalá mi compilador me avisara cuando dejé fuera el move
!