Spec-Zone.ru › C++

Объявление перечисления

Перечисление — это отдельный тип, значения которого ограничены диапазоном (подробности см. ниже) и который может включать несколько явно именованных констант («элементов перечисления»).

Значения констант являются значениями целочисленного типа, известного как базовый тип перечисления. Перечисление имеет тот же размер, представление значения и требования к выравниванию, что и его базовый тип. Кроме того, каждое значение перечисления имеет то же представление, что и соответствующее значение базового типа.

Перечисление объявляется (повторно) с использованием следующего синтаксиса:

enum-key attr (необязательно) enum-head-name (необязательно) enum-base (необязательно)
{ enumerator-list (необязательно) }
(1)
enum-key attr (необязательно) enum-head-name (необязательно) enum-base (необязательно)
{ enumerator-list , }
(2)
enum-key attr (необязательно) enum-head-name enum-base (необязательно) ; (3) (начиная с C++11)
1) enum-specifier, который входит в decl-specifier-seq синтаксиса объявления: определяет тип перечисления и его элементы.
2) После enumerator-list может стоять завершающая запятая.
3) Неполное объявление перечисления: определяет тип перечисления, но не его элементы; после этого объявления тип является полным, и его размер известен.
enum-key -

enum

(до C++11)

одно из enum, enum class или enum struct

(начиная с C++11)
attr - (начиная с C++11) необязательная последовательность из любого количества атрибутов
enum-head-name -

имя объявляемого перечисления; его можно опустить.

(до C++11)

имя объявляемого перечисления, которому может предшествовать nested-name-specifier: последовательность имён и операторов разрешения области видимости ::, заканчивающаяся оператором разрешения области видимости. Его можно опустить только в объявлениях неполных перечислений без области видимости.
nested-name-specifier может присутствовать, только если имя перечисления указано и это объявление является повторным. В неполных объявлениях перечисления nested-name-specifier может стоять перед именем перечисления только в объявлениях явной специализации.
Если указан nested-name-specifier, enum-specifier не может ссылаться на перечисление, лишь унаследованное или введённое using-объявлением, а сам enum-specifier может находиться только в пространстве имён, охватывающем предыдущее объявление. В таких случаях nested-name-specifier не может начинаться со спецификатора decltype.

(начиная с C++11)
enum-base - (начиная с C++11) двоеточие (:), за которым следует type-specifier-seq, задающий целочисленный тип (если он имеет квалификаторы cv, они игнорируются), который будет фиксированным базовым типом данного типа перечисления
enumerator-list - список определений элементов перечисления, разделённых запятыми; каждое из них представляет собой либо просто уникальный identifier, который становится именем элемента перечисления, либо уникальный идентификатор с константным выражением: identifier = constant-expression. В обоих случаях за identifier может непосредственно следовать необязательная последовательность спецификаторов атрибутов.(начиная с C++17)

Существует два различных вида перечислений: перечисление без области видимости (объявляется с помощью enum-key enum) и перечисление с областью видимости (объявляется с помощью enum-key enum class или enum struct).

Перечисления без области видимости

enum name (необязательно) { enumerator = constant-expression , enumerator = constant-expression , ... } (1)
enum name (необязательно) : type { enumerator = constant-expression , enumerator = constant-expression , ... } (2) (начиная с C++11)
enum name : type ; (3) (начиная с C++11)
1) Объявляет тип перечисления без области видимости, базовый тип которого не фиксирован (в этом случае базовый тип — целочисленный тип, определяемый реализацией, способный представлять все значения элементов перечисления; этот тип не шире int, если только значение элемента перечисления не помещается в int или unsignedint. Если enumerator-list пуст, базовый тип выбирается так, как если бы у перечисления был единственный элемент со значением 0. Если ни один целочисленный тип не может представлять все значения элементов перечисления, программа некорректна).
2) Объявляет тип перечисления без области видимости с фиксированным базовым типом.
3) Неполное объявление перечисления без области видимости должно указывать имя и базовый тип.

