【C++】enum構造体イディオム【enumをstructでラップして便利に使う】


扱いが面倒な列挙体をより便利にするテクニックを紹介します。

enum構造体

列挙体を構造体でラップするというアイディアです。

以下のメリットがあります。

  • 列挙体メンバによる名前空間の汚染を回避できる
  • 列挙体向けのメンバ関数を追加できる
  • 列挙体同士の演算・ビット演算ができる

宣言方法

// 構造体
struct Color {
   
   // 列挙体
   enum Type { Red, Green, Blue };
   // 列挙型メンバ変数
   enum Type _type;
   
   // 「列挙型 <-> 構造体」の相互変換
   Color(Type v) : _type(v) {}
   operator enum Type() { return _type; }
   
   // メンバ関数
   bool isRed()   { return _type == Red; }
   bool isGreen() { return _type == Green; }
   
   const char* toString() {
      switch (_type) {
         case Red:   return "Red";
         case Green: return "Green";
         case Blue:  return "Blue";
      }
   }
};

使用例

int main() {
   // 「列挙型 → 構造体」の自動変換
   Color red   = Color::Red;
   Color green = Color::Green;
   Color blue  = Color::Blue;
   
   // メンバ関数の利用
   cout <<  red.isRed(); // true
   cout << blue.isRed(); // false
   
   // 文字列化
   cout << red.toString(); // "Red"
   
   // 構造体同士の比較
   if (red == green) puts("yes"); // ----
   
   // 列挙型との比較
   if (red == Color::Red) puts("yes"); // "yes"
   
   // 列挙型を直接取得
   Color::Type hitokage = Color::Red;
   if (hitokage == red) puts(red.toString());  // "Red"
   
   // 列挙型への逆変換
   Color::Type kamex = blue;
   if (kamex == blue) puts(blue.toString());   // "Blue"
   
   // きちんと警告も発生(Greenが列挙されていないため)
   switch (red) { // !!!: Enumeration value 'Green' not handled in switch
      case Color::Red:  puts("red");  break;
      case Color::Blue: puts("blue"); break;
   }
}

解説

構造体に列挙体と同等の振る舞いをさせています。

基本的な挙動や特性は上記サンプルのコメント内に全て書かれています。ここでは重要な部分だけ解説します。

列挙体と構造体の相互変換

以下の変換コンストラクタとキャスト演算子を構造体側で独自定義することで、構造体と列挙体のシームレスな変換を実現しています。

Color(Type v) : _type(v) {}
operator enum Type() { return _type; }

より詳しい説明は以下のページで行っています。

C++の列挙型でメンバ関数を実現する方法

演算処理に対応

Color列挙体の用途的には無意味な処理ですが、実現自体は可能です。 Color構造体に以下のコンストラクタを加えます。

Color(int v) : _type(static_cast<Type>(v)) {}

これで、構造体と列挙体と数値型の相互変換が実現されます。

Color x = Color::Green + 1;
puts(x.toString()); // "Blue"
Color y = 0;
puts(y.toString()); // "Red"

// 手動で行う場合
Color x = static_cast<Color::Type>(Color::Green + 1);

ビット演算に対応

上記の対応によって既にビット処理可能な状態です。

以下は、ビットマスク処理に対応したenum構造体の例です。

struct LineBreak {
   enum Type : unsigned int {
      CR = 1 << 0,
      LF = 1 << 1,
   } _type;
   
   LineBreak(unsigned int v) : _type(static_cast<Type>(v)) {}
   operator Type() { return _type; }
   
   bool isCRLF() { return _type == (CR | LF); }
};

int main() {
   LineBreak cr = LineBreak::CR;
   LineBreak lf = LineBreak::LF;
   LineBreak crlf = LineBreak::CR | LineBreak::LF;
   crlf = cr | lf;
   
   std::cout << cr.isCRLF() << lf.isCRLF() << crlf.isCRLF(); // 001
   
   if (crlf & LineBreak::CR)
      puts("yes"); // "yes"
}

より詳しい説明は以下のサイトも参考にしてください。

列挙型をビット演算する方法

広告