Объявление перечисления
Перечисление — это отдельный тип, значение которого ограничено диапазоном значений (подробнее см. ниже), который может включать несколько явно указанных констант («перечислители»).
Значения констант являются значениями целочисленного типа, известного как основной тип перечисления. Перечисление имеет тот же размер, представление значения и требования к выравниванию, что и его основной тип. Кроме того, каждое значение перечисления имеет такое же представление, что и соответствующее значение основного типа.
Перечисление объявляется (переобъявляется) с использованием следующего синтаксиса:
ключ-перечисления атрибут (необязательно) имя-заголовка-перечисления (необязательно) базовый-тип-перечисления (необязательно){ список-перечислителей (необязательно) } | (1) | |
ключ-перечисления атрибут (необязательно) имя-заголовка-перечисления (необязательно) базовый-тип-перечисления (необязательно){ список-перечислителей , } | (2) | |
ключ-перечисления атрибут (необязательно) имя-заголовка-перечисления базовый-тип-перечисления (необязательно) ; | (3) | (с C++11) |
| ключ-перечисления | - |
|
||||
| атрибут | - | (с C++11) необязательная последовательность любого количества атрибутов | ||||
| имя-заголовка-перечисления | - |
|
||||
| базовый-тип-перечисления | - | (с C++11) двоеточие (:), за которым следует последовательность-спецификаторов-типов, указывающая целочисленный тип (если он имеет квалификаторы, квалификаторы игнорируются), который будет служить фиксированным основным типом для этого типа перечисления |
||||
| список-перечислителей | - | список перечислителей, разделенный запятыми, каждый из которых представляет собой либо уникальный идентификатор, который становится именем перечислителя, либо уникальный идентификатор с инициализатором: идентификатор = 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 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;
}Каждый перечислитель связан со значением основного типа. Когда в списке-перечислителей предоставляются инициализаторы, значения перечислителей определяются этими инициализаторами. Если у первого перечислителя нет инициализатора, связанное значение равно нулю. Для любого другого перечислителя, у которого нет инициализатора, связанное значение равно значению предыдущего перечислителя плюс один.
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 }; // 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;| В спецификаторах объявления члена объявления, последовательность : всегда анализируется как часть объявления перечисления: 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 для целей связи и которое имеет перечислитель, обозначается, для целей хранения, своим основным типом и своим первым перечислителем; такое перечисление считается имеющим перечислитель в качестве имени для целей связи.
Перечисления с областью видимости
Каждый перечислитель становится именованной константой типа перечисления (то есть имя), которая находится в области перечисления и может быть обращена с помощью оператора разрешения области видимости. Нет неявных преобразований из значений перечислителя со scope в целочисленные типы, хотя #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) |
| Перечисление можно инициализировать целым числом без приведения типов, используя инициализацию списком, если выполняются все следующие условия:
Это позволяет вводить новые целочисленные типы (например, 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) |
Использование объявления enum
спецификатор-имени-вложенного-имени (необязательно) имя должно называть тип перечисления без зависимостей. Объявления перечислений находятся с помощью обычного квалифицированного или неквалифицированного поиска, в зависимости от наличия спецификатор-имени-вложенного-имени. Объявление using-enum вводит имена перечислителей названного перечисления как если бы с помощью объявления using для каждого перечислителя. При нахождении в области класса объявление using-enum добавляет перечислители названного перечисления в область видимости, делая их доступными для поиска членов. 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
}Два объявления using-enum, которые вводят два перечислителя с одинаковым именем, конфликтуют. 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) |
Примечания
Значения типа unscoped перечисления могут быть продвинуты или преобразованы в целочисленные типы:
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| Макросы проверки функций | Значение | Std | Функция |
|---|---|---|---|
__cpp_enumerator_attributes | 201411L | (C++17) | Атрибуты для перечислителей |
__cpp_using_enum | 201907L | (C++20) |
using enum |
Пример
#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 | грамматика неявного объявления перечисления запрещала использование для шаблонов специализаций | спецификатор-имени-вложенного-имени разрешено |
| 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 | не было ясно, как имена перечислений находятся в объявлениях using-enum | они находятся с помощью обычного поиска имен |
См. также
|
(C++11) | проверяет, является ли тип перечислением (шаблон класса) |
|
(C++23) | проверяет, является ли тип перечислением со scope (шаблон класса) |
|
(C++11) | получает базовый целочисленный тип для заданного типа перечисления (шаблон класса) |
|
(C++23) | преобразует перечисление в его базовый тип (шаблон функции) |
| Документация на C по Перечислениям | |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/enum