(Как) я могу подсчитать элементы в перечислении?


98

Этот вопрос пришел мне в голову, когда у меня было что-то вроде

enum Folders {FA, FB, FC};

и хотел создать массив контейнеров для каждой папки:

ContainerClass*m_containers[3];
....
m_containers[FA] = ...; // etc.

(Использование карты это гораздо элегантнее использования: std::map<Folders, ContainerClass*> m_containers;)

Но вернемся к моему первоначальному вопросу: что, если я не хочу жестко программировать размер массива, есть ли способ выяснить, сколько элементов находится в папках? (Не полагаясь, например, на FCто, чтобы быть последним элементом в списке, который позволил бы что-то вроде, ContainerClass*m_containers[FC+1]если я не ошибаюсь.)


Это сообщение может ответить на ваш вопрос: stackoverflow.com/questions/1390703/enumerate-over-an-enum-in-c .
StackedCrooked

1
Вопрос немного недооценен. В соответствии со стандартом C ++ int(FA) | int(FB) | int (FC)это также допустимое значение Foldersпеременной. Если вы выбираете размер m_containersтак, чтобы любая Foldersпеременная была допустимым индексом, этого [FC+1]было бы недостаточно.
MSalters

Я спросил очень
похожую

Я бы порекомендовал вам решение от stackoverflow.com/a/60216003/12894563 и улучшенный вариант от stackoverflow.com/a/60271408/12894563
ixjxk

Ответы:


123

На самом деле нет хорошего способа сделать это, обычно вы видите дополнительный элемент в перечислении, т.е.

enum foobar {foo, bar, baz, quz, FOOBAR_NR_ITEMS};

Итак, вы можете:

int fuz[FOOBAR_NR_ITEMS];

Хотя все равно не очень хорошо.

Но, конечно, вы понимаете, что просто количество элементов в перечислении небезопасно, например,

enum foobar {foo, bar = 5, baz, quz = 20};

количество элементов будет 4, но целочисленные значения значений перечисления будут выходить за пределы диапазона индекса массива. Использование значений перечисления для индексации массива небезопасно, вам следует рассмотреть другие варианты.

edit: как и просили, сделал специальную запись больше.


27
Назовите это LAST или ALWAYS_AT_END или как-нибудь не так загадочно. Сделайте так, чтобы он торчал . Чтобы последующие сопровождающие случайно не добавили новые записи после маркера завершения.
Мартин Йорк,

3
Хорошо это или плохо, но именно такой подход мы используем в нашей организации. Обычно мы называем его FINAL_enumname_ENTRY, например FINAL_foobar_ENTRY. Я также видел, как люди использовали отдельную статическую переменную const FOOBAR_COUNT, определенную сразу после объявления перечисления, подход, который немного более подвержен ошибкам.
Дэррил,

1
По крайней мере, довольно легко увидеть, что "enum foo {a = 10, LAST}" будет странным. И я подумал, что "int arr [LAST]" в этом случае будет 11 элементов, а не 2, поэтому большая часть кода будет работать (но вы тратите память на недопустимые значения индекса)
Code Abominator

32

Для C ++ доступны различные методы безопасного перечисления , и некоторые из них (например, предложенный, но никогда не представленный Boost.Enum ) включают поддержку получения размера перечисления.

Самый простой подход, который работает как в C, так и в C ++, - принять соглашение об объявлении значения ... MAX для каждого из ваших типов перечислений:

enum Folders { FA, FB, FC, Folders_MAX = FC };
ContainerClass *m_containers[Folders_MAX + 1];
....
m_containers[FA] = ...; // etc.

Изменить : Что касается { FA, FB, FC, Folders_MAX = FC}против {FA, FB, FC, Folders_MAX]: я предпочитаю устанавливать значение ... MAX на последнее допустимое значение перечисления по нескольким причинам:

  1. Имя константы технически более точное (так как Folders_MAXдает максимально возможное значение перечисления).
  2. Лично я чувствую, что Folders_MAX = FCвыделяется среди других записей немного больше (что немного затрудняет случайное добавление значений перечисления без обновления максимального значения, проблема, на которую ссылается Мартин Йорк).
  3. GCC включает полезные предупреждения, такие как «значение перечисления не включено в переключатель» для такого кода, как следующий. Разрешение Folders_MAX == FC + 1 нарушает эти предупреждения, так как вы получаете кучу ... MAX значений перечисления, которые никогда не должны включаться в switch.
переключатель (папка) 
{
  case FA: ...;
  case FB: ...;
  // Ой, забыл FC!
}

3
Почему бы не сделать enum Folders { FA, FB, FC, Folders_MAX }; ContainerClass *m_containers[Folders_MAX];:?
Bill

