web-dev-qa-db-ja.com

列挙型クラスの演算子の実装

問題の議論 「enum class」の増分と減分 に続いて、enum class型の算術演算子の可能な実装について尋ねたいと思います。

元の質問の例:

enum class Colors { Black, Blue, White, END_OF_LIST };

// Special behavior for ++Colors
Colors& operator++( Colors &c ) {
  c = static_cast<Colors>( static_cast<int>(c) + 1 );
  if ( c == Colors::END_OF_LIST )
    c = Colors::Black;
  return c;
}

定義済みの演算子を持つ型にキャストせずに算術演算子を実装する方法はありますか?私は何も考えられませんが、キャストは私を悩ませます。キャストは通常​​、何か間違っていることを示しており、その使用には非常に正当な理由が必要です。この言語は、特定の型を強制せずに演算子の実装を実現できると期待しています。

2018年12月更新:C++ 17に向けた論文の1つは、enumクラス変数と基になる型の間の変換を許可することで、少なくとも部分的にこれに対処しているようです: http:// www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0138r2.pdf

31

キャストなしのソリューションは、スイッチを使用することです。ただし、テンプレートを使用して擬似スイッチを生成できます。原則は、テンプレートリスト(またはパラメーターパック)を使用して、列挙型のすべての値を再帰的に処理することです。だから、私が見つけた3つの方法があります。

テスト列挙:

enum class Fruit
{
    Apple,
    banana,
    orange,
    pineapple,
    lemon
};

バニラスイッチ (ここにライブ)

Fruit& operator++(Fruit& f)
{
    switch(f)
    {
        case Fruit::Apple:     return f = Fruit::banana;
        case Fruit::banana:    return f = Fruit::orange;
        case Fruit::orange:    return f = Fruit::pineapple;
        case Fruit::pineapple: return f = Fruit::lemon;
        case Fruit::lemon:     return f = Fruit::Apple;
    }
}

C++ 03-ishメソッド (ここにライブ)

template<typename E, E v>
struct EnumValue
{
    static const E value = v;
};

template<typename h, typename t>
struct StaticList
{
    typedef h head;
    typedef t tail;
};

template<typename list, typename first>
struct CyclicHead
{
    typedef typename list::head item;
};

template<typename first>
struct CyclicHead<void,first>
{
    typedef first item;
};

template<typename E, typename list, typename first = typename list::head>
struct Advance
{
    typedef typename list::head lh;
    typedef typename list::tail lt;
    typedef typename CyclicHead<lt, first>::item next;

    static void advance(E& value)
    {
        if(value == lh::value)
            value = next::value;
        else
            Advance<E, typename list::tail, first>::advance(value);
    }
};

template<typename E, typename f>
struct Advance<E,void,f>
{
    static void advance(E& value)
    {
    }
};

/// Scalable way, C++03-ish
typedef StaticList<EnumValue<Fruit,Fruit::Apple>,
        StaticList<EnumValue<Fruit,Fruit::banana>,
        StaticList<EnumValue<Fruit,Fruit::orange>,
        StaticList<EnumValue<Fruit,Fruit::pineapple>,
        StaticList<EnumValue<Fruit,Fruit::lemon>,
        void
> > > > > Fruit_values;

Fruit& operator++(Fruit& f)
{
    Advance<Fruit, Fruit_values>::advance(f);
    return f;
}

C++ 11-ishメソッド (ここにライブ)

template<typename E, E first, E head>
void advanceEnum(E& v)
{
    if(v == head)
        v = first;
}

template<typename E, E first, E head, E next, E... tail>
void advanceEnum(E& v)
{
    if(v == head)
        v = next;
    else
        advanceEnum<E,first,next,tail...>(v);
}

template<typename E, E first, E... values>
struct EnumValues
{
    static void advance(E& v)
    {
        advanceEnum<E, first, first, values...>(v);
    }
};

/// Scalable way, C++11-ish
typedef EnumValues<Fruit,
        Fruit::Apple,
        Fruit::banana,
        Fruit::orange,
        Fruit::pineapple,
        Fruit::lemon
> Fruit_values11;

Fruit& operator++(Fruit& f)
{
    Fruit_values11::advance(f);
    return f;
}

(C++ 11-ish古いバージョン)

プリプロセッサを追加して、値のリストを繰り返す必要をなくすことで拡張できる場合があります。

30
Synxis

列挙型のC++のすべての演算子は、基になる型にキャストせずに記述できますが、結果はとてつもなく冗長になります。

例として:

size_t index( Colors c ) {
  switch(c) {
    case Colors::Black: return 0;
    case Colors::Blue: return 1;
    case Colors::White: return 2;
  }
}
Color indexd_color( size_t n ) {
  switch(n%3) {
    case 0: return Colors::Black;
    case 1: return Colors::Blue;
    case 2: return Colors::White;
  }
}
Colors increment( Colors c, size_t n = 1 ) {
  return indexed_color( index(c) + n );
}
Colors decrement( Colors c, size_t n = 1 ) {
  return indexed_color( index(c)+3 - (n%3) );
}
Colors& operator++( Colors& c ) {
  c = increment(c)
  return c;
}
Colors operator++( Colors& c, bool ) {
  Colors retval = c;
  c = increment(c)
  return retval;
}

また、スマートコンパイラは、これらを基本整数型に直接作用する操作に変換できます。

しかし、基本整数型へのキャストenum classのインターフェースでは悪いことではありません。また、演算子はenum classのインターフェースの一部です。

size_tのループが気に入らず、それを偽のキャストと見なす場合は、次のように記述できます。

Colors increment( Colors c ) {
  switch(c) {
    case Colors::Black: return Colors::Blue;
    case Colors::Blue: return Colors::White;
    case Colors::White: return Colors::Black;
  }
}

同様に減分についても、nの繰り返しのループとしてincrement_by -incrementを実装します。