Les énumérations C ++ sont-elles signées ou non signées?

107

Les énumérations C ++ sont-elles signées ou non signées? Et par extension, est-il sûr de valider une entrée en vérifiant qu'elle est <= votre valeur max, et en omettant> = votre valeur min (en supposant que vous avez commencé à 0 et incrémenté de 1)?

Mat
la source
Lorsque nous utilisons un type enum dans un contexte qui en nécessite le signe, nous parlons en fait de convertir implicitement l'énumération en un type intégral. Le standard C ++ 03 dit que cela est fait par Integral Promotion, rien lié au type sous-jacent de l'énumération. Donc, je ne comprends pas pourquoi chaque réponse ici mentionne que le type sous-jacent n'est pas défini par la norme? J'ai décrit le comportement attendu ici: stackoverflow.com/questions/24802322/…
JavaMan

Réponses:

60

Vous ne devriez pas vous fier à une représentation spécifique. Lisez le lien suivant . De plus, la norme dit que le type intégral utilisé comme type sous-jacent pour une énumération est défini par l'implémentation, sauf qu'il ne doit pas être plus grand que int, sauf si une valeur ne peut pas entrer dans int ou un int unsigned.

En bref: vous ne pouvez pas compter sur une énumération signée ou non signée.

zvrba
la source
28
La réponse de Michael Burr (qui cite la norme) implique en fait que vous pouvez compter sur sa signature si vous définissez une valeur d'énumération comme négative car le type est capable de "représenter toutes les valeurs d'énumération définies dans l'énumération".
Samuel Harmer
101

