web-dev-qa-db-fra.com

Implémentation d'opérateurs pour la classe enum

Suite à la discussion en question Incrémentation et décrémentation de "enum class" , je voudrais poser des questions sur l'implémentation possible d'opérateurs arithmétiques pour enum class les types.

Exemple de la question d'origine:

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

Existe-t-il un moyen d'implémenter des opérateurs arithmétiques sans transtyper en un type avec des opérateurs déjà définis? Je ne pense à aucun, mais le casting me dérange. Les lancers indiquent généralement quelque chose de mal et il doit y avoir une très bonne raison pour leur utilisation. Je m'attendrais à ce que le langage permette l'implémentation d'un opérateur sans être forcé à un type spécifique.

Mise à jour de décembre 2018 : l'un des articles vers C++ 17 semble résoudre ce problème au moins partiellement en permettant les conversions entre la variable de classe enum et le type sous-jacent: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0138r2.pdf

31
SomeWittyUsername

La solution sans coulée consiste à utiliser le commutateur. Cependant, vous pouvez générer un pseudo-commutateur à l'aide de modèles. Le principe est de traiter récursivement toutes les valeurs de l'énumération à l'aide d'une liste de modèles (ou d'un pack de paramètres). Alors, voici 3 méthodes que j'ai trouvées.

Énumération du test:

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

Le commutateur Vanilla (en direct ici) :

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

La méthode C++ 03-ish (en direct ici) :

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

La méthode C++ 11-ish (en direct ici) :

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

(ancienne version C++ 11-ish)

Vous pouvez être en mesure d'étendre en ajoutant un préprocesseur pour supprimer la nécessité de répéter la liste de valeurs.

30
Synxis

Chaque opérateur en C++ sur des énumérations peut être écrit sans transtyper en un type sous-jacent, mais le résultat serait ridiculement verbeux.

Par exemple:

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

et un compilateur intelligent pourra les transformer en opérations directement sur le type intégral de base.

Mais la conversion vers un type intégral de base dans l'interface de votre enum class n'est pas une mauvaise chose. Et les opérateurs font partie de l'interface de votre enum class.

Si vous n'aimez pas cette boucle à travers size_t et le considérer comme un faux casting, vous pouvez simplement écrire:

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

et de même pour décrémenter, et implémenter increment-by -n comme des boucles de increment répétées.

4