c++ c++11 implicit-conversion lvalue-to-rvalue

c++ - lvalue para validar la conversión implícita



c++11 implicit-conversion (2)

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 siempre pueden actuar como valores r, pero además pueden aparecer en el lado izquierdo de an = , y a la derecha de an & ".

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 hace parecer que podríamos haberlo descifrado 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.


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 el contexto donde se espera rvalue (por ejemplo, un número).

Lvalue para valorizar la conversión
Un glvalue de cualquier tipo no funcional, no de tipo array T 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-qualifiers. 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.


Creo que la conversión lvalue-r-valor es más que solo usar un valor l donde se requiere un valor r . Puede crear una copia de una clase, y siempre produce un valor , no 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órficos ; la implementación genera información asociada con cada objeto que permite determinar el tipo de 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, las categorías de valores clasifican expresiones, no valores. lvalue-expressions incluso no se pueden considerar valores.

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

Aquí están las partes sobre los objetos:

  • Un lvalue ([...]) designa una función o un objeto. [...]
  • Un xvalor (un valor "eXpiring") también se refiere a un objeto [...]
  • Un glvalue ( lvalue "generalizado") es un lvalue o un xvalue.
  • Un valor r ([...]) es un valor x, 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 hace referencia a objetos, los valores verdaderos siempre deben aparecer como prvalue-expressions.

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 T no funcional, no de matriz 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 está preocupado por los efectos de la conversión (que no sea el 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 temporal de tipo T desde el glvalue y el resultado de la conversión es un valor prve para el temporal. De lo contrario, si glvalue tiene (posiblemente cv-qualified) escriba std::nullptr_t , 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 expresión x = y no aplica la conversión lvalue-a-rvalue a y (eso crearía una my_class temporal, por cierto). La razón es que x = y se interpreta como x.operator=(y) , que toma y por defecto por referencia , no 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-a-rvalue a xm .

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 lvalue-r-value simplemente leerá 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 valor prvalue, no un objeto (temporal).

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, una expresión-id puede ser un identificador , que denota una entidad . Un objeto es una entidad, por lo que hay expresiones que producen objetos:

int x; x = 5;

El lado izquierdo de la expresión de asignación x = 5 también necesita ser una expresión. x aquí hay una expresión id , porque x es un identificador. El resultado de esta expresión-id 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 habituales 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 nada más que conversiones. Por ejemplo, [expr.dynamic.cast] / 2 "Si T es un tipo de puntero, v [el operando] será un valor pr de un puntero para completar el tipo de clase".

Las conversiones aritméticas habituales requeridas 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 la asignación simple ( = ), el valor de la expresión reemplaza al del objeto referido por el operando de la izquierda.

Entonces, aunque no requiere explícitamente una expresión de prvalue en el lado derecho, sí requiere un valor . No estoy seguro de si esto requiere estrictamente la conversión lvalue-r-value. Existe un argumento de que acceder al valor de una variable no inicializada siempre debe invocar un comportamiento no definido (también vea CWG 616 ), sin importar 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 una discusión sobre si la conversión lvalue-to-rvalue es necesaria 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 generalmente no se aplica 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 clase 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).