Restringere le conversioni in C ++ 0x. Sono solo io o questo suona come un cambiamento radicale?


86

C ++ 0x renderà il codice seguente e un codice simile mal formato, perché richiede una cosiddetta conversione di restringimento di a doublein a int.

int a[] = { 1.0 };

Mi chiedo se questo tipo di inizializzazione sia utilizzato molto nel codice del mondo reale. Quanti codici verranno interrotti da questa modifica? È molto impegnativo risolvere questo problema nel codice, se il codice è interessato?


Per riferimento, vedere 8.5.4 / 6 di n3225

Una conversione restringente è una conversione implicita

  • da un tipo a virgola mobile a un tipo intero, o
  • da long double a double o float, o da double a float, tranne quando l'origine è un'espressione costante e il valore effettivo dopo la conversione è compreso nell'intervallo di valori che può essere rappresentato (anche se non può essere rappresentato esattamente), o
  • da un tipo intero o un tipo di enumerazione senza ambito a un tipo a virgola mobile, tranne quando l'origine è un'espressione costante e il valore effettivo dopo la conversione si adatterà al tipo di destinazione e produrrà il valore originale quando convertito nel tipo originale, o
  • da un tipo intero o un tipo di enumerazione senza ambito a un tipo intero che non può rappresentare tutti i valori del tipo originale, tranne dove l'origine è un'espressione costante e il valore effettivo dopo la conversione si adatterà al tipo di destinazione e produrrà il valore originale quando riconvertito al tipo originale.

1
Supponendo che questo sia valido solo per l'inizializzazione di tipi incorporati, non riesco a vedere come ciò potrebbe danneggiare. Certo, questo potrebbe rompere un codice. Ma dovrebbe essere facile da risolvere.
Johan Kotlinski

1
@ John Dibling: No, l'inizializzazione non è mal formata quando il valore può essere rappresentato esattamente dal tipo di destinazione. (Ed 0è già un intcomunque.)
aschepler

2
@Nim: si noti che questo è solo mal formato all'interno degli {inizializzatori di parentesi graffe }e l'unico utilizzo legacy di questi è per array e strutture POD. Inoltre, se il codice esistente ha cast espliciti a cui appartengono, non si interromperà.
aschepler

4
@j_random_hacker come dice il documento di lavoro, int a = 1.0;è ancora valido.
Johannes Schaub - litb

1
@litb: grazie. In realtà lo trovo comprensibile ma deludente - IMHO sarebbe stato molto meglio richiedere una sintassi esplicita per tutte le conversioni restringenti fin dall'inizio di C ++.
j_random_hacker

Risposte:


41

Mi sono imbattuto in questo cambiamento radicale quando ho usato GCC. Il compilatore ha stampato un errore per un codice come questo:

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

In funzione void foo(const long long unsigned int&):

errore: restringimento della conversione di (((long long unsigned int)i) & 4294967295ull)da long long unsigned inta unsigned intinterno {}

errore: restringimento della conversione di (((long long unsigned int)i) >> 32)da long long unsigned inta unsigned intinterno {}

Fortunatamente, i messaggi di errore erano chiari e la soluzione era semplice:

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

Il codice era in una libreria esterna, con solo due occorrenze in un file. Non credo che il cambiamento radicale influenzerà molto il codice. I novizi potrebbero essere confusi, però.


9

Sarei sorpreso e deluso da me stesso nell'apprendere che qualsiasi codice C ++ che ho scritto negli ultimi 12 anni aveva questo tipo di problema. Ma la maggior parte dei compilatori avrebbe vomitato avvertimenti su qualsiasi "limitazione" del tempo di compilazione per tutto il tempo, a meno che non mi manchi qualcosa.

Anche queste stanno restringendo le conversioni?

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

Se è così, penso che potrebbero apparire un po 'più spesso del tuo esempio di tipo flottante a tipo integrale.


1
Non capisco perché dici che sarebbe una cosa non insolita da trovare nel codice. Qual è la logica tra l'utilizzo di -1 o INT_MAX invece di USHRT_MAX? USHRT_MAX non era in clima alla fine del 2010?

8

Un esempio pratico che ho riscontrato:

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

Il letterale numerico è implicitamente doubleche causa la promozione.


1
quindi fallo floatscrivendo 0.5f. ;)
underscore_d

2
@underscore_d Non funziona se floatera un parametro typedef o template (almeno senza perdita di precisione), ma il punto è che il codice come scritto funzionava con la semantica corretta ed è diventato un errore con C ++ 11. Cioè, la definizione di "cambiamento radicale".
Jed

7

Non sarei poi così sorpreso se qualcuno venisse colto di sorpresa da qualcosa come:

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

