Enumeration declaration
Une énumération est un type distinct dont la valeur est restreinte à une plage de valeurs (voir ci-dessous pour plus de détails), qui peut inclure plusieurs constantes explicitement nommées (" énumérateurs ").
Les valeurs des constantes sont des valeurs d'un type intégral connu comme le type sous-jacent de l'énumération. Une énumération a la même taille , représentation des valeurs , et exigences d'alignement que son type sous-jacent. De plus, chaque valeur d'une énumération a la même représentation que la valeur correspondante du type sous-jacent.
Une énumération est (re)déclarée en utilisant la syntaxe suivante :
enum-key
attr
(optionnel)
enum-head-name
(optionnel)
enum-base
(optionnel)
{
enumerator-list
(optionnel)
}
|
(1) | ||||||||
enum-key
attr
(optionnel)
enum-head-name
(optionnel)
enum-base
(optionnel)
{
enumerator-list
, }
|
(2) | ||||||||
enum-key
attr
(optionnel)
enum-head-name
enum-base
(optionnel)
;
|
(3) | (depuis C++11) | |||||||
| enum-key | - |
|
||||
| attr | - | (depuis C++11) séquence facultative d'un nombre quelconque d' attributs | ||||
| enum-head-name | - |
|
||||
| enum-base | - |
(depuis C++11)
deux-points (
:
), suivi d'un
séquence-de-spécificateurs-de-type
qui nomme un type intégral (s'il est qualifié cv, les qualifications sont ignorées) qui servira de type sous-jacent fixe pour ce type d'énumération
|
||||
| enumerator-list | - |
liste séparée par des virgules de définitions d'énumérateurs, chacune étant soit simplement un
identifiant
unique, qui devient le nom de l'énumérateur, soit un identifiant unique avec une expression constante :
identifiant
=
expression-constante
.
Dans les deux cas, l'
identifiant
peut être directement suivi d'une
séquence de spécificateurs d'attributs
facultative.
(depuis C++17)
|
Il existe deux types distincts d'énumérations :
l'énumération non-scopée
(déclarée avec le
enum-key
enum
) et
l'énumération scopée
(déclarée avec le
enum-key
enum class
ou
enum struct
).
Table des matières |
Énumérations non délimitées
enum
nom
(optionnel)
{
énumérateur
=
expression-constante
,
énumérateur
=
expression-constante
,
...
}
|
(1) | ||||||||
enum
nom
(optionnel)
:
type
{
énumérateur
=
expression-constante
,
énumérateur
=
expression-constante
,
...
}
|
(2) | (depuis C++11) | |||||||
enum
nom
:
type
;
|
(3) | (depuis C++11) | |||||||
Chaque énumérateur devient une constante nommée du type de l'énumération (c'est-à-dire, name ), visible dans la portée englobante, et peut être utilisé partout où des constantes sont requises.
Chaque énumérateur est associé à une valeur du type sous-jacent. Lorsque
=
sont fournis dans une
enumerator-list
, les valeurs des énumérateurs sont définies par ces
constant-expression
s associées. Si le premier énumérateur n'a pas de
=
, la valeur associée est zéro. Pour tout autre énumérateur dont la définition n'a pas de
=
, la valeur associée est la valeur de l'énumérateur précédent plus un.
enum Foo { a, b, c = 10, d, e = 1, f, g = f + c }; //a = 0, b = 1, c = 10, d = 11, e = 1, f = 2, g = 12
Le nom d'une énumération non délimitée peut être omis : une telle déclaration introduit uniquement les énumérateurs dans la portée englobante :
enum { a, b, c = 0, d = a + 2 }; // définit a = 0, b = 1, c = 0, d = 2
Lorsqu'une énumération non délimitée est un membre de classe, ses énumérateurs peuvent être accédés en utilisant les opérateurs d'accès aux membres de classe
.
et
->
:
struct X { enum direction { left = 'l', right = 'r' }; }; X x; X* p = &x; int a = X::direction::left; // autorisé uniquement en C++11 et versions ultérieures int b = X::left; int c = x.left; int d = p->left;
|
Dans les spécificateurs de déclaration d'une déclaration de membre , la séquence
est toujours analysée comme faisant partie d'une déclaration d'énumération : struct S { enum E1 : int {}; enum E1 : int {}; // error: redeclaration of enumeration, // NOT parsed as a zero-length bit-field of type enum E1 }; enum E2 { e1 }; void f() { false ? new enum E2 : int(); // OK: 'int' is NOT parsed as the underlying type } |
(depuis C++11) |
Nom d'énumération à des fins de liaison
Une énumération sans nom qui ne possède pas de typedef name for linkage purposes et qui a un énumérateur est désignée, pour les linkage purposes , par son type sous-jacent et son premier énumérateur ; une telle énumération est dite avoir un énumérateur comme name for linkage purposes .
Énumérations délimitées
1)
déclare un type d'énumération délimitée dont le type sous-jacent est
int
(les mots-clés
class
et
struct
sont exactement équivalents)
2)
déclare un type d'énumération délimitée dont le type sous-jacent est
type
3)
déclaration opaque d'énumération pour une énumération délimitée dont le type sous-jacent est
int
4)
déclaration opaque d'énumération pour une énumération délimitée dont le type sous-jacent est
type
Chaque
énumérateur
devient une constante nommée du type de l'énumération (c'est-à-dire
nom
), qui est contenue dans la portée de l'énumération, et peut être accédée en utilisant l'opérateur de résolution de portée. Il n'y a pas de conversions implicites des valeurs d'un énumérateur délimité vers les types entiers, bien que
Exécuter ce code
#include <iostream> int main() { enum class Color { red, green = 20, blue }; Color r = Color::blue; switch(r) { case Color::red : std::cout << "red\n"; break; case Color::green: std::cout << "green\n"; break; case Color::blue : std::cout << "blue\n"; break; } // int n = r; // erreur : pas de conversion implicite de l'énumération délimitée vers int int n = static_cast<int>(r); // OK, n = 21 std::cout << n << '\n'; // affiche 21 } |
(depuis C++11) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Une énumération peut être initialisée à partir d'un entier sans cast, en utilisant l'initialisation par liste , si toutes les conditions suivantes sont remplies :
Cela permet d'introduire de nouveaux types entiers (par exemple
enum byte : unsigned char {}; // byte is a new integer type; see also std::byte (C++17) byte b{42}; // OK as of C++17 (direct-list-initialization) byte c = {42}; // error byte d = byte{42}; // OK as of C++17; same value as b byte e{-1}; // error struct A { byte b; }; A a1 = {{42}}; // error (copy-list-initialization of a constructor parameter) A a2 = {byte{42}}; // OK as of C++17 void f(byte); f({42}); // error (copy-list-initialization of a function parameter) enum class Handle : std::uint32_t { Invalid = 0 }; Handle h{42}; // OK as of C++17 |
(depuis C++17) |
using enum déclaration
enum E { x }; void f() { int E; using enum E; // OK } using F = E; using enum F; // OK template<class T> using EE = T; void g() { using enum EE<E>; // OK } Une déclaration using enum introduit les noms des énumérateurs de l'énumération nommée comme si par une using déclaration pour chaque énumérateur. Lorsqu'elle est dans la portée d'une classe, une déclaration using enum ajoute les énumérateurs de l'énumération nommée comme membres à la portée, les rendant accessibles pour la recherche de membres. enum class fruit { orange, apple }; struct S { using enum fruit; // OK: introduit orange et apple dans S }; void f() { S s; s.orange; // OK: nomme fruit::orange S::orange; // OK: nomme fruit::orange } Deux déclarations using enum qui introduisent deux énumérateurs du même nom entrent en conflit. enum class fruit { orange, apple }; enum class color { red, orange }; void f() { using enum fruit; // OK // using enum color; // erreur: color::orange et fruit::orange entrent en conflit } |
(depuis C++20) | ||||||||||||||||||||||||||
Notes
Les valeurs de type énumération non délimitée peuvent être promues ou converties en types entiers :
enum color { red, yellow, green = 20, blue }; color col = red; int n = blue; // n == 21
Les valeurs des types entiers, à virgule flottante et d'énumération peuvent être converties en n'importe quel type d'énumération en utilisant
static_cast
. Notez que la valeur après une telle conversion peut ne pas nécessairement correspondre à l'un des énumérateurs nommés définis pour l'énumération :
enum access_t { read = 1, write = 2, exec = 4 }; // énumérateurs : 1, 2, 4 plage : 0..7 access_t rwe = static_cast<access_t>(7); assert((rwe & read) && (rwe & write) && (rwe & exec)); access_t x = static_cast<access_t>(8.0); // comportement indéfini depuis CWG 1766 access_t y = static_cast<access_t>(8); // comportement indéfini depuis CWG 1766 enum foo { a = 0, b = UINT_MAX }; // plage : [0, UINT_MAX] foo x = foo(-1); // comportement indéfini depuis CWG 1766, // même si le type sous-jacent de foo est unsigned int
| Macro de test de fonctionnalité | Valeur | Std | Fonctionnalité |
|---|---|---|---|
__cpp_enumerator_attributes
|
201411L
|
(C++17) | Attributs pour les énumérateurs |
__cpp_using_enum
|
201907L
|
(C++20) |
using enum
|
Mots-clés
Exemple
#include <cstdint> #include <iostream> // enum qui prend 16 bits enum smallenum: std::int16_t { a, b, c }; // color peut être red (valeur 0), yellow (valeur 1), green (valeur 20), ou blue (valeur 21) enum color { red, yellow, green = 20, blue }; // altitude peut être altitude::high ou altitude::low enum class altitude: char { high = 'h', low = 'l', // virgule finale uniquement autorisée après CWG 518 }; // la constante d est 0, la constante e est 1, la constante f est 3 enum { d, e, f = e + 2 }; // les types énumération (scopés et non scopés) peuvent avoir des opérateurs surchargés std::ostream& operator<<(std::ostream& os, color c) { switch(c) { case red : os << "red"; break; case yellow: os << "yellow"; break; case green : os << "green"; break; case blue : os << "blue"; break; default : os.setstate(std::ios_base::failbit); } return os; } std::ostream& operator<<(std::ostream& os, altitude al) { return os << static_cast<char>(al); } // L'énumération scopée (C++11) peut être partiellement émulée dans les révisions antérieures de C++ : enum struct E11 { x, y }; // depuis C++11 struct E98 { enum { x, y }; }; // OK en pré-C++11 namespace N98 { enum { x, y }; } // OK en pré-C++11 struct S98 { static const int x = 0, y = 1; }; // OK en pré-C++11 void emu() { std::cout << (static_cast<int>(E11::y) + E98::y + N98::y + S98::y) << '\n'; // 4 } namespace cxx20 { enum class long_long_long_name { x, y }; void using_enum_demo() { std::cout << "C++20 `using enum`: __cpp_using_enum == "; switch (auto rnd = []{return long_long_long_name::x;}; rnd()) { #if defined(__cpp_using_enum) using enum long_long_long_name; case x: std::cout << __cpp_using_enum << "; x\n"; break; case y: std::cout << __cpp_using_enum << "; y\n"; break; #else case long_long_long_name::x: std::cout << "?; x\n"; break; case long_long_long_name::y: std::cout << "?; y\n"; break; #endif } } } int main() { color col = red; altitude a; a = altitude::low; std::cout << "col = " << col << '\n' << "a = " << a << '\n' << "f = " << f << '\n'; cxx20::using_enum_demo(); }
Sortie possible :
col = rouge a = l f = 3 C++20 `using enum`: __cpp_using_enum == 201907; x
Rapports de défauts
Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.
| DR | Appliqué à | Comportement tel que publié | Comportement correct |
|---|---|---|---|
| CWG 377 | C++98 |
le comportement n'était pas spécifié lorsqu'aucun type
entier ne peut représenter toutes les valeurs d'énumérateur |
l'énumération est mal
formée dans ce cas |
| CWG 518 | C++98 | une virgule finale n'était pas autorisée après la liste d'énumérateurs | autorisée |
| CWG 1514 | C++11 |
une redéfinition d'énumération avec type sous-jacent fixe
pouvait être analysée comme un champ de bits dans une déclaration de membre de classe |
toujours analysée comme une redéfinition |
| CWG 1638 | C++11 |
la grammaire de déclaration d'énumération opaque
interdisait l'utilisation pour les spécialisations de template |
spécificateur de nom imbriqué
autorisé |
| CWG 1766 | C++98 |
convertir une valeur hors plage en énumération
sans type sous-jacent fixe avait un résultat non spécifié |
le comportement est indéfini |
| CWG 1966 | C++11 |
la résolution de
CWG issue 1514
faisait que le
:
d'une expression conditionnelle faisait partie de enum-base |
appliquer la résolution uniquement aux
spécificateurs de déclaration de membre |
| CWG 2156 | C++11 |
les définitions d'énumération pouvaient définir
des types d'énumération via des using-declarations |
interdit |
| CWG 2157 | C++11 |
la résolution de
CWG issue 1966
ne
couvrait pas les noms d'énumération qualifiés |
couvert |
| CWG 2530 | C++98 |
une liste d'énumérateurs pouvait contenir plusieurs
énumérateurs avec le même identifiant |
interdit |
| CWG 2590 | C++98 |
la taille, la représentation des valeurs et les exigences d'alignement
d'une énumération ne dépendaient pas de son type sous-jacent |
tous sont identiques à
ceux du type sous-jacent |
| CWG 2621 | C++20 |
la recherche de nom d'énumération utilisée dans
using enum les déclarations n'était pas claire |
clarifiée |
| CWG 2877 | C++20 |
la recherche de nom d'énumération utilisée dans
using enum les déclarations n'était pas uniquement de type |
rendue uniquement de type |
Références
- Norme C++23 (ISO/CEI 14882:2024) :
-
- 9.7.1 Déclarations d'énumération [dcl.enum]
- Norme C++20 (ISO/CEI 14882:2020) :
-
- 9.7.1 Déclarations d'énumération [dcl.enum]
- Norme C++17 (ISO/IEC 14882:2017) :
-
- 10.2 Déclarations d'énumération [dcl.enum]
- Norme C++14 (ISO/IEC 14882:2014) :
-
- 7.2 Déclarations d'énumération [dcl.enum]
- Norme C++11 (ISO/IEC 14882:2011) :
-
- 7.2 Déclarations d'énumération [dcl.enum]
- Norme C++03 (ISO/CEI 14882:2003) :
-
- 7.2 Déclarations d'énumération [dcl.enum]
- Norme C++98 (ISO/CEI 14882:1998) :
-
- 7.2 Déclarations d'énumération [dcl.enum]
Voir aussi
|
(C++11)
|
vérifie si un type est un type énumération
(modèle de classe) |
|
(C++23)
|
vérifie si un type est un type énumération délimitée
(modèle de classe) |
|
(C++11)
|
obtient le type entier sous-jacent pour un type énumération donné
(modèle de classe) |
|
(C++23)
|
convertit une énumération en son type sous-jacent
(modèle de fonction) |
|
Documentation C
pour
Énumérations
|
|