1
Я предпочитаю прояснить, что последнее - это число, и все они имеют одно и то же имя благодаря:struct SomeEnum { enum type {FA, FB, FC, NB__};};
Люку Эрмитту,

2
На самом деле я считаю, что эти "полезные" предупреждения - заноза в заднице. Мне нравятся хорошие предупреждения. Я всегда устанавливаю -Wall -pedantic и т. Д., Когда разрабатываю, но эти предупреждения просто глупые. Есть только несколько худших, например, подсказка для && || и & ^ | приоритет оператора. Я имею в виду, я думал , что Java был язык няни, что ад происходит с C и C ++ ...
которым

2
Обратной стороной наличия Folders_max = FC является то, что вам нужно изменять его каждый раз, когда вы добавляете что-то в перечисление!
Этьен

2
перечислить папки {FA, FB, FC, Folders_MIN = FA, Folders_MAX = FC}; Просто чтобы подчеркнуть, что это полезно для повторения?
gjpc

7

Как насчет черт в стиле STL? Например:

enum Foo
{
    Bar,
    Baz
};

написать

std::numeric_limits<enum Foo>::max()

специализация (возможно, constexpr, если вы используете c ++ 11). Затем в вашем тестовом коде укажите любые статические утверждения для поддержания ограничений, которые std :: numeric_limits :: max () = last_item.


2
К сожалению, это не сработает в соответствии с этим ответом .
rr-

3
std::numeric_limits<enum Foo>::max()всегда возвращает ноль ... (см. вопрос для связанного ответа) Протестировано на обычных enums ( enum Foo { ... }), enums ( enum class Foo : uint8_t { ... }) с подсказками типа с gcc 5.2.0 @ Linux и MinGW 4.9.3 @ Windows.
рр-

1
(... и в случае std::numeric_limits<std::underlying_type<Foo>::type>::max(), он возвращает максимальное значение базового типа, т.е. 0xFFFFFFFF для 32-битных целых чисел, что бесполезно в этом контексте.)
rr-

1
Странно, потому что у меня есть рабочий код, который делает то, что я описал. namespace gx { enum struct DnaNucleobase : char { A, C, G, T }; } Затем: namespace std { template<> struct numeric_limits<enum ::gx::DnaNucleobase> { typedef enum ::gx::DnaNucleobase value_type; static constexpr value_type max() { return value_type::T; } (...) И std::cout << std::numeric_limits<::gx::DnaNucleobase>::max() << std::endl;печатает ожидаемый результат. Протестировано с использованием версий gcc 5.2.1 и 4.8 / 4.9.
Войцех Мигда,

2
-1; пингуйте меня, если вы измените ответ, чтобы я мог отменить голос против. Этот ответ - плохое соглашение. Это неправильное использование концепции numeric_limits<T>::max(). Единственное, что функция могла бы разумно вернуть, - это наивысшее перечислимое значение. Он вернется 2, но OP (в данном конкретном случае) должен вернуть его 3. Если у вас есть значения, отличные от значений по умолчанию для enum ( FB = 2057), все ставки отключены, и невозможно даже + 1взломать ошибку «off-by-one». Если бы было numeric_limits<T>::number_of_elements_of_the_set()(или более короткое имя), его можно было бы использовать без двусмысленности.
Мерлин Морган-Грэм

3

Добавьте в конец перечисления запись с именем Folders_MAX или что-то подобное и используйте это значение при инициализации массивов.

ContainerClass* m_containers[Folders_MAX];

2

Мне нравится использовать перечисления в качестве аргументов своих функций. Это простой способ предоставить фиксированный список «вариантов». Проблема с ответом, получившим наибольшее количество голосов, заключается в том, что с его помощью клиент может указать «недопустимый вариант». В качестве дополнительного решения я рекомендую сделать по сути то же самое, но использовать константу int вне перечисления, чтобы определить их количество.

enum foobar { foo, bar, baz, quz };
const int FOOBAR_NR_ITEMS=4;

Это неприятно, но это чистое решение, если вы не меняете перечисление без обновления константы.


1

Я действительно не вижу никакого способа действительно получить количество значений в перечислении в C ++. Любое из упомянутых выше решений работает до тех пор, пока вы не определяете значение своих перечислений, если вы определяете свое значение, которое может возникнуть в ситуациях, когда вы создаете слишком большие или слишком маленькие массивы

enum example{ test1 = -2, test2 = -1, test3 = 0, test4 = 1, test5 = 2 }

в этом примере результат будет создавать массив из 3 элементов, когда вам нужен массив из 5 элементов

enum example2{ test1 , test2 , test3 , test4 , test5 = 301 }

в этом примере результат будет создавать массив из 301 элемента, когда вам нужен массив из 5 элементов

Лучший способ решить эту проблему в общем случае - это перебрать ваши перечисления, но, насколько я знаю, это еще не в стандарте

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.