Cosa succede se static_cast non è valido per enumare la classe?


146

Considera questo codice C ++ 11:

enum class Color : char { red = 0x1, yellow = 0x2 }
// ...
char *data = ReadFile();
Color color = static_cast<Color>(data[0]);

Supponiamo che i dati [0] siano in realtà 100. Qual è il colore impostato secondo lo standard? In particolare, se lo farò più tardi

switch (color) {
    // ... red and yellow cases omitted
    default:
        // handle error
        break;
}

lo standard garantisce che il default verrà colpito? In caso contrario, qual è il modo corretto, più efficiente ed elegante per verificare la presenza di un errore qui?

MODIFICARE:

Come bonus, lo standard offre garanzie a riguardo ma con enum chiaro?

Risposte:


131

Qual è il colore impostato secondo lo standard?

Rispondere con una citazione dagli standard C ++ 11 e C ++ 14:

[Expr.static.cast] / 10

Un valore di tipo integrale o di enumerazione può essere esplicitamente convertito in un tipo di enumerazione. Il valore rimane invariato se il valore originale è compreso nell'intervallo dei valori di enumerazione (7.2). In caso contrario, il valore risultante non è specificato (e potrebbe non essere compreso in tale intervallo).

Esaminiamo l' intervallo dei valori di enumerazione : [dcl.enum] / 7

Per un'enumerazione il cui tipo sottostante è fisso, i valori dell'enumerazione sono i valori del tipo sottostante.

Prima di CWG 1766 (C ++ 11, C ++ 14) Pertanto, per data[0] == 100, viene specificato il valore risultante (*) e non è coinvolto nessun comportamento indefinito (UB) . Più in generale, quando si esegue il cast dal tipo sottostante al tipo di enumerazione, nessun valore in data[0]può portare a UB per il file static_cast.

Dopo CWG 1766 (C ++ 17) Vedi difetto CWG 1766 . Il paragrafo [expr.static.cast] p10 è stato rafforzato, quindi ora puoi invocare UB se lanci un valore al di fuori dell'intervallo rappresentabile di un enum nel tipo enum. Questo non si applica ancora allo scenario nella domanda, poiché data[0]è del tipo sottostante dell'enumerazione (vedi sopra).

Si noti che CWG 1766 è considerato un difetto dello standard, quindi è accettato per gli implementatori di compilatori applicare alle loro modalità di compilazione C ++ 11 e C ++ 14.

(*) chardeve essere largo almeno 8 bit, ma non lo è unsigned. Il valore massimo memorizzabile deve essere almeno 127conforme all'allegato E della norma C99.


Confronta con [expr] / 4

Se durante la valutazione di un'espressione, il risultato non è definito matematicamente o non è compreso nell'intervallo di valori rappresentabili per il suo tipo, il comportamento non è definito.

Prima di CWG 1766, il tipo integrale di conversione -> tipo di enumerazione può produrre un valore non specificato . La domanda è: un valore non specificato può essere esterno ai valori rappresentabili per il suo tipo? Credo che la risposta sia no : se la risposta fosse , non ci sarebbe alcuna differenza nelle garanzie che si ottengono per le operazioni sui tipi firmati tra "questa operazione produce un valore non specificato" e "questa operazione ha un comportamento indefinito".

Quindi, prima CWG 1766, anche static_cast<Color>(10000)sarebbe non invoke UB; ma dopo CWG 1766, si fa invoke UB.


Ora, la switchdichiarazione:

[Stmt.switch] / 2

La condizione deve essere di tipo integrale, tipo di enumerazione o tipo di classe. [...] Vengono eseguite promozioni integrali.

[Conv.prom] / 4

Una prvalue di un senza ambito tipo enumerazione i cui sottostante tipo è fissato (7.2) può essere convertito in un prvalue del suo tipo sottostante. Inoltre, se la promozione integrale può essere applicata al suo tipo sottostante, un valore di un tipo di enumerazione senza ambito il cui tipo sottostante è fisso può anche essere convertito in un valore del tipo sottostante promosso.

Nota: il tipo di base di un enum con ambito senza enum-base è int. Per gli enum senza ambito il tipo sottostante è definito dall'implementazione, ma non deve essere più grande di intse intpuò contenere i valori di tutti gli enumeratori.

Per un'enumerazione senza ambito , questo ci porta a / 1

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

