Typesafe Enum イディオム

enum を使って定数を定義する場合に,それらの値を使うときは「型名::値」と言う形で記述したいなぁとよく思うのですが,普通の記述 (enum 型名 { ... };) だとこの要求を満たせません.それで,これまで enum を使うときは enum の型名にあたる部分を名前空間に書くことでそれっぽい記述ができるようにしていました.

namespace Fruits {
    enum { Apple, Orange, Lemon, Banana };
}

int main(int argc, char* argv[]) {
    int kind = Fruits::Orange;
    
    switch (kind) {
        case Fruits::Apple:  ...;
        case Fruits::Orange: ...;
        case Fruits::Lemon:  ...;
        case Fruits::Banana: ...;
        default: break;
    }
    
    return 0;
}

ただ,この方法だと受け取る変数の型は int や std::size_t 辺りになるのが難点でした.上記の例で言うと,

Fruits kind = Fruits::Orange;

と言う記述ができるのがベストなのですが,その要求に応える方法が思いつきませんでした.

そんな不満を抱きながら Web を漁っていると「Typesafe Enum イディオム」と言うページが引っかかりました.

template<typename def, typename inner = typename def::type>
class safe_enum : public def
{
    typedef typename def::type type;
    inner val;
 
public:
    safe_enum(type v) : val(v) {}
    inner underlying() const { return val; }
 
    bool operator == (const safe_enum & s) const { return this->val == s.val; }
    bool operator != (const safe_enum & s) const { return this->val != s.val; }
    bool operator <  (const safe_enum & s) const { return this->val <  s.val; }
    bool operator <= (const safe_enum & s) const { return this->val <= s.val; }
    bool operator >  (const safe_enum & s) const { return this->val >  s.val; }
    bool operator >= (const safe_enum & s) const { return this->val >= s.val; }
};
More C++ Idioms/Type Safe Enum

これを使って "enum" を定義すると,最初の要求をかなりの部分まで満たしてくれます.

namespace detail {
    struct Fruits_ {
        enum type { Apple, Orange, Lemon, Banana };
    };
}
typedef safe_enum<detail::Fruits_> Fruits;

int main(int argc, char* argv[]) {
    Fruits kind = Fruits::Orange;
    
    switch (kind.underlying()) {
        case Fruits::Apple:  ...;
        case Fruits::Orange: ...;
        case Fruits::Lemon:  ...;
        case Fruits::Banana: ...;
        default: break;
    }
    
    return 0;
}

「Typesafe Enum イディオム」自体は,

  • enum A 型の値と enum B 型の値を比較しようとするとエラーになるようにしたい
  • enum A 型に存在しない値は作れない (enum A 型の変数に代入できない)ようにしたい

辺りの要求が出発点となっているようです.ただ,それ以外にも「switch 文の case に指定できるようにする」や,今回の私の要求である「型名::値と表記したい」などの要求にも応えるにはどうすれば良いかと,いろいろな人がいろいろな方法を検討している(いた?)ようです.