Pregunta lvalue para validar la conversión implícita


Veo el término "conversión de valor a validación" utilizado en muchos lugares a través del estándar de C ++. Este tipo de conversión a menudo se hace implícitamente, hasta donde yo sé.

Una característica inesperada (para mí) del fraseo del estándar es que deciden tratar lvalue-to-rvalue como una conversión. ¿Qué pasaría si hubieran dicho que un glvalue siempre es aceptable en lugar de un prvalue? ¿Esa frase en realidad tendría un significado diferente? Por ejemplo, leemos que lvalues ​​y xvalues ​​son ejemplos de glvalues. No leemos que lvalues ​​y xvalues ​​sean convertibles a glvalues. ¿Hay una diferencia en el significado?

Antes de mi primer encuentro con esta terminología, solía modelar lvalues ​​y valores mentalmente más o menos de la siguiente manera: "lvalues ​​son siempre capaz de actuar como valores r, pero además puede aparecer en el lado izquierdo de un =, y a la derecha de un &".

Esto, para mí, es el comportamiento intuitivo de que si tengo un nombre de variable, entonces puedo poner ese nombre en todas partes donde habría puesto un literal. Este modelo parece consistente con la terminología de conversiones implícitas de valor a validación utilizada en el estándar, siempre que se garantice que esta conversión implícita ocurrirá.

Pero, debido a que usan esta terminología, comencé a preguntarme si la conversión implícita lvalue-to-rvalue puede no ocurrir en algunos casos. Es decir, tal vez mi modelo mental está mal aquí. Aquí hay una parte relevante del estándar: (gracias a los comentaristas).

Siempre que aparezca un glvalue en un contexto donde se espera un prvalue, el glvalue se convierte en un prvalue; ver 4.1, 4.2 y 4.3. [Nota: un intento de vincular una referencia rvalue a un valor l no es ese contexto; ver 8.5.3.-nota final]

Entiendo que lo que describen en la nota es el siguiente:

int x = 1;
int && y = x; //in this declaration context, x won't bind to y.
// but the literal 1 would have bound, so this is one context where the implicit 
// lvalue to rvalue conversion did not happen.  
// The expression on right is an lvalue. if it had been a prvalue, it would have bound.
// Therefore, the lvalue to prvalue conversion did not happen (which is good). 

Entonces, mi pregunta es (son):

1) ¿Podría alguien aclarar los contextos donde esta conversión puede ocurrir implícitamente? Específicamente, aparte del contexto de vinculación a una referencia de valor razonable, ¿hay alguna otra en la que las conversiones valor-valor-valor no ocurran implícitamente?

2) Además, el paréntesis [Note:...] en la cláusula parece que podríamos haberlo deducido de la oración anterior. ¿Qué parte del estándar sería eso?

3) ¿Eso quiere decir que el enlace rvalue-reference no es un contexto donde esperamos una expresión prvalue (a la derecha)?

4) Al igual que otras conversiones, ¿la conversión glvalue-a-prvalue implica trabajo en tiempo de ejecución que me permita observarlo?

Mi objetivo aquí no es preguntar si es deseable permitir tal conversión. Estoy tratando de aprender a explicarme el comportamiento de este código usando el estándar como punto de partida.

Una buena respuesta iría a través de la cita que coloqué arriba y explicará (basado en el análisis del texto) si la nota que contiene también está implícita en su texto. Luego, tal vez, agregue otras citas que me permitan conocer otros contextos en los que esta conversión puede no suceder implícitamente, o explicar que ya no existen tales contextos. Quizás una discusión general de por qué glvalue a prvalue se considera una conversión.


32
2017-12-31 01:52


origen


Respuestas:


Creo que la conversión de lvalue a rvalue es más que solo use un lvalue donde se requiere un valor r. Puede crear una copia de una clase, y siempre produce un valorno un objeto

Estoy usando n3485 para "C ++ 11" y n1256 para "C99".


Objetos y valores

La descripción más concisa es en C99 / 3.14:

objeto

región de almacenamiento de datos en el entorno de ejecución, cuyos contenidos pueden representar   valores

También hay un poco en C ++ 11 / [intro.object] / 1

Algunos objetos son polimórfico; la implementación genera información asociada con   cada objeto tal que hace posible determinar el tipo de ese objeto durante la ejecución del programa. Para otros objetos, la interpretación de los valores que se encuentran en ella está determinada por el tipo de expresiones utilizadas para acceder a ellos.

Entonces un objeto contiene un valor (puede contener).


Categorías de valor

A pesar de su nombre, categorías de valor clasificar expresiones, no valores lvalue-expressions incluso no poder ser considerado valores.