(nella mia implementazione, gli ultimi due non producono lo stesso risultato quando riconvertiti in int / long, quindi si stanno restringendo)

Non ricordo di averlo mai scritto, però. È utile solo se un'approssimazione ai limiti è utile per qualcosa.

Anche questo sembra almeno vagamente plausibile:

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
}

ma non è del tutto convincente, perché se so di avere esattamente due valori, perché metterli in array invece che solo float floatval1 = val1, floatval1 = val2;? Qual è la motivazione, tuttavia, perché dovrebbe compilare (e funzionare, a condizione che la perdita di precisione rientri in un'accuratezza accettabile per il programma), mentre float asfloat[] = {val1, val2};non dovrebbe? In entrambi i casi sto inizializzando due float da due interi, è solo che in un caso i due float sono membri di un aggregato.

Ciò sembra particolarmente duro nei casi in cui un'espressione non costante si traduce in una riduzione della conversione anche se (su una particolare implementazione), tutti i valori del tipo di origine sono rappresentabili nel tipo di destinazione e convertibili nei loro valori originali:

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

Supponendo che non ci siano bug, presumibilmente la soluzione è sempre rendere esplicita la conversione. A meno che tu non stia facendo qualcosa di strano con le macro, penso che un inizializzatore di array appaia solo vicino al tipo di array, o almeno a qualcosa che rappresenta il tipo, che potrebbe dipendere da un parametro del modello. Quindi un cast dovrebbe essere facile, se prolisso.


9
"se so di avere esattamente due valori, perché metterli in array" - ad esempio perché un'API come OpenGL lo richiede.
Georg Fritzsche

5

Prova ad aggiungere -Wno-narrowing alle tue CFLAGS, ad esempio:

CFLAGS += -std=c++0x -Wno-narrowing

o CPPFLAGS in caso di compilatori C ++ (ovviamente dipende dal sistema di compilazione o dal Makefile)
Mikolasan

4

La limitazione degli errori di conversione interagisce male con le regole di promozione di interi impliciti.

Ho avuto un errore con il codice che sembrava

struct char_t {
    char a;
}

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

Che produce un errore di conversione restrittivo (che è corretto secondo lo standard). Il motivo è che ce dviene promosso implicitamente a inte il risultato intnon può essere ristretto a char in un elenco di inizializzatori.

OTOH

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

naturalmente va ancora bene (altrimenti si scatenerà l'inferno). Ma sorprendentemente, anche

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

è ok e viene compilato senza avviso se la somma di ced è inferiore a CHAR_MAX. Continuo a pensare che questo sia un difetto in C ++ 11, ma le persone la pensano diversamente, forse perché non è facile da risolvere senza sbarazzarsi della conversione di interi impliciti (che è un relitto del passato, quando le persone scrivevano codice mi piace char a=b*c/de mi aspettavo che funzionasse anche se (b * c)> CHAR_MAX) o restringendo gli errori di conversione (che sono forse una buona cosa).


Mi sono imbattuto in quanto segue che è davvero una sciocchezza fastidiosa: unsigned char x; static unsigned char const m = 0x7f; ... unsigned char r = { x & m };<- restringere la conversione all'interno di {}. Veramente? Quindi l'operatore e converte anche implicitamente i caratteri senza segno in int? Beh, non mi interessa, il risultato è comunque garantito essere un carattere non firmato, argh.
Carlo Wood

" Conversione integer implicito promozioni"?
curioso

2

È stato davvero un cambiamento radicale poiché l'esperienza di vita reale con questa funzione ha mostrato che gcc si era trasformato in un avvertimento da un errore per molti casi a causa di problemi nella vita reale con il porting delle basi di codice C ++ 03 in C ++ 11. Vedi questo commento in un bug report di gcc :

Lo standard richiede solo che "un'implementazione conforme emetta almeno un messaggio diagnostico", quindi è consentita la compilazione del programma con un avviso. Come ha detto Andrew, -Werror = narrowing ti consente di renderlo un errore se lo desideri.

G ++ 4.6 ha fornito un errore ma è stato modificato intenzionalmente in un avviso per 4.7 perché molte persone (me compreso) hanno scoperto che il restringimento delle conversioni era uno dei problemi più comuni durante il tentativo di compilare basi di codice C ++ 03 di grandi dimensioni come C ++ 11 . Codice precedentemente ben formato come char c [] = {i, 0}; (dove sarò sempre e solo all'interno dell'intervallo di caratteri) ha causato errori e ha dovuto essere modificato in char c [] = {(char) i, 0}


1

Sembra che GCC-4.7 non fornisca più errori per restringere le conversioni, ma invece avvisi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.