Que se passe-t-il si vous static_cast une valeur invalide dans la classe enum?

146

Considérez ce code C ++ 11:

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

Supposons que la donnée [0] soit en fait 100. Quelle est la couleur définie selon la norme? En particulier, si je fais plus tard

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

la norme garantit-elle que la valeur par défaut sera atteinte? Sinon, quelle est la manière la plus appropriée, la plus efficace et la plus élégante de rechercher une erreur ici?

ÉDITER:

En prime, la norme offre-t-elle des garanties à ce sujet mais avec une simple énumération?

Dark happyface
la source

Réponses:

131

Quelle est la couleur définie selon la norme?

Répondre avec une citation des standards C ++ 11 et C ++ 14:

[expr.static.cast] / 10

Une valeur de type intégral ou énumération peut être explicitement convertie en type énumération. La valeur reste inchangée si la valeur d'origine se trouve dans la plage des valeurs d'énumération (7.2). Sinon, la valeur résultante n'est pas spécifiée (et peut ne pas être dans cette plage).

Regardons la plage des valeurs d'énumération : [dcl.enum] / 7

Pour une énumération dont le type sous-jacent est fixe, les valeurs de l'énumération sont les valeurs du type sous-jacent.

Avant CWG 1766 (C ++ 11, C ++ 14) Par conséquent, pour data[0] == 100, la valeur résultante est spécifiée (*) et aucun comportement indéfini (UB) n'est impliqué. Plus généralement, lorsque vous transtypez du type sous-jacent au type d'énumération, aucune valeur dans data[0]ne peut conduire à UB pour le static_cast.

Après CWG 1766 (C ++ 17) Voir le défaut CWG 1766 . Le paragraphe [expr.static.cast] p10 a été renforcé, donc vous pouvez maintenant appeler UB si vous transtypez une valeur qui est en dehors de la plage représentable d'une enum en type enum. Cela ne s'applique toujours pas au scénario de la question, car il data[0]est du type sous-jacent de l'énumération (voir ci-dessus).

Veuillez noter que CWG 1766 est considéré comme un défaut dans le Standard, il est donc accepté pour les implémenteurs de compilateurs de s'appliquer à leurs modes de compilation C ++ 11 et C ++ 14.

(*) chardoit avoir une largeur d'au moins 8 bits, mais n'est pas obligatoire unsigned. La valeur maximale stockable doit être au moins 127conforme à l'annexe E de la norme C99.


Comparer à [expr] / 4

Si lors de l'évaluation d'une expression, le résultat n'est pas défini mathématiquement ou n'est pas dans la plage de valeurs représentables pour son type, le comportement n'est pas défini.

Avant le CWG 1766, le type intégral de conversion -> type d'énumération peut produire une valeur non spécifiée . La question est la suivante: une valeur non spécifiée peut-elle être en dehors des valeurs représentables de son type? Je crois que la réponse est non - si la réponse était oui , il n'y aurait aucune différence dans les garanties que vous obtenez pour les opérations sur les types signés entre "cette opération produit une valeur non spécifiée" et "cette opération a un comportement non défini".

Par conséquent, avant CWG 1766, même static_cast<Color>(10000)serait pas Invoke UB; mais après CWG 1766, il fait Invoke UB.


Maintenant, la switchdéclaration:

[stmt.switch] / 2

La condition doit être de type intégral, de type énumération ou de type classe. [...] Des promotions intégrales sont effectuées.

[conv.prom] / 4

A prvalue d'un non délimité type d'énumération dont le type de sous - jacent est fixe (7,2) peut être converti en un prvalue de ce type sous - jacent. De plus, si la promotion intégrale peut être appliquée à son type sous-jacent, une prvalue d'un type d'énumération sans portée dont le type sous-jacent est fixe peut également être convertie en une prvalue du type sous-jacent promu.

Remarque: Le type sous-jacent d'une énumération étendue sans enum-base est int. Pour les énumérations sans portée, le type sous-jacent est défini par l'implémentation, mais ne doit pas être plus grand que intif intpeut contenir les valeurs de tous les énumérateurs.

Pour une énumération non cadrée , cela nous amène à / 1

A prvalue d'un type entier autre que bool, char16_t, char32_t, ou wchar_tdont le rang entier conversion (4,13) est inférieur au rang de intpeut être converti en un prvalue de type intsi intpeut représenter toutes les valeurs du type de source; sinon, la prvalue source peut être convertie en une prvalue de type unsigned int.

