열거형

열거형 (Enums)

D에서 열거형은 연관된 상수들을 묶어 고유한 타입을 부여하는 선언이에요. 명명된 열거형(named enum)은 새 타입을 만들고, 익명 열거형은 상수만 선언하며, 단일 멤버 문법으로 매니페스트 상수(manifest constant)를 만들 수도 있어요.

출처: Enums

본문

문법

EnumDeclaration:
    enum Identifier EnumBody
    enum Identifier : EnumBaseType EnumBody
    AnonymousEnumDeclaration

EnumBaseType:
    Type

EnumBody:
    { EnumMembers }
    ;

EnumMembers:
    EnumMember
    EnumMember ,
    EnumMember , EnumMembers

EnumMember:
    EnumMemberAttributesopt Identifier
    EnumMemberAttributesopt Identifier = AssignExpression

EnumMemberAttributes:
    EnumMemberAttribute
    EnumMemberAttribute EnumMemberAttributes

EnumMemberAttribute:
    DeprecatedAttribute
    UserDefinedAttribute
    @ disable
AnonymousEnumDeclaration:
    enum : EnumBaseType { EnumMembers }
    enum { AnonymousEnumMembers }

AnonymousEnumMembers:
    AnonymousEnumMember
    AnonymousEnumMember ,
    AnonymousEnumMember , AnonymousEnumMembers

AnonymousEnumMember:
    EnumMember
    EnumMemberAttributesopt Type Identifier = AssignExpression

열거형 선언은 상수들의 그룹을 정의할 때 써요.

Named Enums

명명된 열거형은 서로 연관된 상수들을 선언하고, 그 상수들에 고유한 타입을 부여해 하나로 묶는 데 사용해요. EnumMembers는 명명된 열거형의 스코프 안에서 선언돼요. 명명된 열거형은 새 타입을 선언하며, 모든 EnumMember는 그 타입을 가져요.

아래는 값이 X.A = 0, X.B = 1, X.C = 2인 새 타입 X를 정의하는 예시예요.

enum X { A, B, C }  // named enum

변수는 명명된 열거형 타입이 될 수 있어요. 이때 기본 초기화 값(default initializer)은 그 열거형 타입에서 처음 정의된 멤버예요.

enum X { A = 3, B, C }

X x;
assert(x == X.A);
x |= X.B;
assert(x & X.A);

명명된 열거형에 대한 이항 연산의 결과 타입은 여기에 정의돼 있어요.

EnumBaseType이 명시적으로 설정되지 않고 첫 번째 EnumMember에 AssignExpression이 있다면, EnumBaseType은 그 AssignExpression의 타입으로 정해져요. 그 외의 경우에는 타입 int로 기본 설정돼요.

명명된 열거형 멤버는 자신의 EnumBaseType으로 암시적으로 캐스팅될 수 있어요.

EnumBaseType 인스턴스는 명명된 열거형 타입으로 암시적으로 캐스팅될 수 없어요.

enum Foo { E }

Foo f;
int i;
i = f;           // OK
f = i;           // error
f = cast(Foo)i;  // OK
f = 0;           // error
f = Foo.E;       // OK

명명된 열거형 멤버는 자신의 Type을 선언할 수 없어요.

참고: 명명된 열거형에 대한 final switch도 참고하세요.

Enum Member Values

EnumMember의 값은 AssignExpression이 있으면 그것으로 주어져요. AssignExpression이 없고 첫 번째 EnumMember라면 그 값은 0에서 EnumBaseType으로 변환된 값이에요. AssignExpression이 없고 첫 번째 멤버가 아니라면 이전 EnumMember의 값 + 1을 가져요.

이전 EnumMember의 값이 EnumBaseType.max라면 오류예요. 이는 값 오버플로를 막기 위한 것이에요. 이전 멤버를 컴파일 타임에 EnumBaseType.max와 비교할 수 없다면 오류예요.

기본 타입이 컴파일 타임에 평가 가능한 + 1 연산을 정의하지 않으면 오류예요.

이전 EnumMember의 값 + 1이 이전 EnumMember의 값과 같다면 오류예요. (부동소수점 타입에서 이런 일이 발생할 수 있어요.)

enum E : char
{
    a,
    b = char.max,
    c // overflow
}

static assert(E.a == 0);

모든 EnumMember는 AssignExpression의 스코프 안에 있어요.

enum A = 3;
enum B
{
    A = A // error, circular reference
}
enum C
{
    A = B,  // A = 4
    B = D,  // B = 4
    C = 3,  // C = 3
    D       // D = 4
}
enum E : C
{
    E1 = C.D,
    E2      // error, C.D is C.max
}

빈 열거형 본문은 불투명 열거형(opaque enum)을 의미해요 — enum 멤버들이 알려지지 않아요.

enum X;          // opaque enum
writeln(X.init); // error: enum X is opaque and has no default initializer

Enum Properties

열거형 속성은 명명된 열거형에만 존재해요.

명명된 열거형 속성

.init First enum member value
.min Smallest enum member value
.max Largest enum member value
.sizeof Size of storage for an enumerated value

예를 들어:

enum X { A = 3, B = 1, C = 4, D, E = 2 }
X.init   // is X.A
X.min    // is X.B
X.max    // is X.D
X.sizeof // is same as int.sizeof

명명된 열거형의 EnumBaseType은 .max와 .min 속성을 계산하려면 비교를 지원해야 해요.

