it-swarm.com.de

Implementierung von Operatoren für die Aufzählungsklasse

Nach der Diskussion zu Inkrementieren und Dekrementieren von "enum class" möchte ich nach der möglichen Implementierung von arithmetischen Operatoren für enum class - Typen fragen.

Beispiel aus der ursprünglichen Frage:

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;
}

Gibt es eine Möglichkeit, arithmetische Operatoren zu implementieren, ohne sie in einen Typ mit bereits definierten Operatoren umzuwandeln? Mir fällt nichts ein, aber das Casting stört mich. Casts weisen normalerweise auf einen Fehler hin und es muss einen sehr guten Grund für ihre Verwendung geben. Ich würde erwarten, dass die Sprache die Implementierung eines Operators ermöglicht, ohne auf einen bestimmten Typ angewiesen zu sein.

Update Dezember 2018 : In einem der Artikel zu C++ 17 scheint dies zumindest teilweise behoben zu werden, indem Konvertierungen zwischen Enum-Klassenvariablen und dem zugrunde liegenden Typ zugelassen werden: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0138r2.pdf

31

Die No-Cast-Lösung ist die Verwendung eines Schalters. Sie können jedoch mithilfe von Vorlagen einen Pseudo-Switch generieren. Das Prinzip besteht darin, alle Werte der Aufzählung mithilfe einer Vorlagenliste (oder eines Parameterpakets) rekursiv zu verarbeiten. Hier sind 3 Methoden, die ich gefunden habe.

Test Enum:

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

Der Vanille-Schalter (hier leben) :

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;
    }
}

Die C++ 03-ish Methode (live hier) :

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;
}

Die C++ 11-ish-Methode (live hier) :

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 alte Version)

Möglicherweise können Sie die Liste erweitern, indem Sie einen Präprozessor hinzufügen, damit die Liste der Werte nicht wiederholt werden muss.

30
Synxis

Jeder Operator in C++ für Enums kann geschrieben werden, ohne einen zugrunde liegenden Typ zu verwenden, aber das Ergebnis wäre lächerlich ausführlich.

Als Beispiel:

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;
}

und ein intelligenter Compiler wird in der Lage sein, diese in Operationen umzuwandeln, die sich direkt auf den Basisintegraltyp beziehen.

Aber Casting auf einen Basisintegraltyp in der Schnittstelle Ihres enum class ist keine schlechte Sache. Und Operatoren sind Teil der Schnittstelle für Ihre enum class.

Wenn Ihnen das nicht gefällt, durchschleifen Sie size_t und halte es für eine gefälschte Besetzung, du kannst einfach schreiben:

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

und ebenso zum Dekrementieren und Implementieren von Inkrement-um -n als Schleifen von wiederholten increment.