열거형
열거형 (Enumeration)
값이 특정 범위(아래에서 자세히 설명)로 제한되는 별개의 타입을 열거형이라고 해요. 여러 개의 이름 붙은 상수("열거자(enumerator)")를 포함할 수 있죠. 열거자들의 값은 열거형의 **기반 타입(underlying type)**이라고 불리는 정수 타입의 값이에요.
출처: cppreference
본문
열거형은 기반 타입과 같은 크기, 값 표현, 정렬 요구 사항을 가져요. 게다가 열거형의 각 값은 기반 타입의 대응하는 값과 같은 표현을 가져요.
열거형은 다음 문법으로 (재)선언돼요:
enum-key attr (optional) enum-head-name (optional) enum-base (optional)
{ enumerator-list (optional) } (1)
enum-key attr (optional) enum-head-name (optional) enum-base (optional)
{ enumerator-list , } (2)
enum-key attr (optional) enum-head-name enum-base (optional) ; (3) (C++11)
enum-key— C++11 이전에는enum만, C++11 이후에는enum,enum class,enum struct중 하나.attr— (C++11 이후) 임의 개수의 속성 시퀀스.enum-head-name— 선언되는 열거형의 이름. C++11 이전에는 생략 가능. C++11 이후에는 이름 앞에 중첩 이름 지정자(nested-name-specifier,::로 끝나는 이름 시퀀스)가 올 수 있고, 스코프 없는 비-불투명 열거형 선언에서만 생략 가능.enum-base— (C++11 이후) 콜론(:) 뒤에 오는 정수 타입을 가리키는 type-specifier-seq. 이 타입이 열거형의 고정 기반 타입이 돼요 (cv 한정은 무시됨).enumerator-list— 쉼표로 구분된 열거자 정의 목록. 각각은 고유 식별자, 또는 고유 식별자에 상수 표현식(identifier = constant-expression)을 붙인 것.
- enum-specifier — 선언 문법의 decl-specifier-seq에 나타나며 열거형 타입과 그 열거자들을 정의해요.
- enumerator-list 뒤에 후행 쉼표가 올 수 있어요.
- 불투명 열거형 선언 — 열거형 타입은 정의하지만 열거자는 정의하지 않아요. 이 선언 후에는 타입이 완전한 타입이 되고 크기를 알 수 있어요.
스코프 없는 열거형 (Unscoped enumerations)
enum name (optional) { enumerator = constant-expression , ... } (1)
enum name (optional) : type { enumerator = constant-expression , ... } (2) (C++11)
enum name : type ; (3) (C++11)
- 기반 타입이 고정되지 않은 스코프 없는 열거형을 선언해요. 이 경우 기반 타입은 모든 열거자 값을 표현할 수 있는 구현 정의 정수 타입이에요. 이 타입은 열거자 값이
int나unsigned int에 못 들어가지 않으면int보다 크지 않아요. enumerator-list가 비어 있으면, 마치 값 0인 열거자가 하나 있는 것처럼 기반 타입이 정해져요. 모든 열거자 값을 표현할 정수 타입이 없다면 열거형은 ill-formed예요. - 기반 타입이 고정된 스코프 없는 열거형을 선언해요.
- 스코프 없는 열거형의 불투명 선언은 이름과 기반 타입을 모두 지정해야 해요.
각 열거자는 열거형 타입의 이름 붙은 상수가 되어 둘러싼 스코프에 보이게 되고, 상수가 필요한 곳이면 어디서든 쓸 수 있어요.
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에 =가 주어지면 그 연관 상수 표현식으로 값이 정해져요. 첫 번째 열거자에 =가 없으면 연관 값은 0이에요. 다른 열거자에 =가 없으면 이전 열거자의 값 +1이 돼요.
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;
(C++11 이후) 멤버 선언의 선언 지정자 안에서, 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
}
링크를 위한 열거형 이름
링크 목적의 typedef 이름이 없고 열거자가 하나 이상 있는 이름 없는 열거형은, 링크 목적상 기반 타입과 첫 번째 열거자로 표기돼요. 이런 열거형은 "링크 목적의 이름으로 열거자를 가진다"고 해요.
스코프 있는 열거형 (Scoped enumerations)
enum struct|class name { enumerator = constant-expression , ... } (1)
enum struct|class name : type { enumerator = constant-expression , ... } (2)
enum struct|class name ; (3)
enum struct|class name : type ; (4)
class와struct키워드는 정확히 동등해요.
- 기반 타입이
int인 스코프 있는 열거형을 선언해요. - 기반 타입이
type인 스코프 있는 열거형을 선언해요. - 기반 타입이
int인 스코프 있는 열거형의 불투명 선언. - 기반 타입이
type인 스코프 있는 열거형의 불투명 선언.
각 열거자는 열거형 타입(즉 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 이후)
열거형은 리스트 초기화를 통해 캐스트 없이 정수로부터 초기화될 수 있어요. 다음 조건을 모두 만족할 때 가능해요:
- **직접 리스트 초기화(direct-list-initialization)**여야 함.
- 초기화 목록에 단 하나의 요소만 있어야 함.
- 열거형이 스코프 있는 것이거나, 기반 타입이 고정된 스코프 없는 것이어야 함.
- 변환이 **비-내로잉(non-narrowing)**이어야 함.
이 덕분에 기반 정수 타입과 같은 기존 호출 규약을 누리는 새 정수 타입(예: 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)
선언자는 열거형 타입을 가리키는 (가능하면 한정된) 식별자 또는 단순 템플릿 식별자(C++26 이후에는 splice 타입 지정자)예요. 선언자는 비-의존적인 열거형 타입을 가리켜야 해요. 열거형 선언은 선언자가 한정인지에 따라 타입 전용(type-only) 보통의 한정/비한정 조회로 찾아요.
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: 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 이후)
Notes
스코프 없는 열거형 타입의 값은 정수 타입으로 승격되거나 변환될 수 있어요:
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
예제
여기서 확인할 것: 기반 타입을 지정한 16비트 열거형, 값이 0부터 시작하지 않는 스코프 있는/없는 열거형, 이름 없는 열거형, 그리고 열거형 타입에 연산자를 오버로딩하는 패턴을 보는 코드예요.
#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
결함 보고 (Defect reports)
다음의 동작 변경 결함 보고는 이전에 발표된 C++ 표준에 소급 적용됐어요.
| DR | 적용 대상 | 발표 당시 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 377 | C++98 | 모든 열거자 값을 표현할 정수 타입이 없을 때 동작이 미지정이었음 | 이 경우 열거형은 ill-formed |
| CWG 518 | C++98 | enumerator-list 뒤에 후행 쉼표가 허용되지 않았음 | 허용됨 |
| 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 | enumerator-list가 같은 식별자의 여러 열거자를 포함할 수 있었음 | 금지됨 |
| CWG 2590 | C++98 | 열거형의 크기, 값 표현, 정렬 요구 사항이 기반 타입에 의존하지 않았음 | 모두 기반 타입과 동일함 |
| CWG 2621 | C++20 | using enum 선언에 사용된 열거형 이름 조회가 불명확했음 |
명확해짐 |
| CWG 2877 | C++20 | using enum 선언에 사용된 열거형 이름 조회가 type-only가 아니었음 |
type-only가 됨 |
참고 문헌
- C++23 표준 (ISO/IEC 14882:2024): 9.7.1 Enumeration declarations [dcl.enum]
- C++20 표준 (ISO/IEC 14882:2020): 9.7.1 Enumeration declarations [dcl.enum]
- C++17 표준 (ISO/IEC 14882:2017): 10.2 Enumeration declarations [dcl.enum]
- C++14 표준 (ISO/IEC 14882:2014): 7.2 Enumeration declarations [dcl.enum]
- C++11 표준 (ISO/IEC 14882:2011): 7.2 Enumeration declarations [dcl.enum]
- C++03 표준 (ISO/IEC 14882:2003): 7.2 Enumeration declarations [dcl.enum]
- C++98 표준 (ISO/IEC 14882:1998): 7.2 Enumeration declarations [dcl.enum]
더 알아보기 (Learn more)
- std::is_enum (C++11) — 타입이 열거형 타입인지 확인하는 클래스 템플릿
- std::is_scoped_enum (C++23)
- std::underlying_type (C++11) — 열거형 타입의 기반 정수 타입을 얻는 클래스 템플릿
- std::to_underlying (C++23) — 열거형을 기반 타입으로 변환하는 함수 템플릿
- 정교화된 타입 지정자 —
enum키워드로 열거형 타입을 참조하는 방법