列挙型関連の強化
C++ 23では、列挙型がスコープを持つものか調べるstd::is_scoped_enum<T>型特性、列挙型の値を基底型に変換するstd::to_underlying関数を使えるようになりました。
列挙型とは、関連のある値の集合を意味のある名前(列挙子)で表す構文です。
例えば信号機の現在の色を1、2、3というリテラルではなく、RED、GREEN、BLUEといった名前で表します。リテラルでは値を見ただけではその意味までは分かりませんが、名前を与えることで意味が分かりやすくなり可読性が向上します。
enum COLOR { // RED, GREEN, BLUEという列挙子を持つ列挙型COLOR
RED,
GREEN,
BLUE
};
auto color = RED; // colorの値はRED
これは、伝統的なC言語の列挙型です。この列挙型は以下の定義を続けることはできません。RED、GREEN、BLUEが重複するというコンパイルエラーになります。列挙型がCOLOR、RANGERという違いがあっても同様です。
enum RANGER { // RED, GREEN, BLUE, PINK, YELLOWという列挙子を持つ列挙型RANGER
RED, // COLORと重複する
GREEN, // COLORと重複する
BLUE, // COLORと重複する
PINK,
YELLOW
};
auto ranger = RED;
スコープを持つ列挙型[C++ 11]
このような問題は、従来の列挙型がスコープを持たないことに起因します。そこで、スコープを持つ列挙型が、C++ 11から利用可能になりました。スコープを持つ列挙型では、上記の列挙型を問題なく定義できます。
enum class SCOPED_COLOR: long { (1)
RED,
GREEN,
BLUE
};
auto scoped_color = SCOPED_COLOR::RED; (2)
enum struct SCOPED_RANGER { (3)
RED,
GREEN,
BLUE,
PINK,
YELLOW
};
auto scoped_ranger = SCOPED_RANGER::RED; (4)
スコープを持つ列挙型は、(1)(3)のようにenum classあるいはenum structで定義します(全く同じ機能です)。そして(2)(4)のように、列挙子は「列挙型名::」を前置して参照します。
なお(1)では、列挙型名のあとにコロンが続いてlong型が記述されていますが、これは「基底型」でいわば列挙子の内部型です。この場合はlong型であり、その値範囲の列挙子が指定できますが、(3)のように基底型を指定しない場合の既定はint型となります。
また、スコープを持つ列挙型における列挙子は整数型への暗黙の変換を持たないので、coutなどで出力するにはstatic_cast<T>演算子で整数型などにキャストします。
cout << static_cast<int>(SCOPED_COLOR::RED) << ", " << static_cast<int>(SCOPED_COLOR::GREEN) << ", " << static_cast<int>(SCOPED_COLOR::BLUE) << endl; // 0, 1, 2 cout << static_cast<int>(SCOPED_RANGER::RED) << ", " << static_cast<int>(SCOPED_RANGER::GREEN) << ", " << static_cast<int>(SCOPED_RANGER::BLUE) << ", " << static_cast<int>(SCOPED_RANGER::PINK) << ", " << static_cast<int>(SCOPED_RANGER::YELLOW) << endl; // 0, 1, 2, 3, 4
列挙型がスコープを持つか判定するstd::is_scoped_enum<T>型特性[C++ 23]
C++ 23では、列挙型がスコープを持つか判定するis_scoped_enum<T>型特性を利用できるようになりました。
型特性とは、型の特性を判定、操作するためのクラスのことです。関数のように使うことができることから、メタ関数とも呼ばれます。is_scoped_enum<T>型特性を使うと、従来の列挙型とスコープを持つ列挙型で処理を分けることが可能になります。
#include <type_traits> …略… cout << is_scoped_enum<COLOR>() << endl; // 0 cout << is_scoped_enum<SCOPED_COLOR>() << endl; // 1 +++/スクリプト+++
列挙型の基底型に変換するstd::to_underlying関数[C++ 23]
スコープを持つ列挙型は、基底型で内部的な値の型を指定することができました。とすると、static_cast<T>演算子を使った型変換は、基底型に基づいて行う必要があるということです。
先ほどのリストではintを指定していましたが、SCOPED_COLOR型に関してはlongとすべきです。しかし、いちいち調べて同じ型を指定するのも面倒ですし、変更されたとしたらそれを反映させなければなりません。
そこで、列挙型の基底型を表すメンバ型として、C++ 17でunderlying_type<T>::typeが利用できます。このメンバ型を使うと、実際の基底型で安全に型変換を行うことができます。
#include <type_traits> …略… cout << static_cast<underlying_type<SCOPED_RANGER>::type>(SCOPED_RANGER::RED) << endl; // 0
しかしこの記述は冗長で、できれば直接基底型でキャストしたいところです。そこで、基底型でキャストする関数として、C++ 23でto_underlyingが利用できるようになりました。この関数を使うと、シンプルに基底型に型変換できます。
#include <utility> …略… cout << to_underlying(SCOPED_RANGER::RED) << endl; // 0
