Was passiert, wenn Sie einen ungültigen Wert für static_cast in die Enum-Klasse eingeben?

146

Betrachten Sie diesen C ++ 11-Code:

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

Angenommen, Daten [0] sind tatsächlich 100. Auf welche Farbe wird gemäß dem Standard eingestellt? Insbesondere, wenn ich es später tue

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

garantiert der Standard, dass der Standard erreicht wird? Wenn nicht, wie kann hier am besten, effizientesten und elegantesten nach Fehlern gesucht werden?

BEARBEITEN:

Gibt der Standard als Bonus diesbezüglich Garantien, jedoch mit einfacher Aufzählung?

Darth Happyface
quelle

Antworten:

131

Was ist die Farbe gemäß dem Standard eingestellt?

Antwort mit einem Zitat aus den Standards C ++ 11 und C ++ 14:

[expr.static.cast] / 10

Ein Wert vom Integral- oder Aufzählungstyp kann explizit in einen Aufzählungstyp konvertiert werden. Der Wert bleibt unverändert, wenn der ursprüngliche Wert im Bereich der Aufzählungswerte (7.2) liegt. Andernfalls ist der resultierende Wert nicht angegeben (und liegt möglicherweise nicht in diesem Bereich).

Schauen wir uns den Bereich der Aufzählungswerte an : [dcl.enum] / 7

Bei einer Aufzählung, deren zugrunde liegender Typ festgelegt ist, sind die Werte der Aufzählung die Werte des zugrunde liegenden Typs.

Vor CWG 1766 (C ++ 11, C ++ 14) Daher wird für angegeben data[0] == 100, dass der resultierende Wert angegeben wird (*) und kein undefiniertes Verhalten (UB) beteiligt ist. Im Allgemeinen kann beim Umwandeln vom zugrunde liegenden Typ in den Aufzählungstyp kein Wert in data[0]zu UB für die führen static_cast.

Nach CWG 1766 (C ++ 17) Siehe CWG-Defekt 1766 . Der Absatz [expr.static.cast] p10 wurde verstärkt, sodass Sie jetzt UB aufrufen können , wenn Sie einen Wert, der außerhalb des darstellbaren Bereichs einer Aufzählung liegt, in den Aufzählungstyp umwandeln. Dies gilt immer noch nicht für das Szenario in der Frage, da data[0]es sich um den zugrunde liegenden Typ der Aufzählung handelt (siehe oben).

Bitte beachten Sie, dass CWG 1766 als Fehler im Standard angesehen wird. Daher wird davon ausgegangen, dass Compiler-Implementierer auf ihre C ++ 11- und C ++ 14-Kompilierungsmodi anwenden.

(*) charmuss mindestens 8 Bit breit sein, muss es aber nicht sein unsigned. Der maximal speicherbare Wert muss mindestens 127Anhang E der Norm C99 entsprechen.


Vergleiche mit [Ausdruck] / 4

Wenn während der Auswertung eines Ausdrucks das Ergebnis nicht mathematisch definiert ist oder nicht im Bereich der darstellbaren Werte für seinen Typ liegt, ist das Verhalten undefiniert.

Vor CWG 1766 kann der Konvertierungsintegraltyp -> Aufzählungstyp einen nicht angegebenen Wert erzeugen . Die Frage ist: Kann ein nicht spezifizierter Wert außerhalb der darstellbaren Werte für seinen Typ liegen? Ich glaube, die Antwort ist nein - wenn die Antwort ja wäre , gäbe es keinen Unterschied in den Garantien, die Sie für Operationen mit vorzeichenbehafteten Typen erhalten, zwischen "diese Operation erzeugt einen nicht spezifizierten Wert" und "diese Operation hat ein undefiniertes Verhalten".

Daher vor der CWG 1766, auch static_cast<Color>(10000)würde nicht invoke UB; aber nach CWG 1766 ruft es UB auf.


Nun die switchAussage:

[stmt.switch] / 2

Die Bedingung muss vom Integraltyp, Aufzählungstyp oder Klassentyp sein. [...] Integrale Werbeaktionen werden durchgeführt.

[conv.prom] / 4

Ein Wert eines nicht skalierten Aufzählungstyps, dessen zugrunde liegender Typ fest ist (7.2), kann in einen Wert seines zugrunde liegenden Typs konvertiert werden. Wenn eine integrale Heraufstufung auf den zugrunde liegenden Typ angewendet werden kann, kann außerdem ein Wert eines nicht skalierten Aufzählungstyps, dessen zugrunde liegender Typ festgelegt ist, in einen Wert des heraufgestuften zugrunde liegenden Typs konvertiert werden.

Hinweis: Der zugrunde liegende Typ einer Enum mit Gültigkeitsbereich ohne Enum-Base ist int. Für Aufzählungen ohne Gültigkeitsbereich ist der zugrunde liegende Typ implementierungsdefiniert, darf jedoch nicht größer sein, als intwenn intdie Werte aller Aufzähler enthalten können.

Für eine Aufzählung ohne Gültigkeitsbereich führt uns dies zu / 1

A prvalue eines ganzzahligen anderen Typ als bool, char16_t, char32_t, oder wchar_tderen ganzzahlige Umwandlungs rank (4,13) geringer ist als der Rang der intauf ein prvalue vom Typ umgewandelt werden , intwenn intalle Werte des Quelltyps darstellen; Andernfalls kann der Quellwert in einen Wert vom Typ konvertiert werden unsigned int.

