2016-02-21 27 views
26

Stavo giocando con le enumerazioni e ho cercato di riprodurre alcuni esempi di pagina from this. esempi iniziali ha lavorato come previsto, ma ho ottenuto alcuni risultati interessanti con codice seguente:Assegnazione errata dei valori in enum

#include <iostream> 

enum num : char { 
    zero = '0', 
    one = '1', 
    two = '2', 
    three = '3', 
    four = '4', 
    five = '5', 
    six = '6' 
}; 

int main() 
{ 
    const char two = '2'; 
    std::cout << two << std::endl; 
    std::cout << num::two; 
    return 0; 
} 

l'output è:

mi aspettavo entrambi i risultati di essere il lo stesso, ma lo num::two sembra stampare un altro valore. Anche questo valore non cambia (50), quindi presumo che questo non è un valore casuale/spazzatura & c'è una sorta di analisi char/int che viene eseguita che non capisco? Ecco the ideone link.

So che posso farlo funzionare assegnando come questo zero = 0, senza virgolette singole e funziona. Tuttavia, voglio sapere cosa sta succedendo dietro le quinte e come potrei controllare quale valore di non-cifre singole posso stampare tramite assegnazioni di virgolette singole.

+0

[ASCII - Wikipedia] (https://en.wikipedia.org/wiki/ASCII), [tabella ASCII] (http://www.asciitable.com/) – Drop

+0

@Drop stavo assumendo anche questo, ma allora perché 'const char two = '2';' funziona come previsto? Entrambi usano l'assegnazione di virgolette singole e questo è ciò che lo rende più confuso. –

+0

''2' = 0x32' o 50 –

risposta

19

Questo dovrebbe effettivamente andare al sovraccarico char ora; sfortunatamente nessuno dei compilatori in questione implementa DR 1601.

[conv.prom]/4:

Una prvalue di un tipo di enumerazione senza ambito cui sottostante tipo è fisso ([dcl.enum]) può essere convertito in un prvalue del suo sottostante tipo.

Questo significa che num può essere promosso a char.

Inoltre, se promozione integrale può essere applicato al suo fondo tipo, un prvalue di un tipo di enumerazione senza ambito cui tipo sottostante è fisso possono anche essere convertiti in un prvalue del sottostante tipo promosso.

Così num può essere promosso a int, anche.

I candidati rilevanti sono:

template <class Traits > 
basic_ostream<char,Traits>& operator<<(basic_ostream<char,Traits>& os, 
             char ch); 
template<class charT, class Traits> 
basic_ostream<charT, Traits>& basic_ostream<charT, Traits>::operator<<(int); 

Per entrambi i candidati, il primo argomento è una conversione identità e la seconda è una promozione. Entrambi num a char e num a int hanno il grado di promozione.

Pre-DR1601, sono ugualmente buoni, quindi entra in scena il tie breaker modello/non-modello. Il primo è un modello di funzione; la seconda è una semplice funzione membro, quindi la seconda vince.

DR1601 ha aggiunto una regola che dice:

Una conversione che promuove un'enumerazione il cui tipo sottostante è fissata al suo tipo di fondo è meglio di uno che promuove il tipo sottostante promosso , se i due sono diverso.

Ciò significa che num a char è ora meglio che num a int, quindi il primo overload è ora una migliore corrispondenza e dovrebbe essere selezionato.

+0

questo è per 'C++ 14', sì? – bolov

+5

@bolov Il cambiamento è un DR rispetto a C++ 11, quindi dovrebbe essere retroattivo. –

+0

@StoryTeller http://eel.is/c++draft/over.ics.rank#4.2 è la nuova formulazione.Lo vedo in C++ 14 – Cubbi

7

Poiché le due chiamate due diversi overload dell'operatore:

  • primi inviti il ​​terzi operator<< per std::ostream e char. Questo stampa il personaggio.

  • Il secondo esempio chiama il membro operator<< per int a causa di promozioni integer, come spiegato da altre risposte.

+0

Sì. Devi essere davvero, molto attento quando combini i personaggi con le stringhe. "Abc" + '1' sarà generalmente compilato senza problemi, ma sicuramente non farà quello che vuoi, portando ad un crash. –

14

Secondo Visual C++ (4,5 promozioni Integral)

4 Una prvalue di un tipo di enumerazione senza ambito cui tipo sottostante è fisso (7,2) possono essere convertiti in un prvalue del suo tipo sottostante . Inoltre, se la promozione integrale può essere applicata al tipo sottostante, un valore di un tipo di enumerazione senza ambito il cui tipo sottostante è fisso può anche essere convertito in un valore prenatale di tipo promosso.

Quindi viene applicata la promozione integrale e viene chiamato l'operatore < per oggetti di tipo int.

5

La ragione è che il vostro enum : char non è lo stesso di char (che è esattamente quello che vogliamo - non vogliamo enum sia la stessa di altri tipi, anche se sono compatibili assegnazione - vogliamo essere void func(num n) distinto da void func(char n), giusto?).

Quindi, poiché non è un enum numchar, verrà utilizzato il operator<<(int), e viene stampato il valore intero, anche se il tipo di fondo è char. Non del tutto ragionevole, ma sono sicuro che questo è ciò che accade.

11

Quando si dice enum num : char, quindi si esprimono il fatto che num è implementato internamente in termini di un char ma possono ancora essere automaticamente convertito in un valore intero, che è non necessariamentechar.

come pagina lei cita dice:

Valori di tipo enumerazione senza ambito sono implicitamente convertibile a tipi integrali.

Vedi Why does a value of an enum with a fixed underlying type of char resolve to fct(int) instead of fct(char)? per una discussione interessante sui problemi della formulazione ++ del C standard per quanto riguarda la combinazione di promozione integrale e tipi sottostanti fissi.

In ogni caso, si può immaginare tutta questa faccenda come una classe con un char variabile membro privato e un operatore di int conversione pubblica:

// very similar to your enum: 
class num { 
private: 
    char c; 

public: 
    num(char c) : c(c) {} 
    operator int() const { 
     return c; 
    } 
}; 

num two { '2' }; 
std::cout << two; // prints 50 

Per aumentare la sicurezza di tipo e rendere il std::cout linea di un errore di compilazione, basta ruotare il enum in un enum class:

enum class num : char 

Questo è ancora simile alla immaginato class num sopra, b ut senza l'operatore di conversione.

Quando si alimenta un'istanza di num a std::cout, allora sei un cliente di num e non dovrebbero logicamente pensare che il formato di output prenderà il suo interno char implementazione in considerazione.

Per ottenere maggiore controllo sul formato di output, è preferibile eseguire qualsiasi altro tipo personalizzato e sovraccaricare operator<< per std::ostream. Esempio:

#include <iostream> 

enum class num : char { 
    zero = '0', 
    one = '1', 
    two = '2', 
    three = '3', 
    four = '4', 
    five = '5', 
    six = '6' 
}; 

std::ostream& operator<<(std::ostream& os, num const& n) 
{ 
    switch (n) 
    { 
     case num::zero: os << "Zero"; break; 
     case num::one: os << "One"; break; 
     case num::two: os << "Two"; break; 
     case num::three: os << "Three"; break; 
     // and so on 
    } 
    return os; 
} 

int main() 
{ 
    std::cout << num::two; // prints "Two" 
} 

Naturalmente, le specifiche char valori delle istanze enum sono ormai diventati abbastanza inutile, così si può anche sbarazzarsi di loro completamente:

enum class num : char { 
    zero, 
    one, 
    two, 
    three, 
    four, 
    five, 
    six 
}; 

Questo può colpire come strano , ma tieni presente che un enum che rappresenta nient'altro che numeri generici da zero a sei non è un caso di utilizzo realistico.

+0

Bella spiegazione dettagliata; tuttavia (per i punti di stile) vorrei invece passare dall'implementazione di 'operator <<' a un'istruzione 'switch'. I compilatori generano avvisi per gli interruttori che non gestiscono tutti gli enumeratori (se si rinuncia al caso 'default'):' swtich (n) {numero num :: zero: return os << "Zero"; case ...} lancia std :: runtime_error ("Unknown enumerator"); '. –

+0

@MatthieuM .: Non è possibile usare 'switch' con una classe enum. –

+2

Cosa? [Certo che puoi!] (Http://ideone.com/xzk1V7). –