C++11 における enum のまとめ

後で忘れた時に、すぐに思い出せるようにメモ。ソースはFDIS(N3290)です。正式な規格書が FDIS と異なっていたら、すみません。

2種類の enum

C++11 では、2種類の enum が用意されている(7.2p2)。

  • unscoped enumeration … お馴染みの C++03 でも活躍した enum
  • scoped enumeration … 新規格 C++11 で新たに導入された enum で、いわゆる scoped and strongly typed enums

宣言(7.2p1)

実体宣言

unscoped enumeration は次のような感じ:

enum identifier1 : long {
    enumerator1,
    enumerator2,
    enumerator3 = 100,
    enumerator4, // このカンマは C++03 では ill-formed
}

identifier1 は独立した型名として使える。省略しても可。: long は各 enumerator を表現するための、基盤となる型(underlying type)を指定している。省略すると(underlying type is not fixed)、実装依存の整数型が使用される(7.2p6)。
enumerator1 には定数 0 が対応し、enumerator3 のように明示的に指定されなければ、一つ前の enumerator を1増加させた値が対応する。つまり、以下のように対応する:

  • enumerator1 … 0
  • enumerator2 … 1
  • enumerator3 … 100
  • enumerator4 … 101

一方 scoped enumeration は次のような感じ:

enum class identifier2 : long {
    enumerator5,
    enumerator6,
    enumerator7 = 100,
    enumerator8,
}

基本は unscoped enumeration に似ているが、異なる点がいくらかある。enum class は enum struct でもよい。これら2つは等価(7.2p2)。identifier2 は省略できない(7.2p2)。underlying type の指定を省略するとデフォルトで int が使用される(7.2p5)。

明示的に underlying type を指定してあったり、scoped enumeration でそれを省略した場合、underlying type は「fixed」であるという。逆に unscoped enumeration でそれを省略した場合には not fixed であり、実装依存の整数型が使用される。

前方宣言

unscoped enumeration でも unscoped enumeration でも C++11 から前方宣言が可能となった:

enum identifier1 : long;

または

enum class identifier2 : long;

identifier[12] は省略できない。underlying type の指定については、unscoped enumeration では省略できないが、scoped enumeration では省略できる(名無しさんご指摘ありがとうございました)。
もちろん、後で実体を再宣言することになる。scoped enumeration として前方宣言した後で、unscoped enumeration として再宣言したり、その逆をしてはならない(7.2p3)。また、再宣言する時に、前方宣言と矛盾するような underlying type の指定もできない(7.2p3)。

参照方法(7.2p10)

unscoped enumeration は次のような感じ:

identifier1 a = enumerator1;              // OK.従来の使い方
identifier1 b = identifier1::enumerator3; // OK.C++11 で新しく認められた。C++03 では ill-formed

つまり、各 enumerator は、実体宣言を直接含むスコープでそのまま参照できる。また、C++11 では enum の型名で修飾して参照できるようになった。従来の enum は、実体宣言を直接含む名前空間に enumerator の名前をそのまま追加したので、名前衝突の危険性を持っていた。

一方 scoped enumeration は次のような感じ:

identifier2 c = enumerator5;              // error
identifier2 d = identifier2::enumerator7; // OK.この方法でしか各 enumerator にアクセスできない

つまり、各 enumerator は、enum の型名で修飾しないと参照できない。従来の enum とは異なり、実体宣言を直接含む名前空間を汚さず、名前衝突の危険性も無い。

型変換

enum => 整数型」の変換(7.2p9)

unscoped enumeration では暗黙の型変換がある一方で、scoped enumeration では明示的にキャストしないと整数型に型変換できない。

unscoped enumeration では integral promotion(4.5) により標準変換(Standard conversions)、つまり暗黙の型変換が行われる:

int i = enumerator1; // OK.i に 0 が代入される

一方 scoped enumeration は次のような感じ:

int j = identifier2::enumerator5;                     // ill-formed.整数型への標準変換は無い
int k = static_cast<int>( identifier2::enumerator5 ); // OK.static_cast を使えば,強制的に型変換できる(5.2.9p9)
「整数型 => enum」の変換(7.2p9)

列挙型は、他の型とは区別される、れっきとした型なので、列挙型であるX型のオブジェクトには、基本的にそのX型の enumerator しか代入できない。したがって、整数型の値を、列挙型の変数に代入することは基本的にできない。これは unscoped enumeration でも scoped enumeration でも同じ。どうしても型変換したい場合は、明示的にキャストするしかない。

unscoped enumeration の例:

identifier1 e = 0;                             // ill-formed
identifier1 f = static_cast<identifier1>( 0 ); // OK.static_cast を使えば,強制的に型変換できる(5.2.9p10)

scoped enumeration の例(↑と同じ):

identifier2 g = 0;                             // ill-formed
identifier2 h = static_cast<identifier2>( 0 ); // OK.static_cast を使えば,強制的に型変換できる(5.2.9p10)
異なる列挙型の変換

基本的に「整数型 => enum」の変換といっしょ。
列挙型は、他の型とは区別される、れっきとした型なので、列挙型であるX型のオブジェクトには、基本的にそのX型の enumerator しか代入できない。したがって、ある列挙型の値を、別の列挙型の変数に代入することは基本的にできない。どうしても型変換したい場合は、明示的に static_cast するしかない(5.2.9p10)。

その他

お腹へった…ご飯食べに行こう。