Dans le cas d'une énumération non cadrée , nous traiterionsint ici de l'art. Pour les énumérations étendues ( enum classet enum struct), aucune promotion intégrale ne s'applique. De toute façon, la promotion intégrale ne conduit pas non plus à UB, car la valeur stockée est dans la plage du type sous-jacent et dans la plage de int.

[stmt.switch] / 5

Lorsque l' switchinstruction est exécutée, sa condition est évaluée et comparée à chaque constante de cas. Si l'une des constantes de cas est égale à la valeur de la condition, le contrôle est passé à l'instruction qui suit l' caseétiquette correspondante . Si aucune caseconstante ne correspond à la condition et s'il existe une defaultétiquette, le contrôle passe à l'instruction étiquetée par l' defaultétiquette.

L' defaultétiquette doit être frappée.

Remarque: On pourrait jeter un autre regard sur l'opérateur de comparaison, mais il n'est pas explicitement utilisé dans la "comparaison" référencée. En fait, rien n'indique que cela introduirait UB pour les énumérations à portée ou non dans notre cas.


En prime, la norme offre-t-elle des garanties à ce sujet mais avec une simple énumération?

Que la enumportée soit ou non ne fait aucune différence ici. Cependant, le fait que le type sous-jacent soit fixe ou non fait une différence. Le [decl.enum] / 7 complet est:

Pour une énumération dont le type sous-jacent est fixe, les valeurs de l'énumération sont les valeurs du type sous-jacent. Dans le cas contraire, pour une énumération où e min est la plus petite recenseur et e max est la plus grande, les valeurs de l'énumération sont les valeurs de la plage b min à b max , définie comme suit: Soit Kêtre 1une représentation de complément à deux et 0pour une son complément ou représentation de la grandeur des signes. b max est la plus petite valeur supérieure ou égale à max (| e min | - K, | e max |) et égale à 2M - 1 , oùMest un entier non négatif. b min vaut zéro si e min est non négatif et - (b max + K) sinon.

Jetons un coup d'œil à l'énumération suivante:

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

Notez que nous ne pouvons pas définir cela comme une énumération de portée, car toutes les énumérations de portée ont des types sous-jacents fixes.

Heureusement, ColorUnfixedle plus petit énumérateur est red = 0x1, donc max (| e min | - K, | e max |) est égal à | e max | en tout cas, ce qui est yellow = 0x2. La plus petite valeur supérieure ou égale à 2, qui est égale à 2 M - 1 pour un entier positif Mest 3( 2 2 - 1 ). (Je pense que l'intention est de permettre l'étendue de la plage par pas de 1 bit.) Il s'ensuit que b max est 3et bmin est 0.

Par conséquent, 100serait en dehors de la plage de ColorUnfixed, et static_castproduirait une valeur non spécifiée avant le CWG 1766 et un comportement non défini après le CWG 1766.

trs dyp
la source
3
Le type sous-jacent est fixe, donc la plage des valeurs d'énumération (§7.2 [dcl.enum] p7) est "les valeurs du type sous-jacent". 100 est certainement une valeur de char, donc "La valeur est inchangée si la valeur d'origine est dans la plage des valeurs d'énumération (7.2)." s'applique.
Casey
2
J'ai dû chercher pour voir ce que signifiait "UB". ('comportement indéfini') La question ne mentionnait pas la possibilité d'un comportement indéfini; il ne m'est donc pas venu à l'esprit que vous en parliez peut-être.
karadoc
2
@karadoc J'ai ajouté un lien à la première occurrence du terme.
dyp
1
J'adore cette réponse. Pour ceux qui survolent trop rapidement, notez que la dernière phrase "Par conséquent, 100 serait en dehors de la plage ..." ne s'applique que si le code a été modifié pour supprimer la spécification de type sous-jacente (char dans ce cas). Je pense que c'est ce que cela voulait dire, de toute façon.
Eric Seppanen
1
@Ruslan CWG 1766 (ou sa résolution) ne fait pas partie de C ++ 14, mais je pense que cela fera partie de C ++ 17. Même avec les règles C ++ 17, je ne comprends pas tout à fait ce que vous entendez par "invalider le texte de votre réponse". Les autres parties de ma réponse concernent principalement le fait que la "plage de valeurs d'énumération" est celle à laquelle expr.static.cast p10 fait référence.
dyp