web-dev-qa-db-fra.com

Que se passe-t-il si vous statique_cast valeur invalide à enum classe?

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 data [0] soit en réalité 100. À quoi correspond 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 le défaut sera atteint? Sinon, quel est le moyen le plus approprié, le plus efficace et le plus élégant de vérifier une erreur ici?

MODIFIER:

En prime, la norme donne-t-elle des garanties à ce sujet, mais en termes simples?

129
darth happyface

Quelle est la couleur définie selon la norme?

Réponse 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 un type énumération. La valeur reste inchangée si la valeur d'origine se situe dans la plage des valeurs d'énumération (7.2). Sinon, la valeur résultante n'est pas spécifiée (et pourrait 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 non ndefined Behavior (UB) est impliqué. Plus généralement, lorsque vous passez du type sous-jacent au type énumération, aucune valeur dans data[0] Ne peut conduire à UB pour le static_cast.

Après le CWG 1766 (C++ 17) Voir défaut du CWG 1766 . Le paragraphe [expr.static.cast] p10 a été renforcé, de sorte que vous can invoquez UB si vous convertissez une valeur en dehors de la plage représentable d'une énumération vers le type énuméré. . Cela ne s'applique toujours pas au scénario de la question, car data[0] Est du type sous-jacent de l'énumération (voir ci-dessus).

Veuillez noter que le CWG 1766 est considéré comme un défaut dans la norme et qu’il est donc acceptable pour les développeurs de compilateur de s’appliquer à leurs modes de compilation C++ 11 et C++ 14.

(*) char doit avoir une largeur minimale de 8 bits, mais pas nécessairement unsigned. La valeur maximale pouvant être stockée doit être au moins égale à 127 Conformément à 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 compris dans la plage de valeurs représentables pour son type, le comportement n'est pas défini.

Avant le CWG 1766, le type d’intégration de conversion -> type d’énumération peut produire un valeur non spécifiée. La question est: Une valeur non spécifiée peut-elle être en dehors des valeurs représentables pour son type? Je crois que la réponse est no - 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 indéfini".

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


Maintenant, la déclaration switch:

[stmt.switch]/2

La condition doit être de type entier, type d'énumération ou type de classe. [...] Les promotions intégrales sont effectuées.

[conv.prom]/4

Une valeur d'un type d'énumération non réduit dont le type sous-jacent est fixé (7.2) peut être convertie en une valeur de son type sous-jacente. De plus, si une promotion intégrale peut être appliquée à son type sous-jacent, une valeur d'un type d'énumération non délimité dont le type sous-jacent est fixé peut également être convertie en une valeur du type sous-jacent promu.

Remarque: Le type sous-jacent d'une énumération étendue sans = enum-base est int. Pour les énumérations non délimitées, le type sous-jacent est défini par l'implémentation, mais ne doit pas être supérieur à int si int peut contenir les valeurs de tous les énumérateurs.

Pour un énumération non couverte , cela nous mène à/1

Une valeur d'un type entier autre que bool, char16_t, char32_t Ou wchar_t Et dont le rang de conversion entier (4.13) est inférieur au rang int peut être converti en une valeur de type int si int peut représenter toutes les valeurs du type source; sinon, la valeur source peut être convertie en une valeur de type unsigned int.

Dans le cas d'une énumération non réduite , nous aurions affaire à ints ici. Pour les enumerations étendues (enum class Et enum struct), Aucune promotion intégrale ne s'applique. De toute façon, la promotion intégrale ne mène pas non plus à UB, car la valeur stockée se situe dans la plage du type sous-jacent et dans la plage de int.

[stmt.switch]/5

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

Le libellé default devrait être touché.

Remarque: L'opérateur de comparaison peut être examiné de nouveau, mais il n'est pas explicitement utilisé dans la "comparaison" mentionnée. En fait, rien n’indique qu’il introduirait UB dans les cas d’énums étendus ou non.


En prime, la norme donne-t-elle des garanties à ce sujet, mais en termes simples?

Que le enum soit étendu ou non, cela ne fait aucune différence ici. Cependant, que le type sous-jacent soit fixe ou non fait la différence. L'intégral [decl.enum]/7 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. Sinon, pour une énumération où emin est le plus petit énumérateur et emax est la plus grande, les valeurs de l'énumération sont les valeurs comprises dans la plage bmin à bmax, défini comme suit: Soit K be 1 pour la représentation du complément à deux et 0 pour la représentation du complément ou de la magnitude du signe. bmax est la plus petite valeur supérieure ou égale à max (| emin| - K, | emax|) et égal à 2M - 1, où M est un entier non négatif. bmin est zéro si emin n'est pas négatif et − (bmax + K) sinon.

Regardons 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 étendue, car toutes les énumérations étendues ont des types sous-jacents fixes.

Heureusement, le plus petit énumérateur de ColorUnfixed est red = 0x1, Donc max (| emin| - K, | emax|) est égal à | emax| dans tous les cas, ce qui est yellow = 0x2. La plus petite valeur supérieure ou égale à 2, Ce qui est égal à 2M - 1 pour un entier positif M est 3 ( 22 - 1). (Je pense que l'intention est de permettre à la plage de s'étendre par pas de 1 bit.) Il s'ensuit que bmax est 3 et bmin est 0.

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

116
dyp