c++ como - ¿La inicialización implica una conversión de valor a validación? Es `int x=x;` ¿UB?





verificar enteros (5)


Una secuencia de conversión implícita de una expresión e a tipo T se define como equivalente a la siguiente declaración, utilizando t como resultado de la conversión (categoría de valor de módulo, que se definirá en función de T ), 4p3 y 4p6

T t = e;

El efecto de cualquier conversión implícita es lo mismo que realizar la declaración e inicialización correspondiente y luego usar la variable temporal como resultado de la conversión.

En la cláusula 4, la conversión de una expresión a un tipo siempre produce expresiones con una propiedad específica. Por ejemplo, la conversión de 0 a int* produce un valor de puntero nulo y no solo un valor de puntero arbitrario. La categoría de valor también es una propiedad específica de una expresión y su resultado se define de la siguiente manera

El resultado es un lvalue si T es un tipo de referencia lvalue o una referencia rvalue al tipo de función (8.3.2), un valor x si T es una referencia rvalue al tipo de objeto, y un prvalue en caso contrario.

Por lo tanto, sabemos que en int t = e; , el resultado de la secuencia de conversión es un valor pr, porque int es un tipo que no es de referencia. Entonces, si proporcionamos un glvalue, obviamente necesitamos una conversión. 3.10p2 aclara que para no dejar dudas

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.

El estándar de C ++ contiene un ejemplo semi famoso de búsqueda de nombres "sorprendente" en 3.3.2, "Punto de declaración":

int x = x;

Esto inicializa x consigo mismo, que (siendo un tipo primitivo) no está inicializado y, por lo tanto, tiene un valor indeterminado (suponiendo que sea una variable automática).

¿Es este comportamiento realmente indefinido?

De acuerdo con 4.1 "conversión Lvalue-a -valor", es un comportamiento indefinido realizar una conversión lvalor-avalor en un valor no inicializado. ¿La mano derecha x sufre esta conversión? De ser así, ¿el ejemplo realmente tendría un comportamiento indefinido?




esto no es un comportamiento indefinido. Simplemente no conoce sus valores específicos, porque no hay inicialización. Si la variable es global e incorporada, el compilador la inicializará al valor correcto. Si la variable es local, entonces el compilador no la inicializa, por lo que todas las variables se inicializan para usted, no confíe en el compilador.




ACTUALIZACIÓN: Después de la discusión en los comentarios, agregué más evidencia al final de esta respuesta.

Descargo de responsabilidad : admito que esta respuesta es bastante especulativa. La formulación actual del estándar C ++ 11, por otro lado, no parece permitir una respuesta más formal.

En el contexto de esta sesión de preguntas y respuestas , se ha descubierto que el estándar C ++ 11 no especifica formalmente qué categorías de valores se esperan para cada construcción de lenguaje. A continuación, me centraré principalmente en los operadores integrados , aunque la pregunta es acerca de los inicializadores . Eventualmente, terminaré extendiendo las conclusiones que saqué para el caso de los operadores al caso de los inicializadores.

En el caso de los operadores incorporados, a pesar de la falta de una especificación formal, en la Norma se encuentran evidencias (no normativas) de que la especificación prevista es permitir que se esperen valores primos donde sea que se necesite un valor, y cuando no se especifique de lo contrario

Por ejemplo, una nota en el párrafo 3.10 / 1 dice:

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 valores de los operandos que espera. Por ejemplo, los operadores de asignación integrados esperan que el operando de la izquierda sea un valor l y que el operando de la derecha sea un valor de pr y arroje un valor de 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

La sección 5.17 sobre operadores de asignación, por otro lado, no menciona esto. Sin embargo, se menciona la posibilidad de realizar una conversión de valor l a valor r, nuevamente en una nota (Párrafo 5.17 / 1):

Por lo tanto, una llamada a función no debe intervenir entre la conversión lvalue-a -valor y el efecto secundario asociado con cualquier operador de asignación compuesta única

Por supuesto, si no se esperaba ningún valor r, esta nota no tendría sentido.

Otra evidencia se encuentra en 4/8, como lo señaló Johannes Schaub en los comentarios a Q & A vinculados:

