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