Цикл по значениям перечисления

насколько ужасно - или вполне приемлемо-индексировать цикл на перечислении?

У меня определено перечисление. Значения литералов являются значениями по умолчанию. Присвоенные значения не имеют никакого значения, не будут иметь никакого значения, и значения любых литералов, добавленных в будущем, также не будут иметь никакого значения. Это просто определено, чтобы ограничить допустимые значения и сделать вещи проще следовать. Поэтому значения всегда будут начинаться с 0 и увеличится на 1.

могу ли я настроить цикл следующим образом:

enum MyEnum
{
    value1,
    value2,
    value3,
    maxValue
}

for(MyEnum i = value1; i < maxValue; i = static_cast<MyEnum>(i+1)){}

9 ответов


Что касается меня, то все в порядке. Я уверен, что какой-то пурист где-то там будет волноваться, но что касается спецификации языка, этот код будет работать правильно, поэтому вы должны чувствовать себя свободно, если это облегчит вашу жизнь.


Я написал итератор перечисления некоторое время назад для этих случаев

enum Foo {
 A, B, C, Last
};

typedef litb::enum_iterator<Foo, A, Last> FooIterator;

int main() {
  FooIterator b(A), e;
  std::cout << std::distance(b, e)  << " values:" << std::endl;
  std::copy(b, e, std::ostream_iterator<Foo>(std::cout, "\n"));

  while(b != e) doIt(*b++);
}

если вы заинтересованы, вот код. Если вам нравится, вы можете расширить его, чтобы быть итератором случайного доступа, предоставив +, <, [] и друзей. Алгоритмы типа std::distance поблагодарит вас, предоставив O(1) сложность времени для итератора с произвольным доступом.

#include <cassert>

namespace litb {

template<typename Enum, Enum Begin, Enum End>
struct enum_iterator 
  : std::iterator<std::bidirectional_iterator_tag, Enum> {
  enum_iterator():c(End) { }
  enum_iterator(Enum c):c(c) { }

  enum_iterator &operator=(Enum c) {
    this->assign(c);
    return *this;
  }

  enum_iterator &operator++() {
    this->inc();
    return *this;
  }

  enum_iterator operator++(int) {
    enum_iterator cpy(*this);
    this->inc();
    return cpy;
  }

  enum_iterator &operator--() {
    this->dec();
    return *this;
  }

  enum_iterator operator--(int) {
    enum_iterator cpy(*this);
    this->dec();
    return cpy;
  }

  Enum operator*() const {
    assert(c != End && "not dereferencable!");
    return c;
  }

  bool equals(enum_iterator other) const {
    return other.c == c;
  }

private:
  void assign(Enum c) {
    assert(c >= Begin && c <= End);
    this->c = c; 
  }

  void inc() {
    assert(c != End && "incrementing past end");
    c = static_cast<Enum>(c + 1);
  }

  void dec() {
    assert(c != Begin && "decrementing beyond begin");
    c = static_cast<Enum>(c - 1);
  }

private:
  Enum c;
};

template<typename Enum, Enum Begin, Enum End>
bool operator==(enum_iterator<Enum, Begin, End> e1, enum_iterator<Enum, Begin, End> e2) {
  return e1.equals(e2);
}

template<typename Enum, Enum Begin, Enum End>
bool operator!=(enum_iterator<Enum, Begin, End> e1, enum_iterator<Enum, Begin, End> e2) {
  return !(e1 == e2);
}

} // litb

это может быть полезно сделать.

enum MyEnum
{
    first,
    value1,
    value2,
    value3,
    last
}

это нормально, если вы специально не задаете значение значения перечисления. Ваш пример прекрасен, но это может быть плохо:

// this would be bad
enum MyEnum
{
    value1,
    value2 = 2,
    value3,
    maxValue
};

как указано в стандарте в 7.2 / 1:

перечислитель-определение без инициализатора перечислитель получает значение, полученное путем увеличения значения предыдущего перечислитель по одному.


Я не знаю никакого конкретного использования этого, что на самом деле полезно, и я думаю, что это плохой код. Что делать, если положить такие вещи, как это видно во многих библиотеках ?