Im Falle einer Aufzählung ohne Gültigkeitsbereich würden wir uns hier mit ints befassen . Für Aufzählungen mit Gültigkeitsbereich ( enum classund enum struct) gilt keine integrale Werbung. In keiner Weise führt die integrale Heraufstufung auch nicht zu UB, da der gespeicherte Wert im Bereich des zugrunde liegenden Typs und im Bereich von liegt int.

[stmt.switch] / 5

Wenn die switchAnweisung ausgeführt wird, wird ihre Bedingung ausgewertet und mit jeder Fallkonstante verglichen. Wenn eine der Fallkonstanten dem Wert der Bedingung entspricht, wird die Steuerung an die Anweisung übergeben, die auf die übereinstimmende caseBezeichnung folgt . Wenn keine caseKonstante mit der Bedingung übereinstimmt und eine defaultBeschriftung vorhanden ist , wird die Steuerung an die durch die defaultBeschriftung gekennzeichnete Anweisung übergeben .

Das defaultEtikett sollte getroffen werden.

Hinweis: Man könnte sich den Vergleichsoperator noch einmal ansehen, er wird jedoch im genannten "Vergleich" nicht explizit verwendet. Tatsächlich gibt es in unserem Fall keinen Hinweis darauf, dass UB für Enumes mit oder ohne Gültigkeitsbereich eingeführt werden würde.


Gibt der Standard als Bonus diesbezüglich Garantien, jedoch mit einfacher Aufzählung?

Ob das enumGeltungsbereich ist oder nicht, spielt hier keine Rolle. Es macht jedoch einen Unterschied, ob der zugrunde liegende Typ festgelegt ist oder nicht. Das vollständige [dec.enum] / 7 lautet:

Bei einer Aufzählung, deren zugrunde liegender Typ festgelegt ist, sind die Werte der Aufzählung die Werte des zugrunde liegenden Typs. Ansonsten für eine Aufzählung , wo e min ist die kleinste enumerator und e max die größte ist, sind die Werte der Enumeration die Werte im Bereich b min bis b max , wie folgt definiert: Es sei Ksein 1Komplement - Darstellung für eine Zweier-und 0für eine das eigene Komplement oder die Darstellung der Vorzeichengröße. b max ist der kleinste Wert größer oder gleich max (| e min | - K, | e max |) und gleich 2M - 1 , wobeiMeine nicht negative ganze Zahl ist. b min ist Null, wenn e min nicht negativ ist und - (b max + K) sonst.

Schauen wir uns die folgende Aufzählung an:

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

Beachten Sie, dass wir dies nicht als Aufzählung mit Gültigkeitsbereich definieren können, da alle Aufzählungen mit Gültigkeitsbereich feste zugrunde liegende Typen haben.

Glücklicherweise ist ColorUnfixedder kleinste Enumerator red = 0x1, also ist max (| e min | - K, | e max |) gleich | e max | auf jeden Fall, das ist yellow = 0x2. Der kleinste Wert größer oder gleich 2, der für eine positive ganze Zahl gleich 2 M - 1M ist, ist 3( 2 2 - 1 ). (Ich denke, die Absicht ist es, den Bereich in 1-Bit-Schritten erweitern zu lassen.) Daraus folgt, dass b max ist 3und bmin ist 0.

Daher 100würde außerhalb des Bereichs von liegen ColorUnfixedund static_castwürde einen nicht spezifizierten Wert vor CWG 1766 und ein undefiniertes Verhalten nach CWG 1766 erzeugen.

dyp
quelle
3
Der zugrunde liegende Typ ist fest, daher ist der Bereich der Aufzählungswerte (§7.2 [dcl.enum] p7) "die Werte des zugrunde liegenden Typs". 100 ist sicherlich ein Wert von char, also "Der Wert bleibt unverändert, wenn der ursprüngliche Wert im Bereich der Aufzählungswerte (7.2) liegt." gilt.
Casey
2
Ich musste suchen, um nachzuschlagen, was "UB" bedeutet. ('undefiniertes Verhalten') In der Frage wurde die Möglichkeit eines undefinierten Verhaltens nicht erwähnt. Mir ist also nicht in den Sinn gekommen, dass Sie darüber sprechen könnten.
Karadoc
2
@karadoc Ich habe beim ersten Auftreten des Begriffs einen Link hinzugefügt.
Dyp
1
Ich liebe diese Antwort. Wenn Sie zu schnell überfliegen, beachten Sie, dass der letzte Satz "Daher würde 100 außerhalb des Bereichs liegen ..." nur gilt, wenn der Code geändert wurde, um die zugrunde liegende Typspezifikation zu entfernen (in diesem Fall char). Ich denke, das war sowieso gemeint.
Eric Seppanen
1
@ Ruslan CWG 1766 (oder dessen Auflösung) ist nicht Teil von C ++ 14, aber ich denke, es wird Teil von C ++ 17 sein. Selbst mit den C ++ 17-Regeln verstehe ich nicht ganz, was Sie mit "weiteren Text Ihrer Antwort ungültig machen" meinen. Die anderen Teile meiner Antwort befassen sich hauptsächlich damit, dass sich der "Bereich der Aufzählungswerte" auf expr.static.cast p10 bezieht.
Dyp