Объявление перечисления
Перечисление представляет собой особый тип, значение которого ограничено диапазоном (подробности смотрите ниже), который может включать несколько явно именованных констант ("перечислителей").
Значения констант являются значениями целочисленного типа, известного как базовый тип перечисления. Перечисление имеет те же размер, представление значения и требования к выравниванию в качестве базового типа. Кроме того, каждое значение перечисления имеет то же представление, что и соответствующее значение базового типа.
Перечисление (повторно) объявляется с использованием следующего синтаксиса:
ключевое-слово-enum атрибуты(необязательно) имя-заголовка-перечисления(необязательно) базовый-тип(необязательно)(C++11) { список-перечислителей(необязательно) }
|
(1) | ||||||||
ключевое-слово-enum атрибуты(необязательно) имя-заголовка-перечисления(необязательно) базовый-тип(необязательно){ список-перечислителей , }
|
(2) | ||||||||
ключевое-слово-enum атрибуты(необязательно) имя-заголовка-перечисления базовый-тип(необязательно) ;
|
(3) | (начиная с C++11) | |||||||
ключевое-слово-enum | — | одно из enum , enum class (начиная с C++11) или enum struct (начиная с C++11)
| ||||
атрибуты(C++11) | — | необязательная последовательность из любого количества атрибутов | ||||
имя-заголовка-перечисления | — |
| ||||
базовый-тип(C++11) | — | двоеточие (: ), за которым следует последовательность-спецификатора-типа, который является встроенным типом (если он cv-квалифицирован, квалификаторы игнорируются), который будет служить фиксированным базовым типом для этого типа перечисления
| ||||
список-перечислителей | — | список определений перечислителей, разделённых запятыми, каждый из которых является либо просто уникальным идентификатором, который становится именем перечислителя, либо уникальным идентификатором с инициализатором: идентификато = constexpr. В любом случае за идентификатором может следовать необязательная последовательность спецификаторов атрибута. (начиная с C++17)
|
Есть два разных вида перечислений: перечисление без области видимости (объявляется с помощью ключевого-слова enum
) и перечисление с областью видимости (объявляется с помощью ключевого-слова enum class
или enum struct
).
Содержание |
[править] Перечисление без области видимости
enum имя(необязательно) { перечислитель = constexpr , перечислитель = constexpr , ... }
|
(1) | ||||||||
enum имя(необязательно) : тип { перечислитель = constexpr , перечислитель = constexpr , ... }
|
(2) | (начиная с C++11) | |||||||
enum имя : тип ;
|
(3) | (начиная с C++11) | |||||||
int
, если только значение перечисления не может поместиться в int
или unsigned int
. Если список-перечислителей пуст, базовый тип выглядит так, как если бы в перечислении был один перечислитель со значением 0. Если ни один целочисленный тип не может представить все значения перечислителя, перечисление некорректно).Каждый перечислитель становится именованной константой типа перечисления (то есть, именем), видимой во включающей области видимости и может использоваться всякий раз, когда требуются константы.
Каждый перечислитель связан со значением базового типа. Когда инициализаторы предоставляются в списке-перечислителей, значения перечислителей определяются этими инициализаторами. Если первый перечислитель не имеет инициализатора, связанное значение равно нулю. Для любого другого перечислителя, определение которого не имеет инициализатора, связанным значением является значение предыдущего перечислителя плюс один.
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
Значения типа перечисления с незаданной областью видимости неявно преобразуются в целочисленные типы. Если базовый тип не фиксирован, значение может быть преобразовано в первый тип из следующего списка, способный содержать весь диапазон значений: int, unsigned int, long, unsigned long, long long или unsigned long long, расширенные целочисленные типы с более высоким приоритетом преобразования (в порядке ранжирования, предпочтительно со знаком перед беззнаковым) (начиная с C++11). Если базовый тип фиксирован, значения могут преобразовываться в продвинутый базовый тип (предпочтительнее в разрешении перегрузки), который затем может быть продвинут.
enum color { red, yellow, green = 20, blue }; color col = red; int n = blue; // n == 21
Значения целочисленных, с плавающей запятой и перечислимых типов можно преобразовать с помощью static_cast
или explicit cast, в любой тип перечисления. Если базовый тип не фиксирован, а исходное значение выходит за пределы допустимого диапазона, результат будет невыясненный (до C++17)неопределённый (начиная с C++17). (Исходное значение, преобразованное в базовый тип перечисления, если оно имеет плавающую точку, находится в диапазоне, если оно помещается в битовое поле наименьшего размера, достаточно большое, чтобы содержать все перечислители целевого перечисления.) Иначе, результат будет таким же, как результат неявного преобразования в базовый тип.
Обратите внимание, что значение после такого преобразования не обязательно совпадёт с любым из именованных перечислителей, определённых для перечисления.
enum access_t { read = 1, write = 2, exec = 4 }; // перечислители: 1, 2, 4 диапазон: 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); // неопределённое поведение, начиная с CWG 1766 access_t y = static_cast<access_t>(8); // неопределённое поведение, начиная с CWG 1766 enum foo { a = 0, b = UINT_MAX }; // диапазон: [0, UINT_MAX] foo x= foo(-1); // неопределённое поведение, начиная с CWG 1766, даже если базовый тип foo // unsigned int
Имя перечисления без области видимости можно опустить: такое объявление только вводит перечислители в охватывающую область видимости:
enum { a, b, c = 0, d = a + 2 }; // определяет 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; // разрешено только в C++11 и новее int b = X::left; int c = x.left; int d = p->left;
[править] Перечисления с областью видимости
1) объявляет тип перечисления с ограниченной областью видимости, чей базовый тип это int (ключевые слова class и struct в точности эквивалентны)
2) объявляет тип перечисления с ограниченной областью видимости, чей базовый тип это тип
3) объявление непрозрачного перечисления с ограниченной областью видимости, чей базовый тип это int
4) объявление непрозрачного перечисления с ограниченной областью видимости, чей базовый тип это тип
Каждый перечислитель становится именованной константой типа перечисления (то есть именем), которая содержится в области видимости перечисления, и к которой можно получить доступ с помощью оператора разрешения области видимости. Неявные преобразования значений перечислителя с ограниченной областью видимости в целочисленные типы отсутствуют, хотя для получения числового значения перечислителя можно использовать Запустить этот код #include <iostream> int main() { enum class Color { red, green = 20, blue }; Color r = Color::blue; switch(r) { case Color::red : std::cout << "красный\n"; break; case Color::green: std::cout << "зелёный\n"; break; case Color::blue : std::cout << "синий\n"; break; } // int n = r; // ошибка: нет неявного преобразования из перечисления // с областью видимости в int int n = static_cast<int>(r); // OK, n = 21 std::cout << n << '\n'; // выводит 21 } |
(начиная с C++11) |
Перечисление может быть инициализировано целым числом без приведения, используя список инициализации, если все следующие условия верны:
Это позволяет вводить новые целочисленные типы (например, enum byte : unsigned char {}; // byte, это новый целочисленный тип; // смотрите также std::byte (C++17) byte b { 42 }; // OK в C++17 (прямая инициализация списком) byte c = { 42 }; // ошибка byte d = byte{ 42 }; // OK в C++17; то же значение, что и b byte e { -1 }; // ошибка struct A { byte b; }; A a1 = {{42}}; // ошибка (инициализация параметра конструктора копирования списком) A a2 = {byte{42}}; // OK в C++17 void f(byte); f({ 42 }); // ошибка (инициализация параметра конструктора копирования списком) enum class Handle : std::uint32_t { Invalid = 0 }; Handle h { 42 }; // OK в C++17 |
(начиная с C++17) |
Объявление using enum
спецификатор-вложенного-имени(необязательно) имя должно именовать не зависимый тип перечисления. Объявления перечисления находятся с помощью обычного квалифицированного или неквалифицированного поиска, в зависимости от того, присутствует ли спецификатор-вложенного-имени. Объявление using enum вводит имена перечислителей именованного перечисления, как если бы это было сделано объявлением using для каждого перечислителя. Находясь в области видимости класса, объявление using enum добавляет перечислители именованного перечисления в качестве элементов в область видимости, делая их доступными для поиска элементов. enum class fruit { orange, apple }; struct S { using enum fruit; // OK: вводит orange и apple в S }; void f() { S s; s.orange; // OK: именует fruit::orange S::orange; // OK: именует fruit::orange } Два объявления using enum, которые вводят два перечислителя с одинаковым именем, конфликтуют. enum class fruit { orange, apple }; enum class color { red, orange }; void f() { using enum fruit; // OK // using enum color; // ошибка: color::orange и fruit::orange конфликтуют } |
(начиная с C++20) |
[править] Примечание
Макрос тест функциональности | Значение | Стандарт | Комментарий |
---|---|---|---|
__cpp_enumerator_attributes |
201411L | (C++17) | Атрибуты для перечислителей |
__cpp_using_enum |
201907L | (C++20) | using enum
|
[править] Пример
#include <cstdint> #include <iostream> // перечисление, которое занимает 16 бит enum smallenum: std::int16_t { a, b, c }; // color может быть red (значение 0), yellow (значение 1), green (значение 20) // или blue (значение 21) enum color { red, yellow, green = 20, blue }; // altitude может быть altitude::high или altitude::low enum class altitude: char { high='h', low='l', // замыкающая запятая разрешена только после CWG 518 }; // константа d равна 0, константа e равна 1, константа f равна 3 enum { d, e, f = e + 2 }; //типы перечислений (как с ограниченной, так и с неограниченной областями видимости) //могут иметь перегруженные операторы std::ostream& operator<<(std::ostream& os, color c) { switch(c) { case red : os << "красный"; break; case yellow: os << "жёлтый"; break; case green : os << "зелёный"; break; case blue : os << "синий"; 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 }; void using_enum_demo() { std::cout << "`using enum` в C++20: __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 `using enum` в C++20: __cpp_using_enum == 201907; x
[править] Отчёты о дефектах
Следующие изменения поведения были применены с обратной силой к ранее опубликованным стандартам C++:
Номер | Применён | Поведение в стандарте | Корректное поведение |
---|---|---|---|
CWG 377 | C++98 | поведение было неопределённым, когда ни один целочисленный тип не мог представить все значения перечислителя |
в этом случае перечисление некорректно |
CWG 518 | C++98 | после списка перечислителей не допускается замыкающая запятая | допускается |
CWG 1638 | C++14 | грамматика объявления непрозрачного перечисления
запрещает его использование для специализации шаблонов |
спецификаторы-вложенного-имени разрешены |
CWG 1766 | C++98 | приведение значения вне диапазона к перечислению без фиксированного базового типа имело неопределённый результат |
поведение не определено |
CWG 1966 | C++11 | разрешение CWG проблема 1514 сделало : частью условноговыражения базовый-тип |
применять разрешение только к спецификаторам объявления элемента |
CWG 2156 | C++11 | определения перечисления могут определять типы перечисления с помощью using-объявлений |
запрещено |
CWG 2157 | C++11 | разрешение CWG проблема 1966 не распространялось на полные имена перечислений |
распространяется |
CWG 2530 | C++98 | список перечислителей может содержать несколько перечислителей с одним и тем же идентификатором |
запрещено |
CWG 2590 | C++98 | требования к размеру, представлению значений и выравниванию перечисления не зависели от его базового типа |
все они идентичны базовым типам |
CWG 2621 | C++20 | было неясно, как имена перечислений находятся в объявлениях using-enum |
они находятся с помощью обычного поиска имени |
[править] Смотрите также
(C++11) |
проверяет, является ли тип типом перечисления (шаблон класса) |
(C++23) |
проверяет, является ли тип типом перечисления с ограниченной областью видимости (шаблон класса) |
(C++11) |
получает базовый целочисленный тип для данного типа перечисления (шаблон класса) |
(C++23) |
преобразует перечисление в его базовый тип (шаблон функции) |
Документация C по Перечисления
|