Blog coding and discussion of coding about JavaScript, PHP, CGI, general web building etc.

Saturday, February 20, 2016

How to use enum class values as part of for-loop?

How to use enum class values as part of for-loop?


I'm trying to create a deck of cards by iterating over the enums Suit and Rank (I know there's no great way to iterate over enums but I don't see an alternative). I did this by adding an enumerator enum_count to the end of each enum, whose value is meant to represent the length and end of the enum.

#include     using namespace std;    enum class Suit: int {clubs, diamonds, hearts, spades, enum_count};  enum class Rank: int {one, two, three, four, five, six, seven, eight,                  nine, ten, jack, queen, king, ace, enum_count};    struct Card {      Suit suit;      Rank rank;  };    class Deck{      vector cards{};      public:          Deck();  };    Deck::Deck() {      // ERROR ON THE BELOW LINE      for (Suit suit = Suit::clubs; suit < Suit::enum_count; suit++) {          for (Rank rank = Rank::one; rank < Rank::enum_count; rank++) {              Card created_card;              created_card.suit = suit;              created_card.rank = rank;              cards.push_back(created_card);          };      };  };  

However, when I try to loop over the enum, the compiler doesn't like that I'm trying to increment the suit++ and rank++ in the for-loop, stating:

card.cpp|24|error: no ?operator++(int)? declared for postfix ?++? [-fpermissive]|  card.cpp|25|error: no ?operator++(int)? declared for postfix ?++? [-fpermissive]|  

What is the best way to go about creating a deck of cards without throwing away the useful enum data structures?

Answer by Humam Helfawi for How to use enum class values as part of for-loop?


You can not use this with enum class. You have to use the old style enum.

If you insist you use them. I can suggest you a bad solution not to use (unless you won't tell anyone that I suggested it):

for (Rank rank = Rank::one;        static_cast(rank) < static_cast(Rank::enum_count);        rank = static_cast(static_cast(rank)+1)){  };  

Answer by Simon Kraemer for How to use enum class values as part of for-loop?


You could cast your suit and rank variables to an int& and increase them as such.

    for (Suit suit = Suit::clubs; suit < Suit::enum_count; ((int&)suit)++) {          for (Rank rank = Rank::one; rank < Rank::enum_count; ((int&)rank)++) {  

Yet this might cause some problems like when you assign values to your enum entries.


You could also create a little function that does this for you with the correct type:

template   T& increment(T& value)  {      static_assert(std::is_integral>::value, "Can't increment value");      ((std::underlying_type_t&)value)++;      return value;  }    Deck::Deck() {      bool a = std::is_integral>::value;        // ERROR ON THE BELOW LINE      for (Suit suit = Suit::clubs; suit < Suit::enum_count; increment(suit)) {          for (Rank rank = Rank::one; rank < Rank::enum_count; increment(rank)) {              Card created_card;              created_card.suit = suit;              created_card.rank = rank;              cards.push_back(created_card);          };      };  };  

Answer by old_mountain for How to use enum class values as part of for-loop?


I would recommend doing something different. Create a vector of Suit and one to Rank, and loop over them using the power of STL

const std::vector v_suit {Suit::clubs, Suit::diamonds, Suit::hearts, Suit::spades};    const std::vector v_rank {Rank::one, Rank::two, Rank::three, Rank::four, Rank::five,                             Rank::six, Rank::seven, Rank::eight, Rank::nine, Rank::ten, Rank::jack,                             Rank::queen, Rank::king, Rank::ace};  

Yes, you have to type them twice, but this permits you to use whatever values you want for them (ie. not consecutive), not use awkward stuff like enum_count (What card do you want? Give me a diamonds enum_count!!), no need for casting, and use the iterators provided to std::vector.

To use them:

for(const auto & s : v_suit)      for (const auto & r : v_rank)          cards.push_back({s,r});  

Answer by Simon Kraemer for How to use enum class values as part of for-loop?


Additional answer in response to old_mountain's answer:

You can in some cases prevent that you forget to add new values to your list by using fixed arrays. The main problem with this is that the initializer accepts less arguments than specified but you can work around this:

template  struct first_type  {      using type = T;  };    template   std::array::type, sizeof...(Args)> make_array(Args&&... refs)   {      return std::array::type, sizeof...(Args)>{ { std::forward(refs)... } };  }  

I found the inspiration to make_array in this question, but modified it: How to emulate C array initialization "int arr[] = { e1, e2, e3, ... }" behaviour with std::array?

What it does is to use the first argument to find out what type the std::array shall be of and the number of arguments to get the real size. So the return type is std::array.

Now declare your lists like this:

const std::array SuitValues =       make_array(Suit::clubs, Suit::diamonds, Suit::hearts, Suit::spades);    const std::array RankValues =       make_array(Rank::one, Rank::two, Rank::three, Rank::four,                  Rank::five, Rank::six, Rank::seven, Rank::eight,                 Rank::nine, Rank::ten, Rank::jack, Rank::queen,                  Rank::king, Rank::ace);  

When you add a value to the array your enum_count or whatever value you are using as delimiter will change and therefore the assignment from make_array will fail as the sizes of both std::arrays differ (which results in different types).

Example:

If you just add a new Suit, let's say hexa

enum class Suit : int { clubs, diamonds, hearts, spades, hexa, enum_count };  

The compiler will fail with:

cannot convert from 'std::array' to 'const std::array'  

I have to admit that I am not 100% happy with this solution as it requires a pretty ugly cast to size_t in the array declaration.

Answer by PaperBirdMaster for How to use enum class values as part of for-loop?


I also want to share my approach, based on a previous answer of mine which creates map using C++11 and C++14 features, the code is the one below:

// Shortcut to the map  template   using enum_map = std::map;    // Template variable for each enumerated type  template   enum_map enum_values{};    // Empty function to end the initialize recursion  void initialize(){}    // Recursive template which initializes the enum map  template   void initialize(const ENUM value, const char *name, args ... tail)  {      enum_values.emplace(value, name);      initialize(tail ...);  }  

With this template, we can change the Deck constructor this way:

Deck::Deck() {      for (const auto &S : enum_values) {          for (const auto &R : enum_values) {              Card created_card;              created_card.suit = S.first;              created_card.rank = R.first;              cards.push_back(created_card);          };      };  };  

The only requirement in order to make the whole thing work is to call the initialize function this way:

initialize  (      Suit::clubs,    "Clubs",      Suit::diamonds, "Diamonds",      Suit::hearts,   "Hearts",      Suit::spades,   "Spades",        Rank::one,   "1",      Rank::two,   "2",      Rank::three, "3",      Rank::four,  "4",      Rank::five,  "5",      Rank::six,   "6",      Rank::seven, "7",      Rank::eight, "8",      Rank::nine,  "9",      Rank::ten,   "10",      Rank::jack,  "J",      Rank::queen, "Q",      Rank::king,  "K",      Rank::ace,   "A"  );  

You can take a look at the Live example.


Fatal error: Call to a member function getElementsByTagName() on a non-object in D:\XAMPP INSTALLASTION\xampp\htdocs\endunpratama9i\www-stackoverflow-info-proses.php on line 72

0 comments:

Post a Comment

Popular Posts

Powered by Blogger.