Enumeration declaration
Перечисление — это отдельный тип, значение которого ограничено диапазоном значений (подробности см. ниже), который может включать несколько явно именованных констант (" перечислителей ").
Значения констант являются значениями целочисленного типа, известного как базовый тип перечисления. Перечисление имеет тот же размер , представление значений и требования к выравниванию , что и его базовый тип. Кроме того, каждое значение перечисления имеет то же представление, что и соответствующее значение базового типа.
Перечисление объявляется (или переобъявляется) с использованием следующего синтаксиса:
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) | |||||||
| enum-key | - |
|
||||
| attr | - | (начиная с C++11) опциональная последовательность любого количества атрибутов | ||||
| enum-head-name | - |
|
||||
| enum-base | - |
(начиная с C++11)
двоеточие (
:
), за которым следует
type-specifier-seq
, указывающий целочисленный тип (если он cv-квалифицирован, квалификации игнорируются), который будет служить фиксированным базовым типом для данного типа перечисления
|
||||
| enumerator-list | - |
разделенный запятыми список определений перечислителей, каждый из которых представляет собой либо просто уникальный
идентификатор
, который становится именем перечислителя, либо уникальный идентификатор с константным выражением:
идентификатор
=
constant-expression
.
В любом случае, за
идентификатором
может непосредственно следовать опциональная
последовательность спецификаторов атрибутов
.
(начиная с C++17)
|
Существует два различных вида перечислений:
unscoped enumeration
(объявляется с помощью
enum-key
enum
) и
scoped enumeration
(объявляется с помощью
enum-key
enum class
или
enum struct
).
Содержание |
Неограниченные перечисления
enum
имя
(необязательно)
{
перечислитель
=
константное-выражение
,
перечислитель
=
константное-выражение
,
...
}
|
(1) | ||||||||
enum
имя
(необязательно)
:
тип
{
перечислитель
=
константное-выражение
,
перечислитель
=
константное-выражение
,
...
}
|
(2) | (начиная с C++11) | |||||||
enum
имя
:
тип
;
|
(3) | (начиная с C++11) | |||||||
Каждый enumerator становится именованной константой типа перечисления (то есть, name ), видимой в охватывающей области видимости, и может использоваться там, где требуются константы.
Каждый перечислитель связан со значением базового типа. Когда
=
указаны в
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
Имя неограниченного перечисления может быть опущено: такое объявление только вводит перечислители в охватывающую область видимости:
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;
|
В спецификаторах объявления при объявлении члена класса последовательность
всегда анализируется как часть объявления перечисления: struct S { enum E1 : int {}; enum E1 : int {}; // ошибка: переопределение перечисления, // НЕ анализируется как битовое поле нулевой длины типа enum E1 }; enum E2 { e1 }; void f() { false ? new enum E2 : int(); // OK: 'int' НЕ анализируется как базовый тип } |
(начиная с C++11) |
Имя перечисления для целей компоновки
Безымянное перечисление, которое не имеет typedef-имени для целей линковки и которое имеет перечислитель, обозначается, для целей линковки , своим базовым типом и первым перечислителем; такое перечисление считается имеющим перечислитель в качестве имени для целей линковки .
Областные перечисления
1)
объявляет тип ограниченного перечисления, базовым типом которого является
int
(ключевые слова
class
и
struct
полностью эквивалентны)
2)
объявляет тип ограниченного перечисления, базовым типом которого является
type
3)
непрозрачное объявление перечисления для ограниченного перечисления, базовым типом которого является
int
4)
непрозрачное объявление перечисления для ограниченного перечисления, базовым типом которого является
type
Каждый
enumerator
становится именованной константой типа перечисления (то есть
name
), которая содержится в области видимости перечисления и может быть доступна с использованием оператора разрешения области видимости. Не существует неявных преобразований из значений ограниченного перечислителя в целочисленные типы, хотя
Запустить код
#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; // ошибка: нет неявного преобразования из ограниченного enum в int int n = static_cast<int>(r); // OK, n = 21 std::cout << n << '\n'; // выводит 21 } |
(начиная с C++11) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Перечисление может быть инициализировано из целого числа без приведения типа, используя list initialization , если выполняются все следующие условия:
Это позволяет вводить новые целочисленные типы (например,
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 объявление
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 } Объявление 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 конфликтуют } |
(since 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 }; // перечислители: 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
| Макроопределение для проверки возможности | Значение | Стандарт | Возможность |
|---|---|---|---|
__cpp_enumerator_attributes
|
201411L
|
(C++17) | Атрибуты для перечислителей |
__cpp_using_enum
|
201907L
|
(C++20) |
using enum
|
Ключевые слова
Пример
#include <cstdint> #include <iostream> // enum, занимающий 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 << "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); } // Перечисление с областью видимости (C++11) может быть частично эмулировано в более ранних версиях C++: enum struct E11 { x, y }; // начиная с C++11 struct E98 { enum { x, y }; }; // OK в pre-C++11 namespace N98 { enum { x, y }; } // OK в pre-C++11 struct S98 { static const int x = 0, y = 1; }; // OK в 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 = красный 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 |
грамматика неполного объявления перечисления
запрещала использование для специализаций шаблонов |
квалификатор вложенного имени
разрешен |
| CWG 1766 | C++98 |
приведение значения вне диапазона к перечислению
без фиксированного базового типа имело неопределенный результат |
поведение является неопределенным |
| CWG 1966 | C++11 |
решение
проблемы CWG 1514
сделало
:
условного выражения частью enum-base |
применять решение только к
спецификаторам объявления членов |
| CWG 2156 | C++11 |
определения enum могли определять
типы перечислений с помощью using-объявлений |
запрещено |
| CWG 2157 | C++11 |
решение
проблемы CWG 1966
не
охватывало квалифицированные имена перечислений |
охвачено |
| CWG 2530 | C++98 |
список перечислителей мог содержать несколько
перечислителей с одинаковым идентификатором |
запрещено |
| CWG 2590 | C++98 |
размер, представление значения и требования к выравниванию
перечисления не зависели от его базового типа |
все они идентичны
таковым базового типа |
| CWG 2621 | C++20 |
поиск имени перечисления, используемый в
using enum объявлениях, был неясен |
прояснен |
| CWG 2877 | C++20 |
поиск имени перечисления, используемый в
using enum объявлениях, не был только по типу |
сделан только по типу |
Ссылки
- Стандарт 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]
Смотрите также
|
(C++11)
|
проверяет, является ли тип типом перечисления
(шаблон класса) |
|
(C++23)
|
проверяет, является ли тип типом ограниченного перечисления
(шаблон класса) |
|
(C++11)
|
получает базовый целочисленный тип для заданного типа перечисления
(шаблон класса) |
|
(C++23)
|
преобразует перечисление в его базовый тип
(шаблон функции) |
|
Документация C
для
Перечислений
|
|