La taxonomía / categorización completa se puede encontrar en [basic.lval]; aquí hay una discusión de StackOverflow.

Aquí están las partes sobre los objetos:

  • Un lvalue ([...]) designa una función o un objeto. [...]
  • Un xvalue (un valor "eXpiring") también se refiere a un objeto [...]
  • UN glvalue (Lvalue "generalizado") es un lvalue o un valor x.
  • Un valor ([...]) es un xvalor, un objeto temporal o subobjeto del mismo, o un valor que no está asociado con un objeto.
  • Un prvalue (valor r "puro") es un valor r que no es un valor x. [...]

Tenga en cuenta la frase "un valor que no está asociado con un objeto". También tenga en cuenta que como xvalue-expressions se refieren a objetos, true valores siempre debe ocurrir como expresiones prvalue.


La conversión lvalue a rvalue

Como lo indica la nota al pie 53, ahora debería denominarse "conversión de glvalue a prvalue". Primero, aquí está la cita:

1 Un glvalue de un tipo no funcional, no de matriz T se puede convertir a un prvalue Si T es un tipo incompleto, un programa que necesita esta conversión está mal formado. Si el objeto al que se refiere glvalue no es un objeto de tipo T y no es un objeto de un tipo derivado de T, o si el objeto no está inicializado, un programa   que necesita esta conversión tiene un comportamiento indefinido. Si T es un tipo no de clase, el tipo de prvalue es la versión cv no calificada de T. De lo contrario, el tipo de prvalue es T.

Este primer párrafo especifica los requisitos y el tipo resultante de la conversión. Todavía no se preocupa por el efectos de la conversión (que no sea Comportamiento Indefinido).

2 Cuando se produce una conversión lvalue-r-value en un operando no evaluado o una subexpresión del mismo, no se accede al valor contenido en el objeto al que se hace referencia. De lo contrario, si el glvalue tiene un tipo de clase, la conversión de conversión inicializa un tipo temporal de tipo Tdesde el glvalue y el resultado de la conversión es un prvalue para el temporal. De lo contrario, si el glvalue tiene (posiblemente cv-calificado) tipo std::nullptr_t, el   El resultado de prvalue es una constante de puntero nulo. De lo contrario, el valor contenido en el objeto indicado por el glvalue es el resultado prvalue.

Argumentaría que verá la conversión lvalue-r-value más a menudo aplicada a tipos que no son de clase. Por ejemplo,

struct my_class { int m; };

my_class x{42};
my_class y{0};

x = y;

La expresion x = y hace no aplicar la conversión lvalue-to-rvalue a y (eso crearía un temporal my_class, por cierto). El motivo es que x = y se interpreta como x.operator=(y), el cual toma y por defecto por referenciano por valor (para el enlace de referencia, ver a continuación; no puede enlazar un valor r, ya que sería un objeto temporal diferente de y) Sin embargo, la definición predeterminada de my_class::operator= aplica la conversión lvalue-to-rvalue a x.m.

Por lo tanto, la parte más importante para mí parece ser

De lo contrario, el valor contenido en el objeto indicado por el glvalue es el resultado prvalue.

Por lo general, una conversión de valor a rvalue simplemente lee el valor de un objeto. No es solo una conversión no operativa entre categorías de valor (expresión); incluso puede crear un temporal llamando a un constructor de copia. Y la conversión lvalue-r-value siempre devuelve un prvalue valor, no un (temporal) objeto.

Tenga en cuenta que la conversión lvalue-to-rvalue no es la única conversión que convierte un valor l a prvalue: también existe la conversión de matriz a puntero y la conversión de función a puntero.


valores y expresiones

La mayoría de las expresiones no producen objetos[[cita requerida]]. Sin embargo, un id-expresión puede ser un identificador, que denota un entidad. Un objeto es una entidad, por lo que hay expresiones que producen objetos:

int x;
x = 5;

El lado izquierdo de la asignación-expresión  x = 5 también necesita ser una expresión. x aquí hay un id-expresión, porque x es un identificador. El resultado de esto id-expresión es el objeto denotado por x.

Las expresiones aplican conversiones implícitas: [expr] / 9

Siempre que una expresión glvalue aparece como un operando de un operador que espera un prvalue para ese operando, las conversiones estándar lvalue-to-rvalue, array-to-pointer o function-to-pointer se aplican para convertir la expresión en un valor pr.

Y / 10 sobre conversiones aritméticas usuales y / 3 sobre conversiones definidas por el usuario.

