Enumeration declaration

From cppreference.com
< cpp‎ | language
 
 
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function declaration
inline specifier
Exception specifications (until C++20)
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
decltype (C++11)
auto (C++11)
alignas (C++11)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Implicit conversions - Explicit conversions
static_cast - dynamic_cast
const_cast - reinterpret_cast
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous
 
 

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 is defined using the following syntax:

enum-key attr(optional) enum-name(optional) enum-base(optional)(C++11) { enumerator-list(optional) } (1)
enum-key attr(optional) enum-name enum-base(optional) ; (2) (since C++11)
1) enum-specifier, which appears in decl-specifier-seq of the declaration syntax: defines the enumeration type and its enumerators.
2) Opaque enum declaration: defines the enumeration type but not its enumerators: after this declaration, the type is a complete type and its size is known. Note: an explicit specialization declaration of a scoped enumeration member of a class template is the only case where nested-name-specifier appears before enum-name (since C++14)
enum-key - one of enum, enum class(since C++11), or enum struct(since C++11)
attr(C++11) - optional sequence of any number of attributes
enum-name - the name of the enumeration that's being declared. If present, and if this declaration is a re-declaration, it may be preceded by nested-name-specifier(since C++11): sequence of names and scope-resolution operators ::, ending with scope-resolution operator. The name can be omitted only in unscoped enumeration declarations
enum-base(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 an identifier, which becomes the name of the enumerator, or an 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).

Unscoped enumeration

enum name { enumerator = constexpr , enumerator = constexpr , ... } (1)
enum name : type { enumerator = constexpr , enumerator = constexpr , ... } (2) (since C++11)
enum name : type ; (3) (since C++11)
1) Declares an unscoped enumeration type whose underlying type is not fixed (in this case, the underlying type is an implementation-defined integral type that can represent all enumerator values; this type is not larger than int unless the value of an enumerator cannot fit in an int or unsigned int. If the enumerator-list is empty, the underlying type is as if the enumeration had a single enumerator with value 0).
2) Declares an unscoped enumeration type whose underlying type is fixed.
3) Opaque enum declaration for an unscoped enumeration must specify the underlying type.

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.

enum Color { red, green, blue };
Color r = red;
switch(r)
{
    case red  : std::cout << "red\n";   break;
    case green: std::cout << "green\n"; break;
    case blue : std::cout << "blue\n";  break;
}

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

Values of unscoped enumeration type are implicitly-convertible to integral types. If the underlying type is not fixed, the value is convertible to the first type from the following list able to hold their entire value range: int, unsigned int, long, unsigned long, long long, or unsigned long long. If the underlying type is fixed, the values can be converted to their promoted underlying type.

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 by static_cast or explicit cast, to any enumeration type. If the underlying type is not fixed and the source value is out of range, the result is unspecified (until C++17)undefined (since C++17). (The source value, as converted to the enumeration's underlying type if floating-point, is in range if it would fit in the smallest bit field large enough to hold all enumerators of the target enumeration.) Otherwise, the result is the same as the result of implicit conversion to the underlying type.

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 C++17
access_t y = static_cast<access_t>(8); // undefined behavior since C++17
 
enum foo { a = 0, b = UINT_MAX }; // range: [0, UINT_MAX]
foo x= foo(-1); // undefined behavior since C++17, even if foo's underlying type is unsigned int

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;

Scoped enumerations

enum struct|class name { enumerator = constexpr , enumerator = constexpr , ... } (1)
enum struct|class name : type { enumerator = constexpr , enumerator = constexpr , ... } (2)
enum struct|class name ; (3)
enum struct|class name : type ; (4)
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 static_cast may be used to obtain the numeric value of the enumerator.

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 scoped enum to int conversion
int n = static_cast<int>(r); // OK, n = 21
(since C++11)

Both scoped enumeration types and unscoped enumeration types whose underlying type is fixed can be initialized from an integer without a cast, using list initialization, if all of the following is true:

  • the initialization is direct-list-initialization
  • the initializer list has only a single element
  • the enumeration is either scoped or unscoped with underlying type fixed
  • the conversion is non-narrowing

This makes it possible to introduce new integer types (e.g. SafeInt) that enjoy the same existing calling conventions as their underlying integer types, even on ABIs that penalize passing/returning structures by value.

enum byte : unsigned char {}; // byte is a new integer type
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

using enum nested-name-specifier(optional) name ; (since C++20)

where nested-name-specifier(optional) name must not name a dependent type and must name an enumeration type.

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)

Example

#include <iostream>
#include <cstdint>
 
// 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', // C++11 allows the extra comma
}; 
 
// 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);
}
 
namespace cxx20
{
    enum class long_long_long_name { x, y };
 
    auto rnd = [] { return long_long_long_name::x; };
 
    void using_enum_demo()
    {
        std::cout << "C++20 using enum: ";
        switch (rnd())
        {
        #   if defined(__cpp_using_enum)
            using enum long_long_long_name;
            case x: std::cout << "x\n"; break;
            case y: std::cout << "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();
}

Output:

col = red
a = l
f = 3
C++20 using enum: 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 1638 C++14 grammar of opaque enum declaration prohibited use for template specializations nested-name-specifier permitted

See also