¿Qué son los valores, valores, valores x, valores y valores?


1357

En C ++ 03, una expresión es un valor r o un valor l .

En C ++ 11, una expresión puede ser un:

  1. rvalue
  2. lvalue
  3. xvalue
  4. glvalue
  5. 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?
  • ¿Son las categorías rvalue y lvalue en C ++ 0x las mismas que en C ++ 03?
  • ¿Por qué se necesitan estas nuevas categorías? ¿Están los dioses del WG21 tratando de confundirnos como simples mortales?

99
@Philip Potter: ¿En C ++ 03? Si. Un lvalue se puede usar como rvalue porque hay una conversión estándar de lvalue-to-rvalue.
James McNellis

14
@Tyler: "Si puede asignarle, es un valor l, de lo contrario, es un valor r". -> incorrecto, se puede asignar a rvalues clase: string("hello") = string("world").
fredoverflow

44
Tenga en cuenta que esta es la categoría de valor. Hay más propiedades que las expresiones pueden tener. Estos incluyen campo de bits (verdadero / falso), temporal (verdadero / falso) y tipo (el tipo de éste).
Johannes Schaub - litb

30
Creo que el enlace de Fred anterior es mejor que cualquiera de las respuestas aquí. Sin embargo, el enlace está muerto. Fue trasladado a: stroustrup.com/terminology.pdf
R. Martinho Fernandes el

74
en C ++ incluso tus tipos tienen tipos
nielsbot

Respuestas:


634

Supongo que este documento podría servir como una introducción no tan corta: 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 fáciles de entender exigieron la distinción entre expresiones que se pueden mover y en qué dirección.

Por lo que supongo basado en el borrador, la distinción del valor r / l permanece igual, solo en el contexto de mover las cosas se vuelven desordenadas.

¿Son necesarios? Probablemente no si deseamos perder las nuevas funciones. Pero para permitir una mejor optimización, probablemente deberíamos adoptarlos.

Citando n3055 :

  • Un valor l (denominado históricamente porque los valores l podrían aparecer en el lado izquierdo de una expresión de asignación) designa una función o un objeto. [Ejemplo: si Ees 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 lvalue es un lvalue.]
  • Un xValue (un “expira” valor) también se refiere a un objeto, por lo general cerca del final de su vida útil (de modo que sus recursos se pueden mover, por ejemplo). 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 ( “generalizada” valor-I) es un valor-I o un xValue .
  • Un valor r (denominado históricamente porque los valores pueden aparecer en el lado derecho de una expresión de asignación) es un valor x, un objeto temporal o subobjeto del mismo, o un valor que no está asociado con un objeto.
  • Un valor prva (valor "puro") es un valor r que no es un valor x. [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 sucedido como resultado de la introducción de la nueva nomenclatura.


¡Gracias, esta respuesta es realmente útil! Pero mi compilador no está de acuerdo con sus ejemplos para xvalues ​​y prvalues; son exactamente lo contrario. Devolver por referencia de valor me da un valor, y devolver por valor me da un valor x. ¿Los confundiste o mi banco de pruebas está roto? Intenté esto con GCC 4.6.1, clang (de svn) y MSVC, y todos muestran el mismo comportamiento.
Kim Gräsman

Vaya, acabo de seguir el enlace y noté que los ejemplos están en la fuente. Iré a buscar mi copia del estándar y comprobaré lo que dice ...
Kim Gräsman

44
Utilizo las macros de aquí para probar varias expresiones: stackoverflow.com/a/6114546/96963 Podría ser que diagnostiquen erróneamente las cosas.
Kim Gräsman

1
Agregar el xvalue no es para la semántica de movimiento. Solo con lvalue y rvalue, la semántica de movimiento, la referencia perfecta hacia adelante y rvalue aún funcionan bien. Creo que el xvalue es solo para el operador decltype: si la expresión del operando es xvalue, el decltype da el tipo de referencia rvalue.
ligando

1
@MuhamedCicak "Cada expresión es un valor o un valor": es cierto; y el estándar (o el documento n3055) no dice que sea falso. La razón por la que se tachó esta oración es que estaba viendo cambios entre dos versiones del documento. La oración fue eliminada porque se volvió superflua después de que se agregó una explicación más precisa.
max

337

¿Cuáles son estas nuevas categorías de expresiones?

El FCD (n3092) tiene una excelente descripción:

- Un valor l (llamado históricamente, porque los valores l 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 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 lvalue es un lvalue. —Ejemplo]

- Un valor x (un valor de "eXpiring") también se refiere a un objeto, generalmente cerca del final de su vida útil (por ejemplo, para que sus recursos se puedan mover). 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 de valor r es un valor x. —Ejemplo]