Me encantaría citar ahora a un operador que "espera un valor pror adelantado para ese operando", pero no puede encontrar más que conversiones. Por ejemplo, [expr.dynamic.cast] / 2 "If T es un tipo de puntero, v [el operando] será un valor prvero de un puntero para completar el tipo de clase ".

los conversiones aritméticas usuales requerido por muchos operadores aritméticos, invocan indirectamente una conversión lvalue-a-rvalue a través de la conversión estándar utilizada. Todas las conversiones estándar, pero las tres que convierten de lvalues ​​a rvalues, esperan valores pr.

Sin embargo, la asignación simple no invoca las conversiones aritméticas habituales. Se define en [expr.ass] / 2 como:

En asignación simple (=), el valor de la expresión reemplaza al del objeto referido por el operando izquierdo.

Entonces, aunque no requiere explícitamente una expresión prvalue en el lado derecho, sí requiere una valor. No me queda claro si esto estrictamente requiere la conversión lvalue-to-rvalue. Existe un argumento de que acceder al valor de una variable no inicializada siempre debe invocar un comportamiento indefinido (ver también CWG 616), no importa si es asignando su valor a un objeto o agregando su valor a otro valor. Pero este comportamiento indefinido solo se requiere para una conversión de valor a validación (AFAIK), que luego debería ser la única forma de acceder al valor almacenado en un objeto.

Si esta visión más conceptual es válida, necesitamos la conversión lvalue-r-value para acceder al valor dentro de un objeto, entonces sería mucho más fácil entender dónde se aplica (y se debe aplicar).


Inicialización

Al igual que con la asignación simple, hay un discusión si se requiere o no la conversión lvalue-to-rvalue para inicializar otro objeto:

int x = 42; // initializer is a non-string literal -> prvalue
int y = x;  // initializer is an object / lvalue

Para tipos fundamentales, [dcl.init] / 17 último punto señala:

De lo contrario, el valor inicial del objeto que se inicializa es el valor (posiblemente convertido) de la expresión del inicializador. Las conversiones estándar se usarán, si es necesario, para convertir la expresión del inicializador a la versión cv no calificada del tipo de destino; no se consideran conversiones definidas por el usuario. Si la conversión no se puede hacer, la inicialización está mal formada.

Sin embargo, también mencionó el valor de la expresión del inicializador. De forma similar a la expresión de asignación simple, podemos tomar esto como una invocación indirecta de la conversión lvalue-a-rvalue.


Enlace de referencia

Si vemos la conversión lvalue-to-rvalue como una forma de acceder al valor de un objeto (más la creación de un operando de tipo de clase temporal), entendemos que es no aplicado generalmente para vincular a una referencia: una referencia es un valor l, siempre se refiere a un objeto. Entonces, si vinculamos valores a referencias, tendremos que crear objetos temporales que contengan esos valores. Y este es realmente el caso si la expresión de inicialización de una referencia es un valor pr (que es un valor o un objeto temporal):

int const& lr = 42; // create a temporary object, bind it to `r`
int&& rv = 42;      // same

Se prohíbe enlazar un valor pr a una referencia lvalue, pero los prvalues ​​de los tipos de clases con funciones de conversión que producen referencias lvalue pueden vincularse a las referencias lvalue del tipo convertido.

La descripción completa del enlace de referencia en [dcl.init.ref] es bastante larga y bastante fuera de tema. Creo que la esencia de esto en relación con esta pregunta es que las referencias se refieren a objetos, por lo tanto, no hay conversión glvalue-a-prvalue (object-to-value).


19
2018-01-08 15:11



En glvalues: A glvalue (lvalue "generalizado") es una expresión que es un lvalue o un xvalor. Un glvalue se puede convertir implícitamente a prvalue con conversión implícita de lvalue-a-rvalue, de matriz a puntero o de función a puntero.

Las transformaciones de Lvalue se aplican cuando el argumento lvalue (por ejemplo, referencia a un objeto) se usa en contexto donde se espera rvalue (por ejemplo, un número).

Lvalue para valorizar la conversión
Un glvalue de cualquier tipo de T no funcional, no de matriz se puede convertir implícitamente a prvalue del mismo tipo. Si T no es un tipo de clase, esta conversión también elimina cv-calificadores. A menos que se encuentre en un contexto no evaluado (en un operando de sizeof, typeid, noexcept o decltype), esta conversión efectivamente copia-construye un objeto temporal de tipo T usando el glvalue original como el argumento constructor, y ese objeto temporal se devuelve como un prvalue . Si glvalue tiene el tipo std :: nullptr_t, el prvalue resultante es la constante null puntero nullptr.


2
2018-01-08 07:44