Каждый enumerator становится именованной константой типа перечисления (то есть name), видимой в охватывающей области видимости и применимой везде, где требуются константы.

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;
}

Каждому элементу перечисления соответствует значение базового типа. Когда в enumerator-list указаны =, значения элементов перечисления задаются соответствующими constant-expression. Если у первого элемента перечисления отсутствует =, соответствующее значение равно нулю. Для любого другого элемента перечисления, в определении которого отсутствует =, соответствующее значение равно значению предыдущего элемента, увеличенному на единицу.

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

name перечисления без области видимости можно опустить: такое объявление вводит элементы перечисления только в охватывающую область видимости:

enum { a, b, c = 0, d = a + 2 }; // defines a = 0, b = 1, c = 0, d = 2

Если перечисление без области видимости является членом класса, к его элементам можно обращаться с помощью операторов доступа к членам класса . и ->:

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;

В спецификаторах объявления объявления члена последовательность enum

enum-head-name :

всегда разбирается как часть объявления перечисления:

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
}
(начиная с C++11)

Имя перечисления для целей связывания

Для целей связывания безымянное перечисление, у которого нет имени typedef для целей связывания, но есть элемент перечисления, обозначается своим базовым типом и первым элементом; считается, что такое перечисление имеет элемент в качестве имени для целей связывания.

Перечисления с областью видимости

enum struct|class name { enumerator = constant-expression , enumerator = constant-expression , ... } (1)
enum struct|class name : type { enumerator = constant-expression , enumerator = constant-expression , ... } (2)
enum struct|class name ; (3)
enum struct|class name : type ; (4)
1) объявляет тип перечисления с областью видимости, базовым типом которого является int (ключевые слова class и struct полностью эквивалентны) 2) объявляет тип перечисления с областью видимости, базовым типом которого является type 3) неполное объявление перечисления с областью видимости, базовым типом которого является int 4) неполное объявление перечисления с областью видимости, базовым типом которого является type

Каждый enumerator становится именованной константой типа перечисления (то есть name), которая входит в область видимости перечисления и доступна с помощью оператора разрешения области видимости. Неявных преобразований значений элементов перечисления с областью видимости в целочисленные типы нет, однако для получения числового значения элемента можно использовать static_cast.

#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
}
(начиная с C++11)

Перечисление можно инициализировать целым числом без приведения типов, используя инициализацию списком, если выполнены все следующие условия:

  • Используется прямая инициализация списком.
  • Список инициализаторов содержит только один элемент.
  • Перечисление является перечислением с областью видимости либо перечислением без области видимости с фиксированным базовым типом.
  • Преобразование не является сужающим.

Это позволяет вводить новые целочисленные типы (например, SafeInt), которые используют те же существующие соглашения о вызовах, что и соответствующие базовые целочисленные типы, даже в ABI, где передача и возврат структур по значению сопряжены с дополнительными издержками.

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
(начиная с C++17)

Объявление using enum

using enum using-enum-declarator ; (начиная с C++20)
declarator - (возможно, квалифицированный) идентификатор или простой идентификатор шаблона либо спецификатор типа splice(начиная с C++26), обозначающий тип перечисления


declarator должен именовать не-зависимый тип перечисления. Объявления перечислений находятся посредством обычного поиска имён только по типам — квалифицированного или неквалифицированного, в зависимости от того, является ли declarator квалифицированным.

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
}

Объявление usingenum вводит имена элементов указанного перечисления так, как если бы для каждого элемента было написано отдельное using-объявление. В области видимости класса объявление usingenum добавляет элементы указанного перечисления в область видимости в качестве членов, делая их доступными для поиска членов.

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
}

Два объявления usingenum, вводящие элементы с одинаковыми именами, конфликтуют.

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
}
(начиная с C++20)

Примечания

Значения перечисления без области видимости можно повысить или преобразовать к целочисленным типам:

enum color { red, yellow, green = 20, blue };
color col = red;
int n = blue; // n == 21

