Reducción de conversiones en C ++ 0x. ¿Soy solo yo, o esto suena como un cambio radical?

85

C ++ 0x hará que el siguiente código y un código similar estén mal formados, porque requiere una llamada conversión de restricción de a doublea a int.

int a[] = { 1.0 };

Me pregunto si este tipo de inicialización se usa mucho en el código del mundo real. ¿Cuántos códigos se romperán con este cambio? ¿Es mucho esfuerzo arreglar esto en su código, si su código se ve afectado?


Para referencia, consulte 8.5.4 / 6 de n3225

Una conversión de restricción es una conversión implícita

  • de un tipo de punto flotante a un tipo entero, o
  • de doble largo a doble o flotante, o de doble a flotante, excepto cuando la fuente es una expresión constante y el valor real después de la conversión está dentro del rango de valores que se pueden representar (incluso si no se puede representar exactamente), o
  • de un tipo de número entero o de enumeración sin ámbito a un tipo de punto flotante, excepto cuando la fuente es una expresión constante y el valor real después de la conversión se ajustará al tipo de destino y producirá el valor original cuando se vuelva a convertir al tipo original, o
  • de un tipo de número entero o tipo de enumeración sin ámbito a un tipo de número entero que no puede representar todos los valores del tipo original, excepto cuando la fuente es una expresión constante y el valor real después de la conversión se ajustará al tipo de destino y producirá el valor original cuando convertido de nuevo al tipo original.
Johannes Schaub - litb
fuente
1
Suponiendo que esto es válido solo para la inicialización de tipos incorporados, no puedo ver cómo dañaría esto. Seguro, esto puede romper algún código. Pero debería ser fácil de arreglar.
Johan Kotlinski
1
@John Dibling: No, la inicialización no está mal formada cuando el valor puede ser representado exactamente por el tipo de destino. (Y 0ya es un de inttodos modos.)
aschepler
2
@Nim: Tenga en cuenta que esto solo está mal formado dentro de {los inicializadores de llaves }, y el único uso heredado de esos es para matrices y estructuras POD. Además, si el código existente tiene conversiones explícitas donde pertenecen, no se romperá.
aschepler
4
@j_random_hacker como dice el documento de trabajo, int a = 1.0;sigue siendo válido.
Johannes Schaub - litb
1
@litb: Gracias. En realidad, lo encuentro comprensible pero decepcionante; en mi humilde opinión, hubiera sido mucho mejor requerir una sintaxis explícita para todas las conversiones de reducción desde el comienzo de C ++.
j_random_hacker

Respuestas:

41

Me encontré con este cambio importante cuando usé GCC. El compilador imprimió un error para un código como este:

void foo(const unsigned long long &i)
{
    unsigned int a[2] = {i & 0xFFFFFFFF, i >> 32};
}

En función void foo(const long long unsigned int&):

error: estrechamiento conversión de (((long long unsigned int)i) & 4294967295ull)desde long long unsigned intal unsigned intinterior {}

error: estrechamiento conversión de (((long long unsigned int)i) >> 32)desde long long unsigned intal unsigned intinterior {}

Afortunadamente, los mensajes de error fueron sencillos y la solución fue simple:

void foo(const unsigned long long &i)
{
    unsigned int a[2] = {static_cast<unsigned int>(i & 0xFFFFFFFF),
            static_cast<unsigned int>(i >> 32)};
}

El código estaba en una biblioteca externa, con solo dos ocurrencias en un archivo. No creo que el cambio importante afecte mucho al código. Los novatos pueden conseguir confundido, sin embargo.

Timothy003
fuente
9

Me sorprendería y decepcionaría de mí mismo saber que cualquiera de los códigos C ++ que escribí en los últimos 12 años tenía este tipo de problema. Pero la mayoría de los compiladores habrían arrojado advertencias sobre cualquier "estrechamiento" en tiempo de compilación todo el tiempo, a menos que me falte algo.

¿También reducen las conversiones?

unsigned short b[] = { -1, INT_MAX };

Si es así, creo que pueden aparecer un poco más a menudo que su ejemplo de tipo flotante a tipo integral.

aschepler
fuente
1
No entiendo por qué dice que esto sería algo común de encontrar en el código. ¿Cuál es la lógica entre usar -1 o INT_MAX en lugar de USHRT_MAX? ¿USHRT_MAX no estuvo en climits a finales de 2010?
7

No me sorprendería tanto si alguien queda atrapado por algo como:

float ra[] = {0, CHAR_MAX, SHORT_MAX, INT_MAX, LONG_MAX};

(en mi implementación, los dos últimos no producen el mismo resultado cuando se vuelven a convertir a int / long, por lo tanto, se están reduciendo)

Sin embargo, no recuerdo haber escrito esto nunca. Solo es útil si una aproximación a los límites es útil para algo.

Esto también parece al menos vagamente plausible:

void some_function(int val1, int val2) {
    float asfloat[] = {val1, val2};    // not in C++0x
    double asdouble[] = {val1, val2};  // not in C++0x
    int asint[] = {val1, val2};        // OK
    // now do something with the arrays
}