Hay algunos contextos donde ciertas conversiones son suprimidas. Por ejemplo, la conversión lvalue-r-value no se realiza en el operando del operador unario. Se dan excepciones específicas en las descripciones de esos operadores y contextos.

Esto parece implicar que la conversión lvalue-r-valor se realiza en todos los operandos de operadores incorporados, excepto cuando se especifique lo contrario. Esto significaría, a su vez, que los valores r se esperan como operandos de los operadores incorporados a menos que se especifique lo contrario.

CONJETURA:

Aunque la inicialización no es una asignación, y por lo tanto los operadores no entran en la discusión, mi sospecha es que esta área de la especificación se ve afectada por el mismo problema descrito anteriormente.

Los rastros que respaldan esta creencia se pueden encontrar incluso en el Párrafo 8.5.2 / 5, sobre la inicialización de referencias (para las cuales no se necesita el valor de la expresión del inicializador lvalue):

Las conversiones estándar de lvalue a rvalue (4.1), de matriz a puntero (4.2) y de función a puntero (4.3) no son necesarias, y por lo tanto se suprimen, cuando se realizan tales enlaces directos a lvalues.

La palabra "habitual" parece implicar que cuando se inicializan objetos que no son de un tipo de referencia, se aplica la conversión de valor a validación.

Por lo tanto, creo que aunque los requisitos sobre la categoría de valor esperado de los inicializadores están mal especificados (si no están completamente ausentes), sobre la base de las evidencias proporcionadas, tiene sentido suponer que la especificación prevista es la siguiente:

Siempre que un constructo de lenguaje requiera un valor, se espera un valor prverente a menos que se especifique lo contrario .

Según esta suposición, se requeriría una conversión de valor l a valor en su ejemplo, y eso daría lugar a un comportamiento no definido.

EVIDENCIA ADICIONAL:

Solo para proporcionar más evidencia que respalde esta conjetura, supongamos que está mal , de modo que no se requiere ninguna conversión de valor a invalidación para la inicialización de la copia, y considere el siguiente código (gracias a jogojapan por su contribución):

int y;
int x = y; // No UB
short t;
int u = t; // UB! (Do not like this non-uniformity, but could accept it)
int z;
z = x; // No UB (x is not uninitialized)
z = y; // UB! (Assuming assignment operators expect a prvalue, see above)
       // This would be very counterintuitive, since x == y

Este comportamiento no uniforme no tiene mucho sentido para mí. Lo que tiene más sentido IMO es que siempre que se requiera un valor, se espera un prvalue.

Además, como acertadamente señala Jesse Good en su respuesta, el Párrafo clave del Estándar C ++ es 8.5 / 16:

