Difference between revisions of "cpp/language/enum"
(hold on, how was that redundant? If anything, static_cast and implicit_conversions should link here as this page had way more content) |
(Removed the wordings of conversion details and updated the link to the conversion pages. Also moved the conversion-related descriptions and examples to “Notes” section since the exlicit conversion to enumeration types includes scoped enumeration types) |
||
Line 70: | Line 70: | ||
{{source|1=enum Foo { a, b, c = 10, d, e = 1, f, g = f + c }; | {{source|1=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}} | //a = 0, b = 1, c = 10, d = 11, e = 1, f = 2, g = 12}} | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
The {{spar|name}} of an unscoped enumeration may be omitted: such declaration only introduces the enumerators into the enclosing scope: | The {{spar|name}} of an unscoped enumeration may be omitted: such declaration only introduces the enumerators into the enclosing scope: | ||
Line 255: | Line 232: | ||
===Notes=== | ===Notes=== | ||
+ | Values of unscoped enumeration type can be {{rlp|implicit conversion#Promotion from enumeration types|promoted}} or {{rlp|implicit conversion#Integral conversions|converted}} to integral types: | ||
+ | {{source|1= | ||
+ | enum color { red, yellow, green = 20, blue }; | ||
+ | color col = red; | ||
+ | int n = blue; // n == 21 | ||
+ | }} | ||
+ | |||
+ | Values of integer, floating-point, and enumeration types can be converted to any enumeration type by using {{rlpt|static_cast}}. Note that the value after such conversion may not necessarily equal any of the named enumerators defined for the enumeration: | ||
+ | {{source|1= | ||
+ | enum access_t { read = 1, write = 2, exec = 4 }; // enumerators: 1, 2, 4 range: 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); // undefined behavior since CWG 1766 | ||
+ | access_t y = static_cast<access_t>(8); // undefined behavior since CWG 1766 | ||
+ | |||
+ | enum foo { a = 0, b = UINT_MAX }; // range: [0, UINT_MAX] | ||
+ | foo x = foo(-1); // undefined behavior since CWG 1766, | ||
+ | // even if foo's underlying type is unsigned int | ||
+ | }} | ||
+ | |||
{{ftm begin|core=1|std=1|comment=1}} | {{ftm begin|core=1|std=1|comment=1}} | ||
{{ftm|std=C++17|value=201411L|__cpp_enumerator_attributes|{{rlp|attributes|Attributes}} for enumerators}} | {{ftm|std=C++17|value=201411L|__cpp_enumerator_attributes|{{rlp|attributes|Attributes}} for enumerators}} |
Revision as of 19:35, 27 November 2023
An enumeration is a distinct type whose value is restricted to a range of values (see below for details), which may include several explicitly named constants ("enumerators").
The values of the constants are values of an integral type known as the underlying type of the enumeration. An enumeration has the same size, value representation, and alignment requirements as its underlying type. Furthermore, each value of an enumeration has the same representation as the corresponding value of the underlying type.
An enumeration is (re)declared using the following syntax:
enum-key attr (optional) enum-head-name (optional) enum-base (optional){ enumerator-list (optional) }
|
(1) | ||||||||
enum-key attr (optional) enum-head-name (optional) enum-base (optional){ enumerator-list , }
|
(2) | ||||||||
enum-key attr (optional) enum-head-name enum-base (optional) ;
|
(3) | (since C++11) | |||||||
enum-key | - |
| ||||
attr | - | (since C++11) optional sequence of any number of attributes | ||||
enum-head-name | - |
| ||||
enum-base | - | (since C++11) colon (: ), followed by a type-specifier-seq that names an integral type (if it is cv-qualified, qualifications are ignored) that will serve as the fixed underlying type for this enumeration type
| ||||
enumerator-list | - | comma-separated list of enumerator definitions, each of which is either simply a unique identifier, which becomes the name of the enumerator, or a unique identifier with an initializer: identifier = constexpr. In either case, the identifier can be directly followed by an optional attribute specifier sequence.(since C++17)
|
There are two distinct kinds of enumerations: unscoped enumeration (declared with the enum-key enum
) and scoped enumeration (declared with the enum-key enum class
or enum struct
).
Contents |
Unscoped enumerations
enum name (optional) { enumerator = constexpr , enumerator = constexpr , ... }
|
(1) | ||||||||
enum name (optional) : type { enumerator = constexpr , enumerator = constexpr , ... }
|
(2) | (since C++11) | |||||||
enum name : type ;
|
(3) | (since C++11) | |||||||
Each enumerator becomes a named constant of the enumeration's type (that is, name), visible in the enclosing scope, and can be used whenever constants are required.
Each enumerator is associated with a value of the underlying type. When initializers are provided in the enumerator-list, the values of enumerators are defined by those initializers. If the first enumerator does not have an initializer, the associated value is zero. For any other enumerator whose definition does not have an initializer, the associated value is the value of the previous enumerator plus one.
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
The name of an unscoped enumeration may be omitted: such declaration only introduces the enumerators into the enclosing scope:
enum { a, b, c = 0, d = a + 2 }; // defines a = 0, b = 1, c = 0, d = 2
When an unscoped enumeration is a class member, its enumerators may be accessed using class member access operators .
and ->
:
struct X { enum direction { left = 'l', right = 'r' }; }; X x; X* p = &x; int a = X::direction::left; // allowed only in C++11 and later int b = X::left; int c = x.left; int d = p->left;
In the declaration specifiers of a member declaration, the sequence
is always parsed as a part of enumeration declaration: 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 } |
(since C++11) |
Enumeration name for linkage purposes
An unnamed enumeration that does not have a typedef name for linkage purposes and that has an enumerator is denoted, for linkage purposes, by its underlying type and its first enumerator; such an enumeration is said to have an enumerator as a name for linkage purposes.
Scoped enumerations
1) declares a scoped enumeration type whose underlying type is int (the keywords class and struct are exactly equivalent)
2) declares a scoped enumeration type whose underlying type is type
3) opaque enum declaration for a scoped enumeration whose underlying type is int
4) opaque enum declaration for a scoped enumeration whose underlying type is type
Each enumerator becomes a named constant of the enumeration's type (that is, name), which is contained within the scope of the enumeration, and can be accessed using scope resolution operator. There are no implicit conversions from the values of a scoped enumerator to integral types, although Run this 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; // error: no implicit conversion from scoped enum to int int n = static_cast<int>(r); // OK, n = 21 std::cout << n << '\n'; // prints 21 } |
(since C++11) |
An enumeration can be initialized from an integer without a cast, using list initialization, if all of the following are true:
This makes it possible to introduce new integer types (e.g. 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 |
(since C++17) |
Using-enum-declaration
nested-name-specifier (optional) name must name a non-dependent enumeration type. The enumeration declarations are found by ordinary qualified or unqualified lookup, depending on whether nested-name-specifier is present. A using-enum-declaration introduces the enumerator names of the named enumeration as if by a using-declaration for each enumerator. When in class scope, a using-enum-declaration adds the enumerators of the named enumeration as members to the scope, making them accessible for member lookup. enum class fruit { orange, apple }; struct S { using enum fruit; // OK: introduces orange and apple into S }; void f() { S s; s.orange; // OK: names fruit::orange S::orange; // OK: names fruit::orange } Two using-enum-declarations that introduce two enumerators of the same name conflict. enum class fruit { orange, apple }; enum class color { red, orange }; void f() { using enum fruit; // OK // using enum color; // error: color::orange and fruit::orange conflict } |
(since C++20) |
Notes
Values of unscoped enumeration type can be promoted or converted to integral types:
enum color { red, yellow, green = 20, blue }; color col = red; int n = blue; // n == 21
Values of integer, floating-point, and enumeration types can be converted to any enumeration type by using static_cast
. Note that the value after such conversion may not necessarily equal any of the named enumerators defined for the enumeration:
enum access_t { read = 1, write = 2, exec = 4 }; // enumerators: 1, 2, 4 range: 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); // undefined behavior since CWG 1766 access_t y = static_cast<access_t>(8); // undefined behavior since CWG 1766 enum foo { a = 0, b = UINT_MAX }; // range: [0, UINT_MAX] foo x = foo(-1); // undefined behavior since CWG 1766, // even if foo's underlying type is unsigned int
Feature-test macro | Value | Std | Feature |
---|---|---|---|
__cpp_enumerator_attributes |
201411L | (C++17) | Attributes for enumerators |
__cpp_using_enum |
201907L | (C++20) | using enum
|
Example
#include <cstdint> #include <iostream> // enum that takes 16 bits enum smallenum: std::int16_t { a, b, c }; // color may be red (value 0), yellow (value 1), green (value 20), or blue (value 21) enum color { red, yellow, green = 20, blue }; // altitude may be altitude::high or altitude::low enum class altitude: char { high = 'h', low = 'l', // trailing comma only allowed after CWG 518 }; // the constant d is 0, the constant e is 1, the constant f is 3 enum { d, e, f = e + 2 }; // enumeration types (both scoped and unscoped) can have overloaded operators 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); } // The scoped enum (C++11) can be partially emulated in earlier C++ revisions: enum struct E11 { x, y }; // since C++11 struct E98 { enum { x, y }; }; // OK in pre-C++11 namespace N98 { enum { x, y }; } // OK in pre-C++11 struct S98 { static const int x = 0, y = 1; }; // OK in pre-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(); }
Possible output:
col = red a = l f = 3 C++20 `using enum`: __cpp_using_enum == 201907; x
Defect reports
The following behavior-changing defect reports were applied retroactively to previously published C++ standards.
DR | Applied to | Behavior as published | Correct behavior |
---|---|---|---|
CWG 377 | C++98 | the behavior was unspecified when no integral type can represent all the enumerator values |
the enumeration is ill- formed in this case |
CWG 518 | C++98 | a trailing comma was not allowed after the enumerator list | allowed |
CWG 1514 | C++11 | a redefinition of enumeration with fixed underlying type could be parsed as a bit-field in a class member declaration |
always parsed as a redefinition |
CWG 1638 | C++11 | grammar of opaque enumeration declaration prohibited use for template specializations |
nested-name-specifier permitted |
CWG 1766 | C++98 | casting an out-of-range value to an enumeration without fixed underlying type had an unspecified result |
the behavior is undefined |
CWG 1966 | C++11 | the resolution of CWG issue 1514 made the : of a conditional expression part of enum-base |
only apply the resolution to member declaration specifiers |
CWG 2156 | C++11 | enum definitions could define enumeration types by using-declarations |
prohibited |
CWG 2157 | C++11 | the resolution of CWG issue 1966 did not cover qualified enumeration names |
covered |
CWG 2530 | C++98 | an enumerator list could contain multiple enumerators with the same identifier |
prohibited |
CWG 2590 | C++98 | the size, value representation and alignment requirements of an enumeration did not depend on its underlying type |
all of them are identical to those of the underlying type |
CWG 2621 | C++20 | it was unclear how the enumeration names are found in using-enum-declarations |
they are found using ordinary name lookup |
See also
(C++11) |
checks if a type is an enumeration type (class template) |
(C++23) |
checks if a type is a scoped enumeration type (class template) |
(C++11) |
obtains the underlying integer type for a given enumeration type (class template) |
(C++23) |
converts an enumeration to its underlying type (function template) |
C documentation for Enumerations
|