Значения целочисленных типов, типов с плавающей точкой и типов перечисления можно преобразовать в любой тип перечисления с помощью static_cast. Обратите внимание, что результат такого преобразования не обязательно совпадает со значением какого-либо именованного элемента данного перечисления:

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
Макрос проверки поддержки возможностей Значение Стандарт Возможность
__cpp_enumerator_attributes 201411L (C++17) Атрибуты для элементов перечисления
__cpp_using_enum 201907L (C++20) using enum

Ключевые слова

enum, struct, class, using

Пример

#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();
}

Возможный вывод:

col = red
a = l
f = 3
C++20 `using enum`: __cpp_using_enum == 201907; x

Отчёты об ошибках

Следующие отчёты об ошибках, изменяющие поведение, были применены задним числом к ранее опубликованным стандартам C++.

DR Применяется к Опубликованное поведение Корректное поведение
CWG 377 C++98 поведение не было определено, если ни один целочисленный
тип не мог представить все значения элементов перечисления
в этом случае перечисление является
некорректным
CWG 518 C++98 после списка элементов перечисления не допускалась завершающая запятая допускается
CWG 1514 C++11 переопределение перечисления с фиксированным базовым типом
могло разбираться как битовое поле в объявлении члена класса
всегда разбирается как переопределение
CWG 1638 C++11 грамматика неполного объявления перечисления
запрещала его использование для специализаций шаблонов
разрешён
nested-name-specifier
CWG 1766 C++98 приведение значения, выходящего за диапазон, к перечислению
без фиксированного базового типа давало неопределённый результат
поведение не определено
CWG 1966 C++11 в результате разрешения CWG 1514 :
условного выражения стало частью enum-base
применять это разрешение только к
спецификаторам объявления члена
CWG 2156 C++11 определения перечислений могли задавать
типы перечислений с помощью using-объявлений
запрещено
CWG 2157 C++11 разрешение CWG 1966
не охватывало квалифицированные имена перечислений
охвачено
CWG 2530 C++98 список элементов перечисления мог содержать несколько
элементов с одинаковым идентификатором
запрещено
CWG 2590 C++98 размер, представление значения и требования к выравниванию
перечисления не зависели от его базового типа
все эти характеристики идентичны
характеристикам базового типа
CWG 2621 C++20 поиск имени перечисления, используемый в
объявлениях usingenum, был определён неясно
уточнено
CWG 2877 C++20 поиск имени перечисления, используемый в
объявлениях usingenum, не был ограничен типами
ограничен типами

Ссылки

  • Стандарт C++23 (ISO/IEC 14882:2024):
    • 9.7.1 Объявления перечислений [dcl.enum]
  • Стандарт C++20 (ISO/IEC 14882:2020):
    • 9.7.1 Объявления перечислений [dcl.enum]
  • Стандарт C++17 (ISO/IEC 14882:2017):
    • 10.2 Объявления перечислений [dcl.enum]
  • Стандарт C++14 (ISO/IEC 14882:2014):
    • 7.2 Объявления перечислений [dcl.enum]
  • Стандарт C++11 (ISO/IEC 14882:2011):
    • 7.2 Объявления перечислений [dcl.enum]
  • Стандарт C++03 (ISO/IEC 14882:2003):
    • 7.2 Объявления перечислений [dcl.enum]
  • Стандарт C++98 (ISO/IEC 14882:1998):
    • 7.2 Объявления перечислений [dcl.enum]

См. также

is_enum
(C++11)
проверяет, является ли тип типом перечисления
(шаблон класса)
is_scoped_enum
(C++23)
проверяет, является ли тип типом перечисления с областью видимости
(шаблон класса)
is_enum_type
(C++26)
проверяет, является ли отражённый тип типом перечисления
(функция)
is_scoped_enum_type
(C++26)
проверяет, представляет ли отражение тип перечисления с областью видимости
(функция)
is_enumerator
(C++26)
проверяет, представляет ли отражение элемент перечисления
(функция)
underlying_type
(C++11)
получает базовый целочисленный тип для заданного типа перечисления
(шаблон класса)
to_underlying
(C++23)
преобразует перечисление к его базовому типу
(шаблон функции)
Документация по C о перечислениях

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/enum

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API