pero no es del todo convincente, porque si sé que tengo exactamente dos valores, ¿por qué ponerlos en matrices en lugar de solo float floatval1 = val1, floatval1 = val2;? Sin embargo, ¿cuál es la motivación, por qué debería compilarse (y funcionar, siempre que la pérdida de precisión esté dentro de una precisión aceptable para el programa), mientras float asfloat[] = {val1, val2};que no debería? De cualquier manera, estoy inicializando dos flotantes desde dos entradas, es solo que en un caso los dos flotantes son miembros de un agregado.

Eso parece particularmente severo en los casos en que una expresión no constante da como resultado una conversión restringida aunque (en una implementación particular), todos los valores del tipo de origen se pueden representar en el tipo de destino y se pueden convertir de nuevo a sus valores originales:

char i = something();
static_assert(CHAR_BIT == 8);
double ra[] = {i}; // how is this worse than using a constant value?

Suponiendo que no haya ningún error, presumiblemente la solución es siempre hacer explícita la conversión. A menos que esté haciendo algo extraño con las macros, creo que un inicializador de matriz solo aparece cerca del tipo de matriz, o al menos de algo que representa el tipo, que podría depender de un parámetro de plantilla. Entonces, un yeso debería ser fácil, aunque detallado.

Steve Jessop
fuente
8
"Si sé que tengo exactamente dos valores, ¿por qué ponerlos en matrices?" , por ejemplo, porque una API como OpenGL lo requiere.
Georg Fritzsche
7

Una instancia práctica que me he encontrado:

float x = 4.2; // an input argument
float a[2] = {x-0.5, x+0.5};

El literal numérico es implícitamente lo doubleque provoca la promoción.

Jed
fuente
1
así que floathazlo escribiendo 0.5f. ;)
underscore_d
1
@underscore_d No funciona si floatera un parámetro typedef o de plantilla (al menos sin pérdida de precisión), pero el punto es que el código tal como está escrito funcionó con la semántica correcta y se convirtió en un error con C ++ 11. Es decir, la definición de "cambio radical".
Jed
5

Intente agregar -Wno-narrowing a su CFLAGS, por ejemplo:

CFLAGS += -std=c++0x -Wno-narrowing
Kukuh Indrayana
fuente
o CPPFLAGS en el caso de compiladores de C ++ (por supuesto, depende de su sistema de compilación o de su Makefile)
Mikolasan
4

Los errores de conversión delimitados interactúan mal con las reglas implícitas de promoción de enteros.

Tuve un error con el código que parecía

struct char_t {
    char a;
}

void function(char c, char d) {
    char_t a = { c+d };
}

Lo que produce un error de conversión de estrechamiento (que es correcto según el estándar). La razón es que ce dimplícitamente se promueve a inty el resultado intno se puede reducir a char en una lista de inicializadores.

OTOH

void function(char c, char d) {
    char a = c+d;
}

Por supuesto, todavía está bien (de lo contrario, se desataría el infierno). Pero sorprendentemente, incluso

template<char c, char d>
void function() {
    char_t a = { c+d };
}

está bien y se compila sin una advertencia si la suma de cyd es menor que CHAR_MAX. Sigo pensando que esto es un defecto en C ++ 11, pero la gente piensa lo contrario, posiblemente porque no es fácil de arreglar sin deshacerse de la conversión de enteros implícita (que es una reliquia del pasado, cuando la gente escribía código me gusta char a=b*c/dy esperaba que funcionara incluso si (b * c)> CHAR_MAX) o reduciendo los errores de conversión (que posiblemente sean algo bueno).

Gunther Piez
fuente
Me encontré con lo siguiente que es una tontería realmente molesta: unsigned char x; static unsigned char const m = 0x7f; ... unsigned char r = { x & m };<- reducción de la conversión dentro de {}. De Verdad? Entonces, ¿el operador & también convierte implícitamente caracteres sin firmar a int? Bueno, no me importa, todavía se garantiza que el resultado será un carácter sin firmar, argh.
Carlo Wood
" Implícita conversión entera promociones"?
Curioso
2

De hecho, fue un cambio radical, ya que la experiencia de la vida real con esta función ha demostrado que gcc había convertido el estrechamiento en una advertencia de un error en muchos casos debido a los problemas de la vida real al migrar las bases de código C ++ 03 a C ++ 11. Vea este comentario en un informe de error de gcc :

La norma solo requiere que "una implementación conforme emitirá al menos un mensaje de diagnóstico", por lo que se permite compilar el programa con una advertencia. Como dijo Andrew, -Werror = estrechamiento le permite convertirlo en un error si lo desea.

G ++ 4.6 dio un error, pero se cambió intencionalmente a una advertencia para 4.7 porque muchas personas (incluido yo mismo) descubrieron que reducir las conversiones era uno de los problemas más comunes al intentar compilar grandes bases de código C ++ 03 como C ++ 11 . Código previamente bien formado como char c [] = {i, 0}; (donde solo estaré dentro del rango de char) causó errores y tuvo que cambiarse a char c [] = {(char) i, 0}

Shafik Yaghmour
fuente
1

Parece que GCC-4.7 ya no da errores para reducir las conversiones, sino advertencias.

kyku
fuente