- Un valor gl (valor "generalizado") es un valor o un valor x.

- Un valor r (llamado históricamente, porque los valores pueden aparecer en el lado derecho de las expresiones de una asignación) es un valor x, un objeto temporal (12.2) o subobjeto, o un valor que no está asociado con un objeto.

- Un valor pr (valor "puro") es un valor que no es un valor x. [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 verdadero también es un prvalue. —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 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 izquierdo sea un valor l y que el operando derecho sea un valor prva y produzca un valor l 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

Sin embargo, le sugiero que lea toda la sección 3.10 Lvalues ​​and rvalues .

¿Cómo se relacionan estas nuevas categorías con las categorías rvalue y lvalue existentes?

De nuevo:

Taxonomia

¿Son las categorías rvalue y lvalue en C ++ 0x las mismas que en C ++ 03?

La semántica de los valores ha evolucionado particularmente con la introducción de la semántica de movimiento.

¿Por qué se necesitan estas nuevas categorías?

Para que la construcción / asignación de movimiento se pueda definir y apoyar.


54
Me gusta el diagrama aquí. Creo que podría ser útil comenzar la respuesta con "Cada expresión pertenece exactamente a una de las clasificaciones fundamentales en esta taxonomía: lvalue, xvalue o prvalue". Entonces es fácil usar el diagrama para mostrar que esas tres clases fundamentales se combinan para hacer glvalue y rvalue.
Aaron McDaid

2
"es glvalue" es equivalente a "no es prvalue" y "is rvalue" es equivalente a "is no lvalue".
Vladimir Reshetnikov

2
Este me ayudó más: bajamircea.github.io/assets/2016-04-07-move-forward/… (diagrama de Venn de categorías de valores)
John P

1
@AaronMcDaid Hola, pregunta rápida si usted / alguien puede responder ... ¿Por qué no nombrar glvalueas lvaluey lvalueas plvalue, para ser coherente?
Vijay Chavda

184

Comenzaré con tu última pregunta:

¿Por qué se necesitan estas nuevas categorías?

El estándar 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 sobrecarga. Otras reglas hacen una distinción entre glvalue y prvalue. Por ejemplo, puede tener un valor de gl con un tipo incompleto o abstracto, pero no hay ningún valor con un tipo de resumen o incompleto. Antes de tener esta terminología, las reglas que realmente necesitaban distinguir entre glvalue / prvalue se referían a lvalue / rvalue y estaban equivocadas involuntariamente o contenían muchas explicaciones y excepciones a la regla a la "... a menos que el rvalue se deba a un nombre sin nombre rvalue reference ... ". Por lo tanto, parece una buena idea dar a los conceptos de valores y valores 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 x y valores, y nos referimos a valores y valores x como valores. Los valores X son un nuevo tipo de categoría de valor para referencias de valor 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 tampoco olvide que las referencias rvalue nombradas son valores:

void foo(int&& t) {
  // t is initialized with an rvalue expression
  // but is actually an lvalue expression itself
}

165

¿Por qué se necesitan estas nuevas categorías? ¿Están los dioses del WG21 tratando de confundirnos como simples mortales?

No creo que las otras respuestas (bueno, aunque muchas de ellas lo sean) realmente capturen la respuesta a esta pregunta en particular. Sí, estas categorías y otras 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:

Solo te moverás cuando sea indudablemente seguro hacerlo.

Es por eso que existen estas categorías: para poder hablar sobre valores donde es seguro moverse de ellos, y para hablar sobre valores donde no es seguro.

En la primera versión de las referencias de valor r, el movimiento sucedió fácilmente. Demasiado fácil Es bastante fácil que hubiera mucho potencial para mover cosas implícitamente cuando el usuario realmente no quería hacerlo.

Estas son las circunstancias bajo las cuales es seguro mover algo:

  1. Cuando es temporal o subobjeto del mismo. (prvalue)
  2. Cuando el usuario tiene 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 de valor de r al constructor y, por lo tanto, se une al constructor que toma una referencia de valor de r. Eso es obvio.

Solo hay un problema con esto; No pediste moverlo. Oh, podrías decir que &&debería haber sido una pista, pero eso no cambia el hecho de que rompió la regla. valno es temporal porque los temporales no tienen nombres. Es posible que haya extendido la vida útil de lo temporal, pero eso significa que no lo es temporal ; Es como cualquier otra variable de pila.

Si no es temporal y no solicitó moverlo, entonces mover es incorrecto.

La solución obvia es hacer val un valor l. Esto significa que no puedes moverte de él. Está bien; se llama así que es un valor.

Una vez que haces eso, ya no puedes decir que eso 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 rvalue nombradas son lvalues; esa fue nuestra solución anterior. Entonces, ¿qué llamamos referencias de valor sin nombre (el valor de retorno de Funcarriba)?

No es un valor, porque no puedes moverte de un valor. Y necesitamos poder movernos devolviendo a &&; ¿De qué otra forma podrías decir explícitamente que muevas algo? Eso es lo que std::movevuelve, después de todo. No es un valor r (estilo antiguo), porque puede estar en el lado izquierdo de una ecuación (las cosas son en realidad un poco más complicadas, vea 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 es implícitamente movible desde. Lo llamamos un xvalue.

Tenga en cuenta que los valores x son los que nos hacen ganar las otras dos categorías de valores:

  • Un prvalue es realmente el nuevo nombre para el tipo de rvalue anterior, es decir, son los valores que no son xvalues.

  • Los valores son la unión de valores x y valores en un grupo, porque comparten muchas propiedades en común.

Entonces, en realidad, todo se reduce a los valores x y la necesidad de restringir el movimiento a lugares específicos y solo a ellos. Esos lugares están definidos por la categoría rvalue; los valores son los movimientos implícitos, y los valores x son los movimientos explícitos ( std::movedevuelve un valor x).


11
@Thomas: es un ejemplo; no importa cómo crea el valor de retorno. Lo que importa es que devuelve a &&.
Nicol Bolas

1
Nota: los valores pueden estar en el lado izquierdo de una ecuación, también, como en X foo(); foo() = X;... Por esta razón fundamental, no puedo seguir la excelente respuesta anterior hasta el final, porque realmente solo haces la distinción entre el nuevo xvalue, y el antiguo estilo prvalue, basado en el hecho de que puede estar en lhs.
Dan Nissenbaum

1
Xser una clase; X foo();ser una declaración de función y foo() = X();ser una línea de código. (Dejé el segundo conjunto de paréntesis en foo() = X();mi comentario anterior). Para una pregunta que acabo de publicar con este uso resaltado, consulte stackoverflow.com/questions/15482508/…
Dan Nissenbaum

1
@DanNissenbaum "xvalue no puede estar en el lado izquierdo de la expresión de asignación", ¿por qué no? Ver ideone.com/wyrxiT
Mikhail

1
Respuesta esclarecedora. Esta es sin duda la mejor respuesta aquí. Me dio la razón de introducir las nuevas categorías de valor y lo que sucedió antes.
Nikos

136

En mi humilde opinión, la mejor explicación sobre su significado nos dio Stroustrup + tener en cuenta ejemplos de Dániel Sándor y Mohan :

Stroustrup:

Ahora estaba seriamente preocupado. Claramente nos dirigíamos a un callejón sin salida o un desastre o ambos. Pasé la hora del almuerzo haciendo un análisis para ver cuáles de las propiedades (de los valores) eran independientes. Solo había dos propiedades independientes:

  • has identity - es decir y 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 un estado indeterminado pero válido

Esto me llevó a la conclusión de que hay exactamente tres tipos de valores (usando el truco de notación regex de usar una letra mayúscula para indicar un negativo: tenía prisa):

  • iM: tiene identidad y no se puede mover de
  • im: tiene identidad y se puede mover de (por ejemplo, el resultado de convertir un valor l a una referencia 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 C++(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 identidad
  • m: se puede mover de

Esto me llevó a poner este diagrama en la pizarra: ingrese la descripción de la imagen aquí

Nombrar

Observé que solo teníamos libertad limitada para nombrar: los dos puntos a la izquierda (etiquetados iMy i) son lo que han llamado lvalueslas personas con más o menos formalidad y los dos puntos a la derecha (etiquetados my Im) son lo que las personas con más o menos formalidad han llamado rvalues. Esto debe reflejarse en nuestros nombres. Es decir, la "pata" izquierda del Wdebería tener nombres relacionados lvaluey la "pata" derecha del Wdebería tener nombres relacionados rvalue.. Observo que toda esta discusión / problema surge de la introducción de referencias de valor y semántica de movimiento. Estas nociones simplemente no existen en el mundo de Strachey que consiste en just rvaluesy lvalues. Alguien observó que las ideas que

  • Cada valueuno es un lvalueo unrvalue
  • Un lvalueno es un rvaluey un rvalueno es unlvalue

están profundamente arraigados en nuestra conciencia, propiedades muy útiles, y rastros de esta dicotomía se pueden encontrar en todo el borrador del estándar. Todos estuvimos de acuerdo en que debemos preservar esas propiedades (y hacerlas precisas). Esto restringió aún más nuestras opciones de nombres. Observé que la redacción de la biblioteca estándar suele rvaluesignificar m(la generalización), de modo que para preservar la expectativa y el texto de la biblioteca estándar se Wdebe nombrar el punto inferior derecho delrvalue.

Esto condujo a una discusión centrada en los nombres. Primero, teníamos que decidir sobre lvalue.¿Debería lvaluesignificar iMo la generalización i? Dirigidos por Doug Gregor, enumeramos los lugares en la redacción del lenguaje central donde la palabra lvalueestaba calificada para significar uno u otro. Se hizo una lista y en la mayoría de los casos y en el texto más complicado / frágil lvaluesignifica actualmente iM. Este es el significado clásico de lvalue porque "en los viejos tiempos" no se movía nada; moveEs una noción novedosa en C++0x. Además, nombrar el punto de topleft del W lvaluenos da la propiedad de que cada valor es an lvalueo an rvalue, pero no ambos.

Entonces, el punto superior izquierdo de la Wes lvaluey el punto inferior derecho es rvalue.¿Qué hace que los puntos inferior izquierdo y superior derecho? El punto inferior izquierdo es una generalización del valor clásico de l, que permite el movimiento. Por lo tanto, es un generalized lvalue.nombre. glvalue.Lo podemos discutir sobre la abreviatura, pero (creo) no con la lógica. Asumimos que en uso serio de generalized lvalue alguna manera se abreviaría de todos modos, por lo que sería mejor hacerlo de inmediato (o correr el riesgo de confusión). El punto superior derecho de la W es menos general que el inferior derecho (ahora, como siempre, llamado rvalue). Ese punto representa la noción pura original de un objeto desde el que puede moverse porque no se puede volver a hacer referencia a él (excepto por un destructor). Me gustó la frase specialized rvalueen contraste con generalized lvalueperopure rvalueabreviado para prvalueganar (y probablemente con razón). Por lo tanto, la pierna izquierda de la W es lvaluey glvalue, y la pierna derecha es prvaluey rvalue.Por cierto, cada valor es un glvalue o una prvalue, pero no ambos.

Esto deja a la parte central superior del W: im; es decir, valores que tienen identidad y se pueden mover. 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 restricción real en los nombres para guiarnos, por lo que elegimos 'x' para el centro, lo desconocido, lo extraño, solo el xpert o incluso la calificación x.

Steve mostrando el producto final


14
sí, es mejor leer las propuestas y discusiones originales del comité de C ++, que el estándar, si quieres entender lo que significan: D
Ivan Kush

8
Los literales no tienen identidad y no se pueden mover de ellos; Sin embargo, son útiles.
DrPizza

Solo quiero aclarar una cosa. int && f () {return 1; } y MyClass && g () {return MyClass (); } return xvalue, ¿verdad? Entonces, ¿dónde puedo encontrar la identidad de las expresiones f (); y G();"? Tienen identidad, porque hay otra expresión en la declaración de devolución, que se refiere al mismo objeto al que se refieren: ¿lo entiendo bien?
Dániel Sándor

66
@DrPizza Según el estándar: los literales de cadena son lvalues, todos los demás literales son prvalues. Estrictamente hablando, podría argumentar que los literales que no son cadenas deben ser inamovibles, pero no es así como se escribe el estándar.
Brian Vandenberg

59

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
  • Categorías de valor de expresión xvalue, glvalue, prvalue
  • mover semántica

Si quisiéramos comprender los conceptos de las nuevas categorías de valores de expresión, debemos ser conscientes de que existen referencias de valor y valor. Es mejor saber que los valores se pueden pasar a referencias de valor no constantes.

int& r_i=7; // compile error
int&& rr_i=7; // OK

Podemos obtener cierta intuición de los conceptos de categorías de valores si citamos la subsección titulada Lvalues ​​and rvalues ​​del borrador de trabajo N3337 (el borrador más similar al estándar ISOC ++ 11 publicado).

3.10 Valores y valores [basic.lval]

1 Las expresiones se clasifican de acuerdo con la taxonomía en la Figura 1.

  • Un valor l (llamado históricamente porque los valores l 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 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 lvalue es un lvalue. —Ejemplo]
  • Un valor x (un valor "eXpiring") también se refiere a un objeto, generalmente cerca del final de su vida útil (por ejemplo, para que sus recursos se puedan mover). 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 de valor r es un valor x. —Ejemplo]
  • Un valor gl (valor "generalizado") es un valor l o un valor x.
  • Un valor r (llamado históricamente porque los valores pueden aparecer en el lado derecho de una expresión de asignación) es un valor x, un
    objeto temporal (12.2) o subobjeto, o un valor que no está
    asociado con un objeto.
  • Un valor prva (valor "puro") es un valor r que no es un valor x. [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
    verdadero también es un prvalue. —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 categoría de valor.

Pero no estoy muy seguro de que esta subsección sea suficiente para comprender los conceptos claramente, porque "por lo general" no es realmente general, "cerca del final de su vida útil" no es realmente concreto, "involucrar 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 una serpiente mordiéndose la cola.

CATEGORÍAS DE VALOR PRIMARIO

Cada expresión pertenece exactamente a una categoría de valor primario. Estas categorías de valor son lvalue, xvalue y prvalue.

lvalues

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; // The expression "www" in this row is an lvalue expression, because string literals are arrays and every array has an address.  

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

xvalores

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 de valor 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ático de tipo sin 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 xy el segundo operando es un puntero al miembro de datos.

Tenga en cuenta que el efecto de las reglas anteriores es que las referencias de valor r con nombre a los objetos se tratan como valores y las referencias de valor r sin nombre a los objetos se tratan como valores x; Las referencias de rvalue a las funciones se tratan como valores ya sean 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;
}

prvalues

La expresión E pertenece a la categoría prvalue si y solo si E no pertenece ni al lvalue ni a la categorí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 valor mixto. Estas categorías de valor son rvalue y glvalue.

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.

valores

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 publicado una regla práctica muy útil para distinguir los valores de los valores.

  • 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 de l (por ejemplo, T & o const T &, etc.), esa expresión es un valor de l.
  • De lo contrario, la expresión es un valor r. Conceptualmente (y típicamente también de hecho), los valores corresponden a objetos temporales, como los devueltos por funciones o creados a través de conversiones de tipo implícitas. La mayoría de los valores literales (p. Ej., 10 y 5.3) también son valores.

3
Todos los ejemplos de valores y todos los ejemplos de valores x también son ejemplos de valores. Gracias por editar!
Dániel Sándor

1
Tienes razón. Las tres categorías de valores primarios son suficientes. Rvalue tampoco es necesario. Creo que rvalue y glvalue están en el estándar por conveniencia.
Dániel Sándor

1
Tuve dificultades para entender que struct As{void f(){this;}}la thisvariable es un valor. Pensé que thisdebería ser un valor. Hasta que el estándar 9.3.2 diga: En el cuerpo de una función miembro no estática (9.3), la palabra clave es una expresión prvalue.
r0ng

3
@ r0ng thises un prvalue pero *thises un
lvalue

1
"www" No siempre tiene la misma dirección. Es un valor porque es una matriz .
wally

35

Las categorías de C ++ 03 están demasiado restringidas para capturar la introducción de referencias rvalue correctamente en los atributos de expresión.

Con la introducción de ellos, se dijo que una referencia de valor de r sin nombre se evalúa como un valor de r, de modo que la resolución de sobrecarga preferiría enlaces de referencia de valor de r, lo que lo haría seleccionar constructores de movimiento sobre constructores de copia. Pero se descubrió que esto causa problemas por todas partes, por ejemplo con Tipos dinámicos y con calificaciones.

Para mostrar esto, considere

int const&& f();

int main() {
  int &&i = f(); // disgusting!
}

En los borradores anteriores al valor x, esto estaba permitido, porque en C ++ 03, los valores de los tipos que no son de clase nunca tienen calificación cv. Pero se pretende que se constaplique en el caso rvalue-reference, porque aquí lo hacemos referencia a objetos (= memoria!), Y dejando caer const de rvalues no clase es principalmente por la razón de 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 evalúan a un valor. Eso no es cierto con referencias de valor sin nombre, pero pueden mostrar un comportamiento polimórfico. Entonces para resolverlo,

  • las referencias de valor sin nombre se convierten en valores x . Pueden ser calificados y potencialmente tener su tipo dinámico diferente. Ellos, como se pretendía, prefieren referencias de valor durante la sobrecarga, y no se unen a referencias de valor no constantes.

  • Lo que antes era un valor r (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 x durante la sobrecarga.

  • Lo que antes era un lvalue sigue siendo un lvalue.

Y se realizan dos agrupaciones para capturar aquellas que pueden calificarse y pueden tener diferentes tipos dinámicos ( valores ) y aquellas en las que la sobrecarga prefiere el enlace de referencia de valores ( valores ).


1
La respuesta es obviamente razonable. ¡xvalue es solo rvalue que puede ser calificado por cv y tipado dinámico!
ligand

26

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

Categorías primarias

Las categorías de valores primarios corresponden a dos propiedades de expresiones:

  • tiene identidad : es posible determinar si la expresión se refiere a la misma entidad que otra expresión, como comparando las 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 sobrecarga de función que implemente la semántica de movimiento puede unirse a la expresión.

Expresiones que:

  • tener identidad y no se puede mover de ellos se llaman expresiones de valor ;
  • tener identidad y se puede mover de ellos se llaman expresiones xvalue ;
  • no tienen identidad y se pueden mover de ellas se denominan expresiones prvalue ;
  • no tienen identidad y no se pueden mover de no se utilizan.

lvalue

Una expresión lvalue ("valor izquierdo") es una expresión que tiene identidad y desde la que 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 .

xvalue

Una expresión xvalue ("valor de expiración") es una expresión que tiene identidad y desde la que se puede mover .

glvalue

Una expresión glvalue ("lvalue generalizado") es una expresión que es lvalue o xvalue. Tiene identidad . Puede o no ser movido de.

rvalue (desde C ++ 11)

Una expresión rvalue ("valor correcto") es una expresión que es un prvalue o un xvalue. Se puede mover de . Puede o no tener identidad.


1
En algunos libros, los valores x muestran que su x proviene de "experto" o "excepcional"
noɥʇʎԀʎzɥʇʎԀʎ

Y lo más importante, su lista de ejemplos completa.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

19

¿Cómo se relacionan estas nuevas categorías con las categorías rvalue y lvalue existentes?

Un valor C ++ 03 sigue siendo un valor C ++ 11, mientras que un valor C ++ 03 se denomina prvalue en C ++ 11.


14

Una adición a las excelentes respuestas anteriores, en un punto que me confundió incluso después de haber leído Stroustrup y pensé que entendía la distinción rvalue / lvalue. Cuando veas

int&& a = 3,

Es muy tentador leer el int&&tipo y concluir que aes un valor. No es:

int&& a = 3;
int&& c = a; //error: cannot bind 'int' lvalue to 'int&&'
int& b = a; //compiles

atiene un nombre y es ipso facto un valor. No pienses en el &&como parte del tipo de a; es solo algo que te dice quéa está permitido unirse.

Esto es particularmente importante para los T&&argumentos de tipo en constructores. Si tú escribes

Foo::Foo(T&& _t) : t{_t} {}

va a copiar _ten t. Necesitas

Foo::Foo(T&& _t) : t{std::move(_t)} {}si quieres moverte ¡Ojalá mi compilador me advirtiera cuando dejé el move!


1
Creo que esta respuesta podría aclararse. "A qué ase le permite enlazar": Claro, pero en la línea 2 y 3 sus variables son c y b, y no es a lo que se une, y el tipo de aes irrelevante aquí, ¿no? Las líneas serían las mismas si ase declarara int a. La principal diferencia real aquí es que en la línea 1 a no tiene que ser constunir a 3.
Felix Dombek

12

Como las respuestas anteriores cubrieron exhaustivamente la teoría detrás de las categorías de valores, hay otra cosa que me gustaría agregar: en realidad puedes jugar con ella y probarla.

Para algunos experimentos prácticos con las categorías de valores, puede utilizar el especificador decltype . Su comportamiento distingue explícitamente entre las tres categorías de valores primarios (xvalue, lvalue y prvalue).

Usar el preprocesador nos ahorra algo de tipeo ...

Categorías primarias:

#define IS_XVALUE(X) std::is_rvalue_reference<decltype((X))>::value
#define IS_LVALUE(X) std::is_lvalue_reference<decltype((X))>::value
#define IS_PRVALUE(X) !std::is_reference<decltype((X))>::value

Categorías mixtas:

#define IS_GLVALUE(X) (IS_LVALUE(X) || IS_XVALUE(X))
#define IS_RVALUE(X) (IS_PRVALUE(X) || IS_XVALUE(X))

Ahora podemos reproducir (casi) todos los ejemplos de cppreference en la categoría de valor .

Estos son algunos ejemplos con C ++ 17 (para terse static_assert):

void doesNothing(){}
struct S
{
    int x{0};
};
int x = 1;
int y = 2;
S s;

static_assert(IS_LVALUE(x));
static_assert(IS_LVALUE(x+=y));
static_assert(IS_LVALUE("Hello world!"));
static_assert(IS_LVALUE(++x));

static_assert(IS_PRVALUE(1));
static_assert(IS_PRVALUE(x++));
static_assert(IS_PRVALUE(static_cast<double>(x)));
static_assert(IS_PRVALUE(std::string{}));
static_assert(IS_PRVALUE(throw std::exception()));
static_assert(IS_PRVALUE(doesNothing()));

static_assert(IS_XVALUE(std::move(s)));
// The next one doesn't work in gcc 8.2 but in gcc 9.1. Clang 7.0.0 and msvc 19.16 are doing fine.
static_assert(IS_XVALUE(S().x)); 

Las categorías mixtas son un poco aburridas una vez que descubres la categoría principal.

Para obtener más ejemplos (y experimentación), consulte el siguiente enlace en el explorador del compilador . Sin embargo, no te molestes en leer la asamblea. Agregué muchos compiladores solo para asegurarme de que funciona en todos los compiladores comunes.


Creo #define IS_GLVALUE(X) IS_LVALUE(X) || IS_XVALUE(X)que en realidad debería verse #define IS_GLVALUE(X) (IS_LVALUE(X) || IS_XVALUE(X))lo que sucede si ustedes &&dos IS_GLVALUE.
Gabriel Devillers
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.