2015-01-08 22 views
13

g ++ 4.9.0 -O2 -std = C++ 11avvertimento: restringimento di conversione C++ 11

template<class T> 
struct vec3 { 
    T x, y, z; 
    vec3() = default; 
    vec3(const vec3<T> &other) = default; 
    vec3(T xx, T yy, T zz) { x = xx; y = yy; z = zz; } 
    vec3<T> operator-(const vec3<T> &other) { 
     return vec3<T>{ x - other.x, y - other.y, z - other.z }; 
    } 
}; 

int main() { 
    vec3<char> pos{ 0, 0, 0 }; 
    vec3<char> newPos{ 0, 0, 0 }; 
    auto p = pos - newPos; 

    return 0; 
} 

ottengo l'avvertimento:

!!warning: narrowing conversion of ‘(((int)((vec3<char>*)this)->vec3<char>::x) - ((int)other.vec3<char>::x))’ from ‘int’ to ‘char’ inside { } [-Wnarrowing] 

Ma se lo faccio con (...) di {...} all'interno della funzione operator- l'avviso scompare. Perché?

+2

Date un'occhiata alla discussione in questo [bug report GCC] (https://gcc.gnu.org/bugzilla/show_bug. cgi? id = 44500), in particolare [risposta di Jonathan Wakely] (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=44500#c3) –

+1

'{}' cattura le conversioni restrittive (rendendo il codice illecito formata). '()' non lo fa. O stai chiedendo perché si sta restringendo? –

+1

Sì, perché si restringe? – hidayat

risposta

11

In primo luogo, perché restringere? Che viene dal §5/10:

Molti operatori binari che si aspettano operandi di tipo aritmetico o di enumerazione causano conversioni e producono tipi di risultato in modo simile. Lo scopo è quello di produrre un tipo comune, che è anche il tipo del risultato. Questo modello è chiamato conversioni aritmetiche abituali, che sono definite come segue:

- [..]

- Altrimenti, le promozioni integrali (4.5) sono eseguite su entrambi gli operandi.

dove la promozione integrale è definito in 4.5/1:

Una prvalue di tipo intero diverso da bool, char16_t, char32_t o wchar_t cui rango conversione intero (4.13) è inferiore alla il grado int può essere convertito in un valore di tipo int se int può rappresentare tutti i valori del tipo di origine; in caso contrario, il valore di origine può essere convertito in un valore di tipo unsigned int.

Nel nostro caso, allora, abbiamo decltype(char + char) è int perché la conversione rango char s' meno di int in modo che entrambi sono promossi al int prima della chiamata a operator+.Ora, abbiamo int s che stiamo passando ad un costruttore che prende char s. Per definizione (§8.5.4/7, specificamente 7,4):

Un restringendo conversione è una conversione implicita

(7,4) - da un tipo intero o tipo di enumerazione senza ambito di un tipo intero che non può rappresentare tutti i valori del tipo originale, tranne quando la fonte è un'espressione costante il cui valore dopo le promozioni integrali si adatta al tipo di destinazione.

che è esplicitamente vietato in lista-inizializzazione specificamente come da §8.5.4/3 (sottolineatura mia, il "vedi sotto" in realtà si riferisce a quello che ho appena copiato in precedenza):

Lista- inizializzazione di un oggetto o di riferimento di tipo T è definito come segue

- [..]

- Altrimenti, se T è un tipo di classe, i costruttori sono considerati. I costruttori applicabili sono enumerati e il migliore viene scelto tramite la risoluzione di sovraccarico (13.3, 13.3.1.7). Se per convertire uno qualsiasi degli argomenti è necessaria una conversione di restringimento (vedere di seguito), il programma è malformato. [...]

Questo è il motivo per cui il vostro vec3<T>{int, int, int} ti dà un avvertimento: il programma è mal formata a causa di promozione intero che richiede una conversione restringimento su tutte le espressioni. Ora, l'affermazione sul "mal formato" si pone specificamente solo nel contesto dell'inizializzazione delle liste. Questo è il motivo per cui se si inizializza il vostro vettore, senza {}s, non si vede che l'avvertimento:

vec3<T> operator-(const vec3<T> &other) { 
    // totally OK: implicit conversion from int --> char is allowed here 
    return vec3<T>(x - other.x, y - other.y, z - other.z); 
} 

Come per risolvere questo problema - basta chiamare il costruttore senza lista-inizializzazione è probabilmente la soluzione più semplice. In alternativa, è possibile continuare a utilizzare list-inizializzazione e proprio template vostro costruttore:

template <typename A, typename B, typename C> 
vec3(A xx, B yy, C zz) 
: x(xx) // note these all have to be()s and not {}s for the same reason 
, y(yy) 
, z(yy) 
{ } 
4

Un paio di cose stanno succedendo qui. Innanzitutto, la sintassi {...} impedisce conversioni di restringimento implicito. Quindi la soluzione semplice è quella di cambiare parentesi graffe alle parentesi:

vec3<T> operator-(const vec3<T> &other) { 
    return vec3<T>(x - other.x, y - other.y, z - other.z); 
} 

La seconda cosa accadendo è, "? Eh char meno un char è un char, qual è il problema ?!" E la risposta qui è che C/C++ vuole usare dimensioni naturali per operazioni aritmetiche. Questo è il motivo per cui viene visualizzato il cast (int) nel messaggio di errore. Here is a good explanation del motivo per cui lo fa (nel caso in cui la risposta StackOverflow scompaia mai, sta citando 6.3.1.1 dello standard C11).

Così, l'altro modo per risolvere il tuo codice è:

vec3<T> operator-(const vec3<T> &other) { 
    return vec3<T>{ 
    static_cast<char>(x - other.x), 
    static_cast<char>(y - other.y), 
    static_cast<char>(z - other.z) 
    }; 
} 

A proposito, punto 7 in Effective moderna C++ mi ha convinto che ci sono momenti in cui () è meglio per inizializzare con, e ci sono momenti quando {} è meglio. A volte devi semplicemente scrollare le spalle e usare l'altro.

+2

Chiaro e conciso, ha spiegato esattamente perché stavo ottenendo un errore nel tentativo di inizializzare un membro 'uint16_t' con l'aggiunta di 2' uint16_t's. (ad es. 'Foo foo {x + y}'). – dwanderson