Enum Copying and Assignment

명명된 열거형 타입은 그 EnumBaseType에 정의돼 있더라도 복사 생성자, postblit, identity assignment 오버로드를 절대 갖지 않아요.

기본 타입이 복사 생성자를 가진 struct인 명명된 열거형 값을 복사할 때 복사 생성자는 호출되지 않아요.

struct S
{
    this(ref S rhs) { assert(0); }
}

enum E : S { A = S.init }

void main()
{
    E e1;
    E e2 = e1; // ok - copy constructor not called
}

기본 타입이 postblit을 가진 struct인 명명된 열거형 값을 복사할 때 postblit은 호출되지 않아요.

struct S
{
    this(this) { assert(0); }
}

enum E : S { A = S.init }

void main()
{
    E e1;
    E e2 = e1; // ok - postblit not called
}

같은 타입의 다른 객체에 명명된 열거형 값을 할당할 때, 값들의 기본 타입이 identity assignment 오버로드를 가진 struct라면 identity assignment 오버로드는 호출되지 않아요.

struct S
{
    void opAssign(S rhs) { assert(0); }
}

enum E : S { A = S.init }

void main()
{
    E e1, e2;
    e2 = e1; // ok - opAssign not called
}

Anonymous Enums

enum Identifier가 없으면 그 열거형은 익명 열거형(anonymous enum)이고, EnumMembersEnumDeclaration이 나타나는 스코프에 선언돼요. 새 타입은 만들어지지 않아요.

EnumMember들은 서로 다른 타입을 가질 수 있어요. 그 타입들은 다음 중 처음으로 맞는 것으로 주어져요:

  • Type (있을 때). Type은 EnumBaseType이 있을 때는 허용되지 않아요.
  • EnumBaseType (있을 때).
  • AssignExpression의 타입 (있을 때).
  • 이전 EnumMember의 타입 (있을 때).
  • int
enum { A, B, C }  // anonymous enum

위는 A = 0, B = 1, C = 2 상수를 정의하며 모두 타입이 int예요.

열거형은 최소한 멤버 하나가 있어야 해요.

EnumMember의 값은 AssignExpression이 있으면 그것으로 주어져요. AssignExpression이 없고 첫 번째 EnumMember라면 그 값은 EnumMember 타입의 .init 속성이에요. AssignExpression이 없고 첫 번째 멤버가 아니라면 이전 EnumMember의 값 + 1을 가져요.

이전 EnumMember의 값이 이전 EnumMember 타입의 .max 속성이라면 오류예요. 이는 값 오버플로를 막기 위한 것이에요. 이전 멤버를 컴파일 타임에 .max 속성과 비교할 수 없다면 오류예요.

이전 멤버의 타입이 컴파일 타임에 평가 가능한 +1 연산을 정의하지 않으면 오류예요.

이전 EnumMember + 1의 값이 이전 EnumMember의 값과 같다면 오류예요. (부동소수점 타입에서 이런 일이 발생할 수 있어요.)

모든 EnumMember는 AssignExpression의 스코프 안에 있어요.

enum { A, B = 5 + 7, C, D = 8 + C, E }

위는 A = 0, B = 12, C = 13, D = 21, E = 22를 설정하며 모두 타입이 int예요.

enum : long { A = 3, B }

위는 A = 3, B = 4를 설정하며 둘 다 타입이 long이에요.

enum : string
{
    A = "hello",
    B = "betty",
    C     // error, cannot add 1 to "betty"
}
enum
{
    A = 1.2f,  // A is 1.2f of type float
    B,         // B is 2.2f of type float
    int C = 3, // C is 3 of type int
    D          // D is 4 of type int
}

Single Member Syntax

익명 열거형의 멤버가 하나뿐이라면 { }를 생략할 수 있어요. 문법적으로 이것은 VarDeclarations의 한 형태예요.

enum i = 4;      // i is 4 of type int
enum long l = 3; // l is 3 of type long

이것을 매니페스트 상수(manifest constant)라고도 불러요.

Manifest Constants

열거형 멤버는 매니페스트 상수이며, 컴파일 타임에만 존재해요.

매니페스트 상수는 lvalue가 아니므로 그 주소를 취할 수 없어요. 그것들은 컴파일러의 메모리에만 존재해요.

enum size = __traits(classInstanceSize, Foo);  // evaluated at compile-time

매니페스트 상수의 초기화 값은 컴파일 타임 함수 평가(compile-time function evaluation)로 평가돼요.

template Foo(T)
{
    // Not bad, but the 'size' variable will be located in the executable.
    const size_t size = T.sizeof;       // evaluated at compile-time

    // ... use of 'size' at compile-time ...
}

template Bar(T)
{
    // Better, the manifest constant has no runtime location in the executable.
    enum size_t size = T.sizeof;        // evaluated at compile-time

    // ... use of 'size' at compile time ...

    // Taking the address of Foo!T.size also causes it to go into the exe file.
    auto p = &Foo!T.size;
}

더 알아보기

  • 열거형 타입에 대한 이항 연산의 결과 타입 규칙은 타입(Type) 문서에서 확인할 수 있어요.
  • 매니페스트 상수와 컴파일 타임 함수 평가는 CTFE 관련 문서를 참고하세요.
  • 명명된 열거형에 대한 final switch 문장을 참고하세요.
  • dlang.org의 원문에서 최신 내용을 확인할 수 있어요.