enum MyEnum
{
    value1,
    value2,
    value3,
    maxValue = 99999;
};

это непросто...

единственным решением, которое я когда-либо нашел, было фактически реализовать класс над перечислением, а затем использовать макрос для определения перечисления...

DEFINE_NEW_ENUM(MyEnum, (value1)(value2)(value3))

основная идея состоит в том, чтобы хранить значения в контейнере STL, хранящиеся статически в классе шаблона в зависимости от MyEnum символ. Таким образом, вы получаете совершенно определенную итерацию, и вы даже получаете недопустимое состояние за ничто (из-за end()).

я использовал отсортированный vector of pair<Enum,std::string> чтобы я мог извлечь выгоду из красивой печати в журналах и длительной сериализации (и потому, что это быстрее, чем ассоциативный контейнер для небольших наборов данных и занимает меньше памяти).

я еще не нашел лучшего способа, C++11 наконец-то получает итерацию на перечислениях или нет ?


поэтому значения всегда будут начинаться с 0 и увеличиваться на 1.

имейте в виду, что если это не записано в стандарт кодирования, где вы работаете, когда программист обслуживания приходит через 6 месяцев и вносит изменения в перечисление, например:

enum MyEnum 
{ 
    value1, 
    value2 = 5, 
    value3, 
    maxValue 
}

из-за нового требования, вы сможете объяснить, почему их законная смена нарушил приложение.

чтобы присоединить значения к именам, вы можете использовать карта:

typedef std::map<std::string,int> ValueMap;
ValueMap myMap;
myMap.insert(make_pair("value1", 0));
myMap.insert(make_pair("value2", 1));
myMap.insert(make_pair("value3", 2));

for( ValueMap::iterator iter = theMap.begin(); iter != theMap.end(); ++iter )
{
  func(iter->second);
}

если имена не имеют значения, вы можете использовать массив C-style:

int values[] = { 0, 1, 2 };
int valuesSize = sizeof(values) / sizeof(values[0]);

for (int i = 0; i < valuesSize; ++i)
{
  func(values[i]);
}

или сопоставимый метод с использованием std:: vector.

кроме того, если то, что вы говорите, правда, и the values will always start at 0 and increase by 1 тогда я смущен, почему это не сработает:

const int categoryStartValue = 0;
const int categoryEndValue = 4;

for (int i = categoryStartValue; i < categoryEndValue; ++i)
{
  func(i);
}

Я обычно определяю начальные и конечные значения в моих перечислениях, когда мне нужна итерация, со специальным синтаксисом, таким как следующее (с комментариями стиля doxygen для уточнения):

enum FooType {
    FT__BEGIN = 0,        ///< iteration sentinel
    FT_BAR = FT__BEGIN,   ///< yarr!
    FT_BAZ,               ///< later, when the buzz hits  
    FT_BEEP,              ///< makes things go beep
    FT_BOOP,              ///< makes things go boop
    FT__END              ///< iteration sentinel
}

FT_ помогает с пространством имен (чтобы избежать столкновений между перечислениями), а двойные подчеркивания помогают различать реальные значения и часовые итерации.

Sidenote:

написание этого я начинаю беспокоиться о двойном подчеркивании, которое не разрешено для пользователя идентификатор (это зарезервированная реализация, IIRC?), но я еще не столкнулся с проблемой (после 5-6 лет кодирования в этом стиле). Это может быть нормально, так как я всегда помещаю все свои типы в пространство имен, чтобы избежать загрязнения и вмешательства в глобальное пространство имен.


вы можете обернуть тело цикла с помощью оператора switch для защиты от не инкрементных значений. Это потенциально очень медленно,как в случае maxValue = 9999;, но это может быть не самое худшее. Я вижу этот стиль здесь все время в наших кодовых базах

enum MyEnum  {
 value1,  
 value2,  
 value3,  
 maxValue  
}  
for(MyEnum i = value1; i < maxValue; i = static_cast<MyEnum>(i+1)){
     switch(i)
     {
       case value1:
       case value2:
       case value3:
                    //actual code
     }

}