Estreitando conversões em C ++ 0x. Sou só eu ou isso soa como uma mudança repentina?

86

C ++ 0x tornará o código a seguir e um código semelhante malformado, porque requer a chamada conversão de estreitamento de a doubleem a int.

int a[] = { 1.0 };

Estou me perguntando se esse tipo de inicialização é muito usado em código do mundo real. Quantos códigos serão quebrados por essa mudança? É muito difícil consertar isso em seu código, se ele for afetado de alguma forma?


Para referência, consulte 8.5.4 / 6 de n3225

Uma conversão de estreitamento é uma conversão implícita

  • de um tipo de ponto flutuante para um tipo inteiro, ou
  • de long double para double ou float, ou de double para float, exceto onde a fonte é uma expressão constante e o valor real após a conversão está dentro do intervalo de valores que podem ser representados (mesmo que não possa ser representado exatamente), ou
  • de um tipo de número inteiro ou tipo de enumeração sem escopo para um tipo de ponto flutuante, exceto onde a fonte é uma expressão constante e o valor real após a conversão se ajustará ao tipo de destino e produzirá o valor original quando convertido de volta ao tipo original, ou
  • de um tipo inteiro ou tipo de enumeração sem escopo para um tipo inteiro que não pode representar todos os valores do tipo original, exceto onde a fonte é uma expressão constante e o valor real após a conversão se ajustará ao tipo de destino e produzirá o valor original quando convertido de volta ao tipo original.
Johannes Schaub - litb
fonte
1
Presumindo que isso seja válido apenas para inicialização de tipos embutidos, não consigo ver como isso poderia prejudicar. Claro, isso pode quebrar algum código. Mas deve ser fácil de consertar.
Johan Kotlinski
1
@John Dibling: Não, a inicialização não é malformada quando o valor pode ser representado exatamente pelo tipo de destino. (E 0já é um de intqualquer maneira.)
aschepler
2
@Nim: Observe que isso só é malformado nos {inicializadores de chaves }e o único uso legado deles é para matrizes e estruturas POD. Além disso, se o código existente tiver conversões explícitas a que pertencem, ele não quebrará.
aschepler
4
@j_random_hacker como diz o documento de trabalho, int a = 1.0;ainda é válido.
Johannes Schaub - litb
1
@litb: Obrigado. Na verdade, acho isso compreensível, mas decepcionante - IMHO, teria sido muito melhor exigir uma sintaxe explícita para todas as conversões de estreitamento desde o início do C ++.
j_random_hacker

Respostas:

41

Corri para essa alteração significativa quando usei o GCC. O compilador imprimiu um erro para um código como este:

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

Em função void foo(const long long unsigned int&):

erro: estreitando conversão de (((long long unsigned int)i) & 4294967295ull)partir long long unsigned intpara unsigned intdentro {}

erro: estreitando conversão de (((long long unsigned int)i) >> 32)partir long long unsigned intpara unsigned intdentro {}

Felizmente, as mensagens de erro eram diretas e a correção era simples:

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

O código estava em uma biblioteca externa, com apenas duas ocorrências em um arquivo. Não acho que a alteração significativa afetará muito o código. Os novatos podem ficar confusos, no entanto.

Timothy003
fonte
9

Eu ficaria surpreso e desapontado comigo mesmo se soubesse que qualquer código C ++ que escrevi nos últimos 12 anos teve esse tipo de problema. Mas a maioria dos compiladores teria lançado avisos sobre qualquer "estreitamento" de tempo de compilação o tempo todo, a menos que esteja faltando alguma coisa.

Essas conversões também estão reduzindo?

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

Nesse caso, acho que eles podem surgir com um pouco mais de frequência do que o exemplo do tipo flutuante para o tipo integral.

Aschepler
fonte
1
Não entendo por que você diz que isso seria algo comum de se encontrar no código. Qual é a lógica entre usar -1 ou INT_MAX em vez de USHRT_MAX? USHRT_MAX não estava em escaladas no final de 2010?
8

Um exemplo prático que encontrei:

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

O literal numérico é implicitamente o doubleque causa a promoção.

Jed
fonte
1
então faça isso floatescrevendo 0.5f. ;)
sublinhado_d
2
@underscore_d Não funciona se floatfor um typedef ou parâmetro de modelo (pelo menos sem perda de precisão), mas o ponto é que o código como escrito funcionou com a semântica correta e se tornou um erro com C ++ 11. Ou seja, a definição de uma "mudança significativa".
Jed,
7

Eu não ficaria tão surpreso se alguém fosse pego por algo como:

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

