web-dev-qa-db-de.com

Was passiert, wenn Sie einen ungültigen Wert für static_cast an die Aufzählungsklasse senden?

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 Farben 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, was ist die richtige, effizienteste und eleganteste Methode, um hier nach Fehlern zu suchen?

BEARBEITEN:

Gibt der Standard als Bonus irgendwelche Garantien dafür, aber mit einfacher Aufzählung?

129
darth happyface

Wie ist die Farbeinstellung gemäß dem Standard?

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

[expr.static.cast]/10

Ein Wert von 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 data[0] == 100 Der resultierende Wert angegeben (*) , und nein ndefiniertes Verhalten (UB) ist beteiligt. Allgemeiner gesagt, wenn Sie vom zugrunde liegenden Typ in den Aufzählungstyp umwandeln, kann kein Wert in data[0] Zu UB für static_cast Führen.

Nach CWG 1766 (C++ 17) Siehe CWG-Defekt 1766 . Der Absatz [expr.static.cast] p10 wurde verbessert, sodass Sie jetzt UB aufrufen können , wenn Sie einen Wert setzen, der außerhalb des darstellbaren Bereichs von liegt eine Aufzählung des Aufzählungstyps. Dies gilt immer noch nicht für das Szenario in der Frage, da data[0] Vom zugrunde liegenden Typ der Aufzählung ist (siehe oben).

Bitte beachten Sie, dass CWG 1766 als Fehler im Standard angesehen wird. Daher ist es für Compiler-Implementierer zulässig, sich auf ihre C++ 11- und C++ 14-Kompilierungsmodi zu beziehen.

(*) char muss mindestens 8 Bit breit sein, muss jedoch nicht unsigned sein. Der maximal speicherbare Wert muss mindestens 127 Gemäß Anhang E des C99-Standards betragen.


Vergleiche mit [expr]/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 unspezifizierter Wert ergeben. Die Frage lautet: Kann ein nicht angegebener Wert außerhalb der darstellbaren Werte für seinen Typ liegen? Ich glaube, die Antwort lautet nein - Wenn die Antwort yes wäre, gäbe es keinen Unterschied in den Garantien, die Sie für Operationen mit signierten Typen erhalten, zwischen "Diese Operation erzeugt einen nicht angegebenen Wert" und "Diese Operation hat ein undefiniertes Verhalten ".

Daher würde vor CWG 1766 sogar static_cast<Color>(10000) nicht UB aufrufen; aber nach CWG 1766 ruft es UB auf .


Nun die switch Anweisung:

[stmt.switch]/2

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

[conv.prom]/4

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

Hinweis: Der zugrunde liegende Typ einer Bereichsaufzählung ohne enum-base ist int. Bei Aufzählungen ohne Gültigkeitsbereich ist der zugrunde liegende Typ implementierungsdefiniert, darf jedoch nicht größer als int sein, wenn int die Werte aller Enumeratoren enthalten kann.

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

Ein Wert eines anderen ganzzahligen Typs als bool, char16_t, char32_t Oder wchar_t, Dessen ganzzahliger Umwandlungsrang (4.13) kleiner ist als der Rang von int kann in einen Wert vom Typ int konvertiert werden, wenn int alle Werte des Quellentyps darstellen kann; Andernfalls kann der Quellwert in einen Wert vom Typ unsigned int konvertiert werden.

Im Fall einer nicht mit einem Bereich versehenen Aufzählung würden wir uns hier mit int befassen. Für Aufzählungen mit Gültigkeitsbereich (enum class Und enum struct) Gilt keine integrale Promotion. In jedem Fall führt die integrale Promotion auch nicht zu UB, da der gespeicherte Wert im Bereich des zugrunde liegenden Typs und im Bereich von int liegt.

[stmt.switch]/5

Wenn die Anweisung switch ausgeführt wird, wird ihre Bedingung ausgewertet und mit jeder Fallkonstante verglichen. Wenn eine der case-Konstanten dem Wert der Bedingung entspricht, wird die Steuerung an die Anweisung übergeben, die auf das übereinstimmende Label case folgt. Wenn keine Konstante case mit der Bedingung übereinstimmt und ein default - Label vorhanden ist, wird die Steuerung an die Anweisung übergeben, die mit dem Label default gekennzeichnet ist.

Das Label default sollte angeklickt werden.

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


Gibt der Standard als Bonus irgendwelche Garantien dafür, aber mit einfacher Aufzählung?

Ob das enum den Gültigkeitsbereich hat oder nicht, spielt hier keine Rolle. Es macht jedoch einen Unterschied, ob der zugrunde liegende Typ festgelegt ist oder nicht. Die komplette [decl.enum]/7 ist:

Bei einer Aufzählung, deren zugrunde liegender Typ festgelegt ist, sind die Werte der Aufzählung die Werte des zugrunde liegenden Typs. Andernfalls für eine Aufzählung mit emindest ist der kleinste Enumerator und emax ist der größte, die Werte der Aufzählung sind die Werte im Bereich bmindest bis bmax, wie folgt definiert: Sei K1 für die Zweierkomplementdarstellung und 0 für die Darstellung des eigenen Komplements oder der Vorzeichengröße. bmax ist der kleinste Wert größer oder gleich max (| emindest| - K, | emax|) und gleich 2M - 1, wobei M eine nicht negative ganze Zahl ist. bmindest ist Null, wenn emindest ist nicht negativ und − (bmax + K) ansonsten.

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 bereichsspezifische Aufzählung definieren können, da alle bereichsspezifischen Aufzählungen feste zugrunde liegende Typen haben.

Glücklicherweise ist der kleinste Enumerator von ColorUnfixedred = 0x1, Also max (| emindest| - K, | emax|) ist gleich | emax| auf jeden Fall yellow = 0x2. Der kleinste Wert größer oder gleich 2, Der gleich 2 istM - 1 für eine positive ganze Zahl M ist 3 ( 22 - 1). (Ich denke, die Absicht ist es, den Bereich in 1-Bit-Schritten zu erweitern.) Daraus folgt bmax ist 3 und bmin ist 0.

Daher würde 100 Außerhalb des Bereichs von ColorUnfixed liegen, und static_cast Würde einen nicht angegebenen Wert vor CWG 1766 und ein undefiniertes Verhalten nach CWG 1766 erzeugen.

116
dyp