Nel caso di un senza ambito enumerazione, avremmo a che fare con ints qui. Per le enumerazioni con ambito ( enum classe enum struct), non si applica alcuna promozione integrale. In ogni caso, la promozione integrale non porta nemmeno a UB, poiché il valore memorizzato è compreso nell'intervallo del tipo sottostante e nell'intervallo di int.

[Stmt.switch] / 5

Quando switchviene eseguita l' istruzione, la sua condizione viene valutata e confrontata con ogni costante di caso. Se una delle costanti del caso è uguale al valore della condizione, il controllo viene passato all'istruzione che segue l' caseetichetta corrispondente . Se nessuna casecostante corrisponde alla condizione e se esiste defaultun'etichetta, il controllo passa all'istruzione etichettata defaultdall'etichetta.

L' defaultetichetta dovrebbe essere colpita.

Nota: si potrebbe dare un'altra occhiata all'operatore di confronto, ma non è esplicitamente usato nel "confronto" riferito. In realtà, non c'è alcun suggerimento che introdurrebbe UB per enumerazioni con ambito o senza ambito nel nostro caso.


Come bonus, lo standard offre garanzie a riguardo ma con enum chiaro?

Se l' enumambito è o meno non fa alcuna differenza qui. Tuttavia, fa la differenza se il tipo sottostante è riparato o meno. Il [decl.enum] completo / 7 è:

Per un'enumerazione il cui tipo sottostante è fisso, i valori dell'enumerazione sono i valori del tipo sottostante. In caso contrario, per un'enumerazione dove e min è il più piccolo enumeratore ed e max è la più grande, i valori della enumerazione sono i valori nel range b min a b massimo , definito come segue: Let Ksia 1per la rappresentazione di un complemento a due e 0per un complemento o rappresentazione della grandezza del segno. b max è il valore più piccolo maggiore o uguale a max (| e min | - K, | e max |) e uguale a 2M - 1 , doveMè un numero intero non negativo. b min è zero se e min non è negativo e - (b max + K) in caso contrario.

Diamo un'occhiata alla seguente enumerazione:

enum ColorUnfixed /* no fixed underlying type */
{
    red = 0x1,
    yellow = 0x2
}

Si noti che non è possibile definirlo come enum con ambito, poiché tutti gli enum con ambito hanno tipi fissi sottostanti.

Fortunatamente, ColorUnfixedl'enumeratore più piccolo è red = 0x1, quindi max (| e min | - K, | e max |) è uguale a | e max | in ogni caso, che è yellow = 0x2. Il valore più piccolo maggiore o uguale a 2, che è uguale a 2 M - 1 per un numero intero positivo Mè 3( 2 2 - 1 ). (Penso che l'intenzione sia quella di consentire l'estensione dell'intervallo in incrementi di 1 bit.) Ne consegue che b max è 3e bmin è 0.

Pertanto, 100sarebbe al di fuori dell'intervallo di ColorUnfixede static_castprodurrebbe un valore non specificato prima di CWG 1766 e un comportamento indefinito dopo CWG 1766.


3
Il tipo sottostante è fisso, quindi l'intervallo dei valori di enumerazione (§7.2 [dcl.enum] p7) è "i valori del tipo sottostante". 100 è certamente un valore di char, quindi "Il valore rimane invariato se il valore originale è compreso nell'intervallo dei valori di enumerazione (7.2)." si applica.
Casey,

2
Ho dovuto cercare il significato di "UB". ("comportamento indefinito") La domanda non menzionava la possibilità di un comportamento indefinito; quindi non mi è venuto in mente che potresti parlarne.
Karadoc,

2
@karadoc Ho aggiunto un link alla prima occorrenza del termine.
dyp,

1
Adoro questa risposta. Per coloro che si scremano troppo rapidamente, si noti che l'ultima frase "Pertanto, 100 sarebbe al di fuori dell'intervallo ..." si applica solo se il codice fosse modificato per rimuovere la specifica del tipo sottostante (carattere in questo caso). Penso che sia quello che intendevo, comunque.
Eric Seppanen,

1
@Ruslan CWG 1766 (o la sua risoluzione) non fa parte di C ++ 14, ma penso che farà parte di C ++ 17. Anche con le regole di C ++ 17, non capisco bene cosa intendi con "invalidare ulteriore testo della tua risposta". Le altre parti della mia risposta riguardano principalmente il fatto che "l'intervallo dei valori di enumerazione" è che expr.static.cast p10 si riferisce.
dyp,
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.