enum State {Idle, Fidget, Walk, Scan, Attack}; enum Direction {North, South, East, West};
デバッグ中に、「
State: 1
」ではなく「
State: Fidget
」などのメッセージがコンソールに表示されると、さらに便利です。 また、列挙をJSON、YAML、または別の形式に、文字列値の形式でシリアル化する必要があることがよくあります。 文字列の認識は数字よりも簡単であるという事実に加えて、文字列の認識をシリアル化形式で使用すると、列挙定数の数値の変更に対する耐性が高まります。 理想的には、新しい定数が宣言されていても、
"Fidget"
は
"Fidget"
を参照する必要があり、
Fidget
の値は1以外です。
残念ながら、C ++では、列挙値を文字列に、またはその逆に簡単に変換する方法はありません。 したがって、開発者は、ハードコードされた変換や、Xマクロなどの見苦しい制限構文の使用など、何らかのサポートを必要とするさまざまなトリックに頼らざるを得ません。 さらに誰かが自動変換のためにビルドツールを使用します。 当然、これは開発プロセスを複雑にするだけです。 結局、列挙には独自の構文があり、独自の入力ファイルに保存されます。これは、Makefileまたはプロジェクトファイルのビルドツールの作業を容易にしません。
ただし、C ++を使用すると、列挙型を文字列型に変換する問題を解決するのがはるかに簡単になります。
前述のすべての問題を回避し、純粋なC ++で完全に反映した列挙を生成することができます。 広告は次のようになります。
BETTER_ENUM(State, int, Idle, Fidget, Walk, Scan, Attack) BETTER_ENUM(Direction, int, North, South, East, West)
使用方法:
State state = State::Fidget; state._to_string(); // "Fidget" std::cout << "state: " << state; // "state: Fidget" state = State::_from_string("Scan"); // State::Scan (3) // switch, . switch (state) { case State::Idle: // ... break; // ... }
これは、プリプロセッサとテンプレートに関連するいくつかのトリックで行われます。 それらについては、記事の最後で少し説明します。
文字列への変換、およびその逆の変換に加えて、入出力のストリーミングに加えて、生成された列挙を反復処理することもできます。
for (Direction direction : Direction._values()) character.try_moving_in_direction(direction);
スパース範囲で列挙を生成してから、次をカウントできます。
BETTER_ENUM(Flags, char, Allocated = 1, InUse = 2, Visited = 4, Unreachable = 8) Flags::_size(); // 4
C ++ 11で作業している場合、
constexpr
関数を使用してコンパイル中にすべての変換とループを実行できるため、列挙に基づいてコードを生成することもできます。 たとえば、列挙の最大値を計算し、コンパイル時に使用可能にする
constexpr
関数を作成できます。 定数の値が任意に選択され、昇順で宣言されていない場合でも。
Githubから 、Better Enumsと呼ばれるライブラリにパッケージ化されたマクロの実装例をダウンロードできます 。 BSDの下でライセンスされているため、何でもできます。 この実装にはヘッダーファイルが1つあるため、それを使用するのは非常に簡単で、
enum.h
をプロジェクトフォルダーに追加するだけです。 それを試してみてください、これはあなたの問題を解決するのに役立つでしょう。
仕組み
文字列と列挙値の間の変換を実行するには、対応するマッピングを生成する必要があります。 Better Enumsは、コンパイル中に2つの配列を作成することによりこれを行います。 たとえば、次のような広告がある場合:
BETTER_ENUM(Direction, int, North = 1, South = 2, East = 4, West = 8)
マクロは次のようにリメイクします。
struct Direction { enum _Enum : int {North = 1, South = 2, East = 4, West = 8}; static const int _values[] = {1, 2, 4, 8}; static const char * const _names[] = {"North", "South", "East", "West"}; int _value; // ..., ... };
そして、変換に進みます
_values
または
_names
値または文字列のインデックスを見つけ、対応する値または文字列を別の配列に返します。
値の配列
_values
、
_Enum
内部列挙
_Enum
アクセスして生成されます。 マクロのこの部分は次のようになります。
static const int _values[] = {__VA_ARGS__};
次のように変換されます
static const int _values[] = {North = 1, South = 2, East = 4, West = 8};
これはほとんど有効な配列宣言です。 問題は、「= 1」のような追加の初期化子にあります。 これらを使用するために、Better Enumsは代入演算子のヘルパータイプを定義しますが、値自体は無視します。
template <typename T> struct _eat { T _value; template <typename Any> _eat& operator =(Any value) { return *this; } // . explicit _eat(T value) : _value(value) { } // T. operator T() const { return _value; } // T. }
重要ではない代入式に初期化子「= 1」を含めることができるようになりました。
static const int _values[] = {(_eat<_Enum>)North = 1, (_eat<_Enum>)South = 2, (_eat<_Enum>)East = 4, (_eat<_Enum>)West = 8};
文字列の配列
この配列を作成するために、Better Enumsは、文字列への変換(文字列化)の前処理演算子(
#
)を使用します。
__VA_ARGS__
を次のようなものに変換します。
static const char * const _names[] = {"North = 1", "South = 2", "East = 4", "West = 8"};
これで、定数の名前をほぼ文字列に変換しました。 イニシャライザを取り除くために残っています。 ただし、Better Enumsはサポートしていません。
_names
配列の
_names
を比較するとき
_names
彼はスペースと等しい文字を追加の行境界として認識しているだけです。 したがって、「
North = 1
」を検索すると
North = 1
より良い列挙型は「
North
」のみを検索します。
マクロなしで行うことは可能ですか?
ほとんどない。 実際、C ++では、演算子(#)がソースコードトークンを文字列に変換する唯一の方法です。 したがって、リフレクションで列挙を自動的に変換するライブラリでは、少なくとも1つの高レベルマクロを使用する必要があります。
その他の考慮事項
もちろん、マクロの実装を十分に検討することは、この記事で説明するよりもはるかに退屈で難しくなります。 基本的に、異なるコンパイラの特性のために、
static
constexpr
で動作する
constexpr
関数のサポートのために困難が生じ
constexpr
。 また、コンパイルを高速化するために、可能な限り多くのマクロをテンプレートに分解することに特定の困難が伴う可能性があります(テンプレートは作成中に再解析する必要がなく、拡張マクロが必要です)。