- 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 (cláusula 4) se usarán, si es necesario , para convertir la expresión del inicializador en 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. [Nota: una expresión de tipo "cv1 T" puede inicializar un objeto de tipo "cv2 T" independientemente de los cv-calificadores cv1 y cv2.

Sin embargo, aunque Jesse se centra principalmente en el bit " si es necesario ", también me gustaría hacer hincapié en la palabra " tipo ". El párrafo anterior menciona que las conversiones estándar se usarán " si es necesario " para convertir al tipo de destino, pero no dice nada sobre las conversiones de categoría :

  1. ¿Se realizarán conversiones de categoría si es necesario?
  2. ¿Se necesitan?

Para lo que concierne a la segunda pregunta, como se discutió en la parte original de la respuesta, el Estándar C ++ 11 actualmente no especifica si las conversiones de categoría son necesarias o no, porque en ninguna parte se menciona si la inicialización de copia espera un prvalue como inicializador . Por lo tanto, es imposible dar una respuesta clara. Sin embargo, creo que proporcioné pruebas suficientes para suponer que esta es la especificación prevista , por lo que la respuesta sería "Sí".

En cuanto a la primera pregunta, me parece razonable que la respuesta sea "Sí" también. Si fuera "No", obviamente los programas correctos estarían mal formados:

int y = 0;
int x = y; // y is lvalue, prvalue expected (assuming the conjecture is correct)

Para resumir (A1 = " Respuesta a la pregunta 1 ", A2 = " Respuesta a la pregunta 2 "):

          | A2 = Yes   | A2 = No |
 ---------|------------|---------|
 A1 = Yes |     UB     |  No UB  | 
 A1 = No  | ill-formed |  No UB  |
 ---------------------------------

Si A2 es "No", A1 no importa: no hay UB, pero las situaciones extrañas del primer ejemplo (por ejemplo, z = y dando UB, pero no z = x aunque x == y ) aparecen. Si A2 es "Sí", por otro lado, A1 se vuelve crucial; sin embargo, se ha dado suficiente evidencia para demostrar que sería "Sí".

Por lo tanto, mi tesis es que A1 = "Sí" y A2 = "Sí", y deberíamos tener un comportamiento indefinido .

MÁS EVIDENCIA:

Este informe de defectos (cortesía de Jesse Good ) propone un cambio que tiene como objetivo dar Comportamiento Indefinido en este caso:

[...] Además, el párrafo 4.1 [conv.lval] dice que la aplicación de la conversión lvalue-avalor a un "objeto [que] no se inicializa" da como resultado un comportamiento indefinido; esto debe reformularse en términos de un objeto con un valor indeterminado .

En particular, la redacción propuesta para el Párrafo 4.1 dice:

Cuando se produce una conversión lvalue-r-value en un operando no evaluado o una subexpresión del mismo (cláusula 5 [expr]), no se accede al valor contenido en el objeto al que se hace referencia. En todos los demás casos, el resultado de la conversión se determina de acuerdo con las siguientes reglas:

- Si T es (posiblemente cv-qualified) std :: nullptr_t, el resultado es una constante de puntero nulo (4.10 [conv.ptr]).

- De lo contrario, si el glvalue T tiene un tipo de clase, la conversión de conversión inicializa un temporal de tipo T del glvalue y el resultado de la conversión es un valor prvero para el temporal.

- De lo contrario, si el objeto al que se refiere glvalue contiene un valor de puntero no válido (3.7.4.2 [basic.stc.dynamic.deallocation], 3.7.4.3 [basic.stc.dynamic.safety]), el comportamiento está definido por la implementación .

- De lo contrario, si T es un tipo de carácter sin firmar (posiblemente cv calificado) (3.9.1 [basic.fundamental]), y el objeto al que se refiere el glvalue contiene un valor indeterminado (5.3.4 [expr.new], 8.5 [dcl.init], 12.6.2 [class.base.init]), y ese objeto no tiene una duración de almacenamiento automática o el glvalue era el operando de unario & operador o estaba enlazado a una referencia, el resultado es un valor no especificado. [Nota: el valor puede ser diferente cada vez que se aplica la conversión lvalue-r-value al objeto. Un objeto carbon sin signo con un valor indeterminado asignado a un registro puede atrapar. -finalizar nota al pie]

- De lo contrario, si el objeto al que se refiere glvalue contiene un valor indeterminado, el comportamiento no está definido.

- De lo contrario, si el glvalue tiene (posiblemente cv-qualified) escriba std :: nullptr_t, el resultado de prvalue es una constante de puntero nulo (4.10 [conv.ptr]). De lo contrario, el valor contenido en el objeto indicado por el glvalue es el resultado prvalue.




El comportamiento no está indefinido. La variable no está inicializada y se mantiene con cualquier valor aleatorio con el que comiencen los valores no inicializados. Un ejemplo de la demanda de prueba clan'g:

int test7b(int y) {
  int x = x; // expected-note{{variable 'x' is declared here}}
  if (y)
    x = 1;
  // Warn with "may be uninitialized" here (not "is sometimes uninitialized"),
  // since the self-initialization is intended to suppress a -Wuninitialized
  // warning.
  return x; // expected-warning{{variable 'x' may be uninitialized when used here}}
}

Que puedes encontrar en las clang/test/Sema/uninit-variables.c para este caso explícitamente.




La única forma en que puedo pensar para almacenar una lambda en una clase es usar una plantilla con una función auxiliar make_ :

#include <cstdio>
#include <utility>

template<class Lambda>
class MyClass {
    Lambda _t;
public:
    MyClass(Lambda &&t) : _t(std::forward<Lambda>(t)) {
        _t();
    }
};

template<class Lambda>
MyClass<Lambda> make_myclass(Lambda &&t) {
    return { std::forward<Lambda>(t) };
}

int main() {
    make_myclass([] {
        printf("hi");
    });
}






c++ initialization undefined-behavior language-lawyer