Allons à la source. Voici ce que dit le document de la norme C ++ 03 (ISO / IEC 14882: 2003) dans 7.2-5 (Déclarations d'énumération):

Le type sous-jacent d'une énumération est un type intégral qui peut représenter toutes les valeurs d'énumération définies dans l'énumération. Le type intégral utilisé comme type sous-jacent pour une énumération est défini par l'implémentation, sauf que le type sous-jacent ne doit pas être plus grand que int à moins que la valeur d'un énumérateur ne puisse tenir dans un int ou un unsigned int.

En bref, votre compilateur peut choisir (évidemment, si vous avez des nombres négatifs pour certaines de vos valeurs d'ennumération, il sera signé).

Michael Burr
la source
Comment éviter les suppositions du compilateur et lui dire d'utiliser un type non signé sous-jacent lorsque toutes les valeurs d'énumération sont de petits entiers positifs? (Nous attrapons une constatation UBsan parce que le compilateur choisit un int, et int souffre de débordement. La valeur est non signée et positive, et notre utilisation dépend d'un wrap non signé pour fournir un décrément ou "foulée négative").
jww
@jww - cela dépendra du compilateur que vous utilisez exactement, je suppose. Étant donné que la norme ne dicte pas le type sous-jacent et laisse cela à l'implémentation, vous devez alors consulter la documentation de votre outil et voir si cette option est possible. Si vous souhaitez garantir un certain comportement dans votre code, pourquoi ne pas convertir le membre enum que vous utilisez dans l'expression?
ysap
22

Vous ne devriez pas dépendre de leur signature ou non. Si vous souhaitez les rendre explicitement signés ou non signés, vous pouvez utiliser ce qui suit:

enum X : signed int { ... };    // signed enum
enum Y : unsigned int { ... };  // unsigned enum
Adam Rosenfield
la source
11
Uniquement dans le futur standard C ++ 0x.
dalle
3
@dalle Le compilateur Microsoft autorise également les énumérations
teodozjan
15

Vous ne devriez pas vous fier à ce qu'il soit signé ou non signé. Selon la norme, l'implémentation définit le type intégral utilisé comme type sous-jacent pour une énumération. Dans la plupart des implémentations, cependant, il s'agit d'un entier signé.

En C ++ 0x, des énumérations fortement typées seront ajoutées, ce qui vous permettra de spécifier le type d'une énumération telle que:

enum X : signed int { ... };    // signed enum
enum Y : unsigned int { ... };  // unsigned enum

Même maintenant, cependant, une validation simple peut être obtenue en utilisant l'énumération comme variable ou type de paramètre comme ceci:

enum Fruit { Apple, Banana };

enum Fruit fruitVariable = Banana;  // Okay, Banana is a member of the Fruit enum
fruitVariable = 1;  // Error, 1 is not a member of enum Fruit
                    // even though it has the same value as banana.
Mat
la source
Je pense que votre deuxième exemple est un peu confus :)
Miral
5

Le compilateur peut décider si les énumérations sont signées ou non.

Une autre méthode de validation des énumérations consiste à utiliser l'énumération elle-même comme type de variable. Par exemple:

enum Fruit
{
    Apple = 0,
    Banana,
    Pineapple,
    Orange,
    Kumquat
};

enum Fruit fruitVariable = Banana;  // Okay, Banana is a member of the Fruit enum
fruitVariable = 1;  // Error, 1 is not a member of enum Fruit even though it has the same value as banana.
Cristián Romo
la source
5

Même certaines vieilles réponses ont obtenu 44 votes positifs, j'ai tendance à ne pas être d'accord avec toutes. En bref, je ne pense pas que nous devrions nous soucier underlying typede l'énumération.

Tout d'abord, le type Enum C ++ 03 est un type distinct qui n'a pas de concept de signe. Depuis la norme C ++ 03dcl.enum

7.2 Enumeration declarations 
5 Each enumeration defines a type that is different from all other types....

Ainsi, lorsque nous parlons du signe d'un type enum, par exemple lorsque nous comparons 2 opérandes enum à l'aide de l' <opérateur, nous parlons en fait de convertir implicitement le type enum en un type intégral. C'est le signe de ce type intégral qui compte . Et lors de la conversion d'énumération en type intégral, cette instruction s'applique:

9 The value of an enumerator or an object of an enumeration type is converted to an integer by integral promotion (4.5).

Et, apparemment, le type sous-jacent de l'énumération n'a rien à voir avec la promotion intégrale. Puisque la norme définit la promotion intégrale comme ceci:

4.5 Integral promotions conv.prom
.. An rvalue of an enumeration type (7.2) can be converted to an rvalue of the first of the following types that can represent all the values of the enumeration
(i.e. the values in the range bmin to bmax as described in 7.2: int, unsigned int, long, or unsigned long.

Ainsi, si un type enum devient signed intou unsigned intdépend de si signed intpeut contenir toutes les valeurs des énumérateurs définis, pas le type sous-jacent de l'énumération.

Voir ma question connexe Signe du type d'énumération C ++ incorrect après la conversion en type intégral

JavaMan
la source
C'est important lorsque vous compilez avec -Wsign-conversion. Nous l'utilisons pour aider à détecter les erreurs involontaires dans notre code. Mais +1 pour avoir cité la norme, et souligné qu'une énumération n'a aucun type ( signedversus unsigned) qui lui est associé.
jww
4

À l'avenir, avec C ++ 0x, des énumérations fortement typées seront disponibles et présenteront plusieurs avantages (tels que la sécurité de type, les types sous-jacents explicites ou la portée explicite). Avec cela, vous pourriez être mieux assuré du signe du type.

Kris Kumler
la source
4

En plus de ce que d'autres ont déjà dit à propos des signés / non signés, voici ce que dit la norme sur la plage d'un type énuméré:

7.2 (6): "Pour une énumération où e (min) est le plus petit énumérateur et e (max) est le plus grand, les valeurs de l'énumération sont les valeurs du type sous-jacent dans l'intervalle b (min) à b (max ), où b (min) et b (max) sont respectivement les valeurs les plus petites et les plus grandes du plus petit champ de bits pouvant stocker e (min) et e (max). Il est possible de définir une énumération dont les valeurs ne sont pas définies par l'un de ses agents recenseurs. "

Donc par exemple:

enum { A = 1, B = 4};

définit un type énuméré où e (min) est 1 et e (max) est 4. Si le type sous-jacent est signé int, alors le plus petit champ de bits requis a 4 bits, et si ints dans votre implémentation est un complément à deux, alors la plage valide de l'énumération est de -8 à 7. Si le type sous-jacent n'est pas signé, il a 3 bits et la plage est comprise entre 0 et 7. Vérifiez la documentation de votre compilateur si cela vous intéresse (par exemple, si vous souhaitez convertir des valeurs intégrales autres que les énumérateurs en type énuméré, vous devez savoir si la valeur est dans la plage de l'énumération ou non - sinon la valeur d'énumération résultante n'est pas spécifiée).

Le fait que ces valeurs soient des entrées valides pour votre fonction peut être un problème différent de savoir si ce sont des valeurs valides du type énuméré. Votre code de vérification est probablement préoccupé par le premier plutôt que par le second, et donc dans cet exemple devrait au moins vérifier> = A et <= B.

Steve Jessop
la source
0

Vérifiez-le avec std::is_signed<std::underlying_type+ les énumérations étendues par défaut àint

https://en.cppreference.com/w/cpp/language/enum implique:

main.cpp

#include <cassert>
#include <iostream>
#include <type_traits>

enum Unscoped {};
enum class ScopedDefault {};
enum class ScopedExplicit : long {};

int main() {
    // Implementation defined, let's find out.
    std::cout << std::is_signed<std::underlying_type<Unscoped>>() << std::endl;

    // Guaranteed. Scoped defaults to int.
    assert((std::is_same<std::underlying_type<ScopedDefault>::type, int>()));

    // Guaranteed. We set it ourselves.
    assert((std::is_same<std::underlying_type<ScopedExplicit>::type, long>()));
}

GitHub en amont .

Compilez et exécutez:

g++ -std=c++17 -Wall -Wextra -pedantic-errors -o main main.cpp
./main

Production:

0

Testé sur Ubuntu 16.04, GCC 6.4.0.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source