(na minha implementação, os dois últimos não produzem o mesmo resultado quando convertidos de volta para int / long, portanto, estão se estreitando)

Não me lembro de ter escrito isso, no entanto. Só é útil se uma aproximação dos limites for útil para algo.

Isso também parece pelo menos vagamente plausível:

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
}

mas não é totalmente convincente, porque se eu sei que tenho exatamente dois valores, por que colocá-los em matrizes em vez de apenas float floatval1 = val1, floatval1 = val2;? Qual é a motivação, porém, por que isso deveria compilar (e funcionar, desde que a perda de precisão esteja dentro da precisão aceitável para o programa), enquanto float asfloat[] = {val1, val2};não deveria? De qualquer forma, estou inicializando dois flutuantes de dois ints, mas em um caso os dois flutuantes são membros de um agregado.

Isso parece particularmente difícil nos casos em que uma expressão não constante resulta em uma conversão de estreitamento, embora (em uma implementação particular), todos os valores do tipo de origem sejam representáveis ​​no tipo de destino e conversíveis de volta aos seus valores originais:

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

Supondo que não haja bug, presumivelmente a correção é sempre tornar a conversão explícita. A menos que você esteja fazendo algo estranho com macros, acho que um inicializador de array só aparece próximo ao tipo do array, ou pelo menos a algo que representa o tipo, que pode ser dependente de um parâmetro de template. Portanto, um elenco deve ser fácil, embora detalhado.

Steve Jessop
fonte
9
"se eu sei que tenho exatamente dois valores, por que colocá-los em matrizes" - por exemplo, porque uma API como o OpenGL exige isso.
Georg Fritzsche
5

Tente adicionar -Wno-estreitamento às suas CFLAGS, por exemplo:

CFLAGS += -std=c++0x -Wno-narrowing
Kukuh Indrayana
fonte
ou CPPFLAGS no caso de compiladores C ++ (claro que depende do seu sistema de compilação ou do seu Makefile)
Mikolasan
4

Erros de conversão restritos interagem mal com as regras de promoção de inteiros implícitos.

Eu tive um erro com o código que parecia

struct char_t {
    char a;
}

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

O que produz um erro de conversão de estreitamento (que está correto de acordo com o padrão). O motivo é que ce dimplicitamente são promovidos a inte o resultado intnão pode ser reduzido a char em uma lista de inicializadores.

OTOH

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

é claro que ainda está bem (caso contrário, o inferno iria explodir). Mas, surpreendentemente, mesmo

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

está ok e compila sem aviso se a soma de ced for menor que CHAR_MAX. Ainda acho que isso é um defeito no C ++ 11, mas as pessoas pensam o contrário - possivelmente porque não é fácil de consertar sem se livrar de qualquer conversão implícita de inteiro (que é uma relíquia do passado, quando as pessoas escreveram código como char a=b*c/de esperava que funcionasse mesmo se (b * c)> CHAR_MAX) ou reduzindo erros de conversão (que são possivelmente uma coisa boa).

Gunther Piez
fonte
Encontrei o seguinte, o que é um absurdo realmente irritante: unsigned char x; static unsigned char const m = 0x7f; ... unsigned char r = { x & m };<- estreitando a conversão dentro de {}. Mesmo? Portanto, o operador e também converte implicitamente caracteres não assinados em int? Bem, eu não me importo, o resultado ainda é garantido ser um char não assinado, argh.
Carlo Wood
" Conversão implícita inteiro promoções"?
curiousguy
2

Foi realmente uma mudança significativa, pois a experiência da vida real com esse recurso mostrou que o gcc transformou o estreitamento em um aviso de um erro em muitos casos devido a problemas da vida real com a portabilidade de bases de código C ++ 03 para C ++ 11. Veja este comentário em um relatório de bug do gcc :

O padrão exige apenas que "uma implementação conforme deve emitir pelo menos uma mensagem de diagnóstico", portanto, é permitido compilar o programa com um aviso. Como disse Andrew, -Werror = estreitamento permite que você cometa um erro, se desejar.

O G ++ 4.6 deu um erro, mas foi alterado para um aviso intencionalmente para o 4.7 porque muitas pessoas (inclusive eu) descobriram que o estreitamento das conversões era um dos problemas mais comumente encontrados ao tentar compilar grandes bases de código C ++ 03 como C ++ 11 . Código previamente bem formado, como char c [] = {i, 0}; (onde eu só estarei dentro do intervalo de char) causou erros e teve que ser alterado para char c [] = {(char) i, 0}

Shafik Yaghmour
fonte
1

Parece que o GCC-4.7 não fornece mais erros para estreitar as conversões, mas sim avisos.

kyku
fonte