web-dev-qa-db-fra.com

Autoriser les plages basées sur des classes enum?

J'ai un morceau de code récurrent où je boucle sur tous les membres d'un enum class.

La boucle for que j'utilise actuellement semble très peu maniable par rapport à la nouvelle range-based for.

Existe-t-il un moyen de profiter des nouvelles fonctionnalités C++ 11 pour réduire la verbosité de ma boucle for actuelle?

Code actuel que j'aimerais améliorer:

enum class COLOR
{
    Blue,
    Red,
    Green,
    Purple,
    First=Blue,
    Last=Purple
};

inline COLOR operator++( COLOR& x ) { return x = (COLOR)(((int)(x) + 1)); }

int main(int argc, char** argv)
{
  // any way to improve the next line with range-based for?
  for( COLOR c=COLOR::First; c!=COLOR::Last; ++c )
  {
    // do work
  }
  return 0;
}

En d'autres termes, ce serait bien si je pouvais faire quelque chose comme:

for( const auto& c : COLOR )
{
  // do work
}
65
kfmfe04

Itérer les énumérations avec l'énumération elle-même en tant qu'itérateur est une mauvaise idée, et je recommande d'utiliser un véritable itérateur comme dans la réponse de deft_code. Mais si c'est vraiment ce que vous voulez:

COLOR operator++(COLOR& x) {
    return x = (COLOR)(std::underlying_type<COLOR>::type(x) + 1); 
}

COLOR operator*(COLOR c) {
    return c;
}

COLOR begin(COLOR r) {
    return COLOR::First;
}

COLOR end(COLOR r) {
    COLOR l=COLOR::Last;
    return ++l;
}

int main() { 
    //note the parenthesis after COLOR to make an instance
    for(const auto& c : COLOR()) {
        //do work
    }
    return 0;
}

Travailler ici: http://ideone.com/cyTGD8


extern const COLOR COLORS[(int)COLOR::Last+1];
const COLOR COLORS[] = {COLOR::Blue, COLOR::Red, COLOR::Green, COLOR::Purple};

int main() { 
    for(const auto& c : COLORS) {
        //do work
    }
    return 0;
}

Comme vu ici: http://ideone.com/9XadVt

