Spec-Zone.ru › C++

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

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

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

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

ключ-перечисления атрибут (необязательно) имя-заголовка-перечисления (необязательно) базовый-тип-перечисления (необязательно)
{ список-перечислителей (необязательно) }
(1)
ключ-перечисления атрибут (необязательно) имя-заголовка-перечисления (необязательно) базовый-тип-перечисления (необязательно)
{ список-перечислителей , }
(2)
ключ-перечисления атрибут (необязательно) имя-заголовка-перечисления базовый-тип-перечисления (необязательно) ; (3) (с C++11)
1) спецификатор-перечисления, который появляется в последовательности-спецификаторов-объявления синтаксиса объявления: определяет тип перечисления и его перечислители.
2) После списка-перечислителей может стоять запятая.
3) Объявление неявного перечисления: определяет тип перечисления, но не его перечислители: после этого объявления тип является полным типом, и его размер известен.
ключ-перечисления -

enum

(до C++11)

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

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

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

(до C++11)

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

(с 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)
1) Объявляет тип перечисления без области видимости, основной тип которого не фиксирован (в этом случае основной тип — это определяемый реализацией целочисленный тип, который может представлять все значения перечислителей; этот тип не больше int , если значение перечислителя не может поместиться в int или unsigned int. Если список-перечислителей пуст, основной тип такой же, как если бы перечисление имело один перечислитель со значением ​0​. Если ни один целочисленный тип не может представить все значения перечислителей, перечисление некорректно).
2) Объявляет тип перечисления без области видимости, основной тип которого фиксирован.
3) Объявление неявного перечисления без области видимости должно указывать имя и основной тип.

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

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;

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

имя-заголовка-перечисления :

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

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 имя { перечислитель = constexpr , перечислитель = constexpr , ... } (1)
enum struct|class имя : тип { перечислитель = constexpr , перечислитель = constexpr , ... } (2)
enum struct|class имя ; (3)
enum struct|class имя : тип ; (4)
1) объявляет тип перечисления со scope, основным типом которого является int (ключевые слова class и struct точно эквивалентны) 2) объявляет тип перечисления со scope, основным типом которого является тип 3) неявное объявление перечисления со scope, основным типом которого является int 4) неявное объявление перечисления со scope, основным типом которого является тип

Каждый перечислитель становится именованной константой типа перечисления (то есть имя), которая находится в области перечисления и может быть обращена с помощью оператора разрешения области видимости. Нет неявных преобразований из значений перечислителя со scope в целочисленные типы, хотя 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)

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

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

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

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 спецификатор-имени-вложенного-имени (необязательно) имя ; (с C++20)

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

Объявление 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
они находятся с помощью
обычного поиска имен

См. также

is_enum
(C++11)
проверяет, является ли тип перечислением
(шаблон класса)
is_scoped_enum
(C++23)
проверяет, является ли тип перечислением со scope
(шаблон класса)
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/w/cpp/language/enum

Spec-Zone.ru

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