(La déclaration et la définition distinctes du tableau en font une erreur de compilation si le nombre de couleurs ne correspond pas au nombre d'éléments dans le tableau. Excellent contrôle de sécurité facile.)

30
Mooing Duck

Personnellement, je n'aime pas surcharger le ++ opérateur pour les énumérations. Souvent incrémentation une valeur d'énumération n'a pas vraiment de sens. Tout ce que l'on veut vraiment, c'est un moyen d'itérer sur l'énumération.

Vous trouverez ci-dessous une classe générique Enum qui prend en charge l'itération. C'est fonctionnel mais incomplet. Une vraie implémentation ferait bien de restreindre l'accès au constructeur et d'ajouter tous les traits de l'itérateur.

#include <iostream>

template< typename T >
class Enum
{
public:
   class Iterator
   {
   public:
      Iterator( int value ) :
         m_value( value )
      { }

      T operator*( void ) const
      {
         return (T)m_value;
      }

      void operator++( void )
      {
         ++m_value;
      }

      bool operator!=( Iterator rhs )
      {
         return m_value != rhs.m_value;
      }

   private:
      int m_value;
   };

};

template< typename T >
typename Enum<T>::Iterator begin( Enum<T> )
{
   return typename Enum<T>::Iterator( (int)T::First );
}

template< typename T >
typename Enum<T>::Iterator end( Enum<T> )
{
   return typename Enum<T>::Iterator( ((int)T::Last) + 1 );
}

enum class Color
{
   Red,
   Green,
   Blue,
   First = Red,
   Last = Blue
};

int main()
{
   for( auto e: Enum<Color>() )
   {
      std::cout << ((int)e) << std::endl;
   }
}
49
deft_code
enum class Color {
    blue,
    red,
    green = 5,
    purple
};
const std::array<Color,4> all_colors = {Color::blue, Color::red, Color::green, Color::purple};

Ensuite:

for (Color c : all_colors) {
    //...
}

Plusieurs fois, je l'utilise comme ceci, où je veux une valeur "aucune":

// Color of a piece on a chess board
enum class Color {
    white,
    black,
    none
};
const std::array<Color,3> colors = {Color::white, Color::black};

template <typename CONTAINER>
bool has_item (CONTAINER const & c, typename CONTAINER::const_reference v) {
    return std::find(c.begin(), c.end(), v) != c.end();
}

bool is_valid (Color c) {
    return has_item(colors, c) || c == Color::none;
}

bool do_it (Color c) {
    assert(has_item(colors, c)); // here I want a real color, not none
    // ...
}

bool stop_it (Color c) {
    assert(is_valid(c));         // but here I just want something valid
    // ...
}
33
user1594322

Vous pourriez probablement faire quelque chose d'intelligent avec boost :: mpl, une version approximative pourrait ressembler à:

#include <typeinfo>

// ---------------------------------------------------------------------------|
// Boost MPL
// ---------------------------------------------------------------------------|
#include <boost/mpl/for_each.hpp>
#include <boost/mpl/iterator_range.hpp>
#include <boost/mpl/range_c.hpp>

namespace mpl = boost::mpl;

using namespace std;

enum class COLOR 
{ 
   Blue,
   Red,
   Green,
   Purple,
   Last
};

struct enumValPrinter
{
    template< typename T >
    void operator() (const T&)
    {
        cout << "enumValPrinter with: " << typeid( T ).name() << " : " 
             << T::value << "\n";
    }
};

int main(int, char**)
{
    typedef mpl::range_c< int, static_cast<int>( COLOR::Blue ), 
                            static_cast<int>( COLOR::Last ) > Colors;
    mpl::for_each< Colors >( enumValPrinter() );
    return 0;
}
4
mark

Voici un exemple testé (GCC 4.6.1):

enum class COLOR
{
    Blue,
    Red,
    Green,
    Purple,
    First=Blue,
    Last=Purple
};

COLOR operator++( COLOR& x ) { return x = (COLOR)(((int)(x) + 1)); }

COLOR operator*(COLOR c) {return c;}

COLOR begin(COLOR r) {return COLOR::First;}
// end iterator needs to return one past the end!
COLOR end(COLOR r)   {return COLOR(int(COLOR::Last) + 1);}


int main()
{
    for (const auto& color : COLOR()) std::cout << int(color); //0123
    return 0;
}
3
jrok

J'aime beaucoup l'idée et je l'ai souvent souhaitée.

Le problème que je vois est ce qui se passe lorsqu'il y a une valeur numérique répétée pour un élément enum. Toutes les implémentations que je vois ci-dessus nécessitent des transtypages en type intégral et ++. En fin de compte, je pense que le support linguistique pourrait être nécessaire pour vraiment itérer sur chaque élément dans tous les cas. Cela supprimerait le besoin d'avoir First, Last ou Begin, End bien que je ne m'y oppose pas trop. C'est comme chercher begin () end () pour les conteneurs.

enum class COLOR 
{
   Blue,
   Red,
   Green,
   Mauve = 0,
   Purple,
   Last
};

La numérotation recommence à Mauve.

2
emsr

Si vous êtes une personne terrible, vous pouvez obtenir ce comportement avec le préprocesseur, quelque chose comme:

#include <vector>
#include <cstdio>

#define ENUM_NAME COLOR
#define ENUM_VALUES \
    ENUM_VALUE(Blue) \
    ENUM_VALUE(Red) \
    ENUM_VALUE(Green) \
    ENUM_VALUE(Purple)

// This block would be a #include "make_iterable_enum.h"
#define ENUM_VALUE(v) v,
enum class ENUM_NAME {ENUM_VALUES};
#undef ENUM_VALUE
#define ENUM_VALUE(v) ENUM_NAME::v,
#define VECTOR_NAME(v) values_ ## v
#define EXPAND_TO_VECTOR_NAME(v) VECTOR_NAME(v)
const std::vector<ENUM_NAME> EXPAND_TO_VECTOR_NAME(ENUM_NAME){ENUM_VALUES};
#undef ENUM_VALUE
#undef ENUM_NAME
#undef ENUM_VALUES
#undef VECTOR_NAME
#undef EXPAND_TO_VECTOR_NAME
// end #included block

int main() {
    for (auto v : COLOR_values) {
        printf("%d\n", (int)v);
    }
}

Avec des modifications mineures, cela pourrait également prendre en charge par exemple. ENUM_SETVALUE (Blue, 4) et faire une carte de const par exemple. COULEUR :: Bleu à "Bleu". Et vice versa.

Je souhaite que la norme ait simplement intégré ces fonctionnalités comme options pour énumérer la classe. Aucune des solutions de contournement n'est bonne.

2
Raven Black

Je suis sûr que vous pouvez parcourir les membres d'une liste d'initialisation C++, donc je pense que j'ai fait cela dans le passé:

enum class Color {Red, Green, Blue};

for (const Color c : {Color::Red, Color::Green, Color::Blue})
{
}

S'il y a des problèmes avec cela, je ne sais pas, mais j'ai pensé le suggérer car il est concis, mais pas idéal s'il y a beaucoup de couleurs.

2
Coder_Dan

Que vous approuviez ou non l'incrémentation des énumérations, il y a des moments où cela est utile. Voici donc un moyen simple de le faire:

enum class COLOR
{
    Blue,
    Red,
    Green,
    Purple,
    First=Blue,
    Last=Purple
};

COLOR c;

++( *reinterpret_cast<int*>( &c));

Il n'y a pas de frais généraux puisque le compilateur se chargera du casting et du déréférencement. Ajoutez la vérification de la plage ou d'autres capacités si nécessaire.

2
rm1948