Как я могу перебрать перечисление?
Я только что заметил, что вы не можете использовать стандартные математические операторы для перечисления, такие как ++ или +=
Итак, каков наилучший способ перебрать все значения в перечислении C++?
29 ответов
Типичный способ заключается в следующем:
enum Foo {
One,
Two,
Three,
Last
};
for ( int fooInt = One; fooInt != Last; fooInt++ )
{
Foo foo = static_cast<Foo>(fooInt);
// ...
}
Конечно, это не работает, если указаны значения перечисления:
enum Foo {
One = 1,
Two = 9,
Three = 4,
Last
};
Это показывает, что перечисление на самом деле не предназначено для итерации. Типичный способ работы с перечислением - использовать его в операторе switch.
switch ( foo )
{
case One:
// ..
break;
case Two: // intentional fall-through
case Three:
// ..
break;
case Four:
// ..
break;
default:
assert( ! "Invalid Foo enum value" );
break;
}
Если вы действительно хотите перечислить, поместите значения перечисления в вектор и выполните итерации по этому. Это будет правильно работать с указанными значениями перечисления.
#include <iostream>
#include <algorithm>
namespace MyEnum
{
enum Type
{
a = 100,
b = 220,
c = -1
};
static const Type All[] = { a, b, c };
}
void fun( const MyEnum::Type e )
{
std::cout << e << std::endl;
}
int main()
{
// all
for ( const auto e : MyEnum::All )
fun( e );
// some
for ( const auto e : { MyEnum::a, MyEnum::b } )
fun( e );
// all
std::for_each( std::begin( MyEnum::All ), std::end( MyEnum::All ), fun );
return 0;
}
С C++11, на самом деле, есть альтернатива: написание простого шаблонного пользовательского итератора.
давайте предположим, что ваше перечисление
enum class foo {
one,
two,
three
};
Этот универсальный код довольно эффективно справится с задачей - поместит его в универсальный заголовок, он будет служить вам для любого перечисления, которое вам может понадобиться для перебора:
#include <type_traits>
template < typename C, C beginVal, C endVal>
class Iterator {
typedef typename std::underlying_type<C>::type val_t;
int val;
public:
Iterator(const C & f) : val(static_cast<val_t>(f)) {}
Iterator() : val(static_cast<val_t>(beginVal)) {}
Iterator operator++() {
++val;
return *this;
}
C operator*() { return static_cast<C>(val); }
Iterator begin() { return *this; } //default ctor is good
Iterator end() {
static const Iterator endIter=++Iterator(endVal); // cache it
return endIter;
}
bool operator!=(const Iterator& i) { return val != i.val; }
};
Вам нужно будет специализировать это
typedef Iterator<foo, foo::one, foo::three> fooIterator;
И тогда вы можете итерировать с помощью диапазона для
for (foo i : fooIterator() ) { //notice the parenteses!
do_stuff(i);
}
Предположение, что у вас нет пробелов в вашем перечислении, все еще верно; нет никакого предположения о количестве битов, фактически необходимых для хранения значения перечисления (благодаря std:: basic_type)
Слишком сложное решение, я делаю так:
enum NodePosition { Primary = 0, Secondary = 1, Tertiary = 2, Quaternary = 3};
const NodePosition NodePositionVector[] = { Primary, Secondary, Tertiary, Quaternary };
for (NodePosition pos : NodePositionVector) {
...
}
Я часто так делаю
enum EMyEnum
{
E_First,
E_Orange = E_First,
E_Green,
E_White,
E_Blue,
E_Last
}
for (EMyEnum i = E_First; i < E_Last; i = EMyEnum(i + 1))
{}
или если не последовательный, но с регулярным шагом (например, битовые флаги)
enum EMyEnum
{
E_First,
E_None = E_First,
E_Green = 0x1,
E_White = 0x2
E_Blue = 0x4,
E_Last
}
for (EMyEnum i = E_First; i < E_Last; i = EMyEnum(i << 1))
{}
Если ваше перечисление начинается с 0, а приращение всегда равно 1.
enum enumType
{
A = 0,
B,
C,
enumTypeEnd
};
for(int i=0; i<enumTypeEnd; i++)
{
enumType eCurrent = (enumType) i;
}
Если нет, я думаю, единственное, почему нужно создать что-то вроде
vector<enumType> vEnums;
добавить элементы и использовать обычные итераторы....
Предполагая, что перечисление нумеруется последовательно, подвержено ошибкам. Кроме того, вы можете перебирать только выбранные перечислители. Если это подмножество маленькое, зацикливание на нем явно может быть элегантным выбором:
enum Item { Man, Wolf, Goat, Cabbage }; // or enum class
for (auto item : {Wolf, Goat, Cabbage}) { // or Item::Wolf, ...
// ...
}
Вы не можете с перечислением. Может быть, enum не совсем подходит для вашей ситуации.
Общепринятым соглашением является присвоение последнему значению перечисления что-то вроде MAX и использование его для управления циклом с использованием int
Приведение переменной к
int&
позволяет вам увеличивать, сохраняя при этом читаемый тип.
#include <iostream>
enum MyEnum
{
ONE,
TWO,
THREE,
FOUR,
};
int main()
{
for (MyEnum v = MyEnum::ONE; v <= MyEnum::FOUR; ++(int&)v)
{
std::cout<<v<<std::endl;
}
return 0;
}
0
1
2
3
То, что не было рассмотрено в других ответах = если вы используете строго типизированные перечисления C++11, вы не можете использовать ++
или же + int
на них. В этом случае требуется более сложное решение:
enum class myenumtype {
MYENUM_FIRST,
MYENUM_OTHER,
MYENUM_LAST
}
for(myenumtype myenum = myenumtype::MYENUM_FIRST;
myenum != myenumtype::MYENUM_LAST;
myenum = static_cast<myenumtype>(static_cast<int>(myenum) + 1)) {
do_whatever(myenum)
}
enum class A {
a0=0, a3=3, a4=4
};
constexpr std::array<A, 3> ALL_A {A::a0, A::a3, A::a4}; // constexpr is important here
for(A a: ALL_A) {
if(a==A::a0 || a==A::a4) std::cout << static_cast<int>(a);
}
constexpr std::array
может повторять даже непоследовательные перечисления без создания экземпляра массива компилятором. Это зависит от таких вещей, как эвристика оптимизации компилятора и от того, берете ли вы адрес массива.
В своих экспериментах я обнаружил, что g++
9,1 с -O3
оптимизирует вышеприведенный массив, если есть 2 непоследовательных значения или довольно много последовательных значений (я проверял до 6). Но он делает это только если у вас есть if
заявление. (Я пробовал оператор, который сравнивал целочисленное значение больше, чем все элементы в последовательном массиве, и он включал итерацию, хотя ни один из них не был исключен, но когда я пропустил оператор if, значения были помещены в память.) Он также встроен 5 значения из непоследовательного перечисления в [одном случае | https://god bolt.org/z/XuGtoc]. Я подозреваю, что это странное поведение связано с глубокой эвристикой, связанной с кэшем и предсказанием ветвлений.
Вот ссылка на простую итерацию теста на God bolt, которая демонстрирует, что массив не всегда создается.
Ценой этой техники является написание элементов enum дважды и синхронизация двух списков.
Вы можете попытаться определить следующий макрос:
#define for_range(_type, _param, _A1, _B1) for (bool _ok = true; _ok;)\
for (_type _start = _A1, _finish = _B1; _ok;)\
for (int _step = 2*(((int)_finish)>(int)_start)-1;_ok;)\
for (_type _param = _start; _ok ; \
(_param != _finish ? \
_param = static_cast<_type>(((int)_param)+_step) : _ok = false))
Теперь вы можете использовать его:
enum Count { zero, one, two, three };
for_range (Count, c, zero, three)
{
cout << "forward: " << c << endl;
}
Его можно использовать для итерации вперед и назад через unsigned, целые числа, перечисления и символы:
for_range (unsigned, i, 10,0)
{
cout << "backwards i: " << i << endl;
}
for_range (char, c, 'z','a')
{
cout << c << endl;
}
Несмотря на неловкое определение, он очень хорошо оптимизирован. Я посмотрел на дизассемблер в VC++. Код чрезвычайно эффективен. Не стоит откладывать, но три для операторов: компилятор будет производить только один цикл после оптимизации! Вы даже можете определить вложенные циклы:
unsigned p[4][5];
for_range (Count, i, zero,three)
for_range(unsigned int, j, 4, 0)
{
p[i][j] = static_cast<unsigned>(i)+j;
}
Очевидно, вы не можете перебирать перечисляемые типы с пробелами.
Вы также можете перегрузить операторы увеличения / уменьшения для вашего перечисляемого типа.
Вот несколько очень читаемых и простых для понимания подходов как для слабо типизированных C и C++ обычных s, так и для строго типизированных C++ es.
Я рекомендую скомпилировать все приведенные ниже примеры с расширением . Это дает вам дополнительную безопасность: если вы забудете указать какое-либо значение перечисления в случае, если ваш компилятор выдаст ошибку времени компиляции ! Это заставляет вас синхронизировать определение перечисления и переключать регистры, что является дополнительной мерой безопасности для вашего кода. Этот совет работает до тех пор, пока вы:
- Покройте все значения перечисления в вашем
switch
случай, и - НЕ иметь
default
случай переключателя. - Построить с флагами.
Я рекомендую вам следовать всем трем пунктам, так как это хорошая практика и позволяет создавать лучший код.
1. Для стандартного слаботипизированного C или C++:
Определение C (это также допустимо для C++):
typedef enum my_error_type_e
{
MY_ERROR_TYPE_SOMETHING_1 = 0,
MY_ERROR_TYPE_SOMETHING_2,
MY_ERROR_TYPE_SOMETHING_3,
MY_ERROR_TYPE_SOMETHING_4,
MY_ERROR_TYPE_SOMETHING_5,
/// Not a valid value; this is the number of members in this enum
MY_ERROR_TYPE_count,
// helpers for iterating over the enum
MY_ERROR_TYPE_begin = 0,
MY_ERROR_TYPE_end = MY_ERROR_TYPE_count,
} my_error_type_t;
Определение С++:
enum my_error_type_t
{
MY_ERROR_TYPE_SOMETHING_1 = 0,
MY_ERROR_TYPE_SOMETHING_2,
MY_ERROR_TYPE_SOMETHING_3,
MY_ERROR_TYPE_SOMETHING_4,
MY_ERROR_TYPE_SOMETHING_5,
/// Not a valid value; this is the number of members in this enum
MY_ERROR_TYPE_count,
// helpers for iterating over the enum
MY_ERROR_TYPE_begin = 0,
MY_ERROR_TYPE_end = MY_ERROR_TYPE_count,
};
Итерация C или C++ по этому перечислению со слабой типизацией :
for (my_error_type_t my_error_type = MY_ERROR_TYPE_begin;
my_error_type < MY_ERROR_TYPE_end;
my_error_type++)
{
switch (my_error_type)
{
case MY_ERROR_TYPE_SOMETHING_1:
break;
case MY_ERROR_TYPE_SOMETHING_2:
break;
case MY_ERROR_TYPE_SOMETHING_3:
break;
case MY_ERROR_TYPE_SOMETHING_4:
break;
case MY_ERROR_TYPE_SOMETHING_5:
break;
case MY_ERROR_TYPE_count:
// This case will never be reached.
break;
}
}
2. Для ограниченного , строго типизированного С++:
Определение С++:
enum class my_error_type_t
{
SOMETHING_1 = 0,
SOMETHING_2,
SOMETHING_3,
SOMETHING_4,
SOMETHING_5,
/// Not a valid value; this is the number of members in this enum
count,
// helpers for iterating over the enum
begin = 0,
end = count,
};
Итерация C++ по этому строго типизированному перечислению (обратите внимание на дополнительные приведения типов, необходимые для принудительного увеличения переменной!):
for (my_error_type_t my_error_type = my_error_type_t::begin;
my_error_type < my_error_type_t::end;
my_error_type = static_cast<my_error_type_t>((size_t)my_error_type + 1))
{
switch (my_error_type)
{
case my_error_type_t::SOMETHING_1:
break;
case my_error_type_t::SOMETHING_2:
break;
case my_error_type_t::SOMETHING_3:
break;
case my_error_type_t::SOMETHING_4:
break;
case my_error_type_t::SOMETHING_5:
break;
case my_error_type_t::count:
// This case will never be reached.
break;
}
}
Также обратите внимание на область видимости. В строго типизированном С++ я использовал
my_error_type_t::
для доступа к каждому члену области. Но, как я продемонстрировал, в слабо типизированном регулярном C-стиле можно добиться очень похожей области видимости, просто добавляя префикс к каждому имени члена
MY_ERROR_TYPE_
. Таким образом, тот факт, что строго типизированный C++ добавляет область видимости, на самом деле не добавляет большой ценности - это просто личное предпочтение в этом отношении. И тот факт, что C++ со строгой типизацией имеет дополнительную безопасность типов, также имеет свои плюсы и минусы. Это может помочь вам в некоторых случаях, но это определенно делает увеличение перечисления и итерацию по нему занозой в заднице, что, честно говоря, означает, что он выполняет свою работу. Затрудняя увеличение переменной области действия, как если бы это было целое число, строго типизированный C ++ делает именно то, для чего он был разработан .
Смотрите также:
- [мой вопрос и ответ] Каковы наиболее часто используемые способы перебора класса enum в C++?
- Мой ответ о некоторых различиях между
enum class
es ( строго типизированные перечисления) и обычныеenum
s ( слабо типизированные перечисления) в C++: как автоматически преобразовать строго типизированное перечисление в int? - Некоторые из моих личных заметок о
-Wall -Wextra -Werror
и другие варианты сборки из моего репозитория eRCaGuy_hello_world .
Вот еще одно решение, которое работает только для смежных перечислений. Это дает ожидаемую итерацию, за исключением уродства в инкременте, который находится там, где он принадлежит, поскольку это то, что сломано в C++.
enum Bar {
One = 1,
Two,
Three,
End_Bar // Marker for end of enum;
};
for (Bar foo = One; foo < End_Bar; foo = Bar(foo + 1))
{
// ...
}
В комментариях уже есть обсуждение std :: initializer_list (C++11). Я упоминаю пример перебора перечисления.
или std :: initializer_list и более простой синтаксис:
enum E {
E1 = 4,
E2 = 8,
// ..
En
};
constexpr std::initializer_list<E> all_E = {E1, E2, /*..*/ En};
а потом
for (auto e : all_E) {
// Do job with e
}
Ссылка Ссылка
В книге Бьярна Страуструпа по языку программирования C++ вы можете прочитать, что он предлагает перегрузить operator++
для вашего конкретного enum
. enum
являются определяемыми пользователем типами, и для этих конкретных ситуаций в языке существует оператор перегрузки.
Вы сможете закодировать следующее:
#include <iostream>
enum class Colors{red, green, blue};
Colors& operator++(Colors &c, int)
{
switch(c)
{
case Colors::red:
return c=Colors::green;
case Colors::green:
return c=Colors::blue;
case Colors::blue:
return c=Colors::red; // managing overflow
default:
throw std::exception(); // or do anything else to manage the error...
}
}
int main()
{
Colors c = Colors::red;
// casting in int just for convenience of output.
std::cout << (int)c++ << std::endl;
std::cout << (int)c++ << std::endl;
std::cout << (int)c++ << std::endl;
std::cout << (int)c++ << std::endl;
std::cout << (int)c++ << std::endl;
return 0;
}
тестовый код: http://cpp.sh/357gb
Помните, что я использую enum class
. Код отлично работает сenum
также. Но я предпочитаюenum class
поскольку они строго типизированы и могут предотвратить ошибку во время компиляции.
Плюсы: перечисления могут иметь любые значения в любом порядке, и их по-прежнему легко перебирать. Имена и значения определяются один раз в первом #define.
Минусы: если вы используете это на работе, вам понадобится целый абзац, чтобы объяснить это вашим коллегам. И раздражает необходимость объявлять память, чтобы дать вашему циклу что-то для перебора, но я не знаю обходного пути, который не ограничивал бы вас перечислениями со смежными значениями (и если перечисление всегда будет иметь смежные значения, В любом случае enum может не так уж много вам покупать.)
//create a, b, c, d as 0, 5, 6, 7
#define LIST x(a) x(b,=5) x(c) x(d)
#define x(n, ...) n __VA_ARGS__,
enum MyEnum {LIST}; //define the enum
#undef x //needed
#define x(n,...) n ,
MyEnum myWalkableEnum[] {LIST}; //define an iterable list of enum values
#undef x //neatness
int main()
{
std::cout << d;
for (auto z : myWalkableEnum)
std::cout << z;
}
//outputs 70567
Уловка объявления списка с неопределенной оболочкой макроса, а затем определения оболочки по-разному в различных ситуациях, имеет много других приложений, кроме этого.
Если вам не нравится загрязнять ваше перечисление конечным элементом COUNT (потому что, возможно, если вы также используете перечисление в переключателе, тогда компилятор предупредит вас о пропущенном регистре COUNT:), вы можете сделать это:
enum Colour {Red, Green, Blue};
const Colour LastColour = Blue;
Colour co(0);
while (true) {
// do stuff with co
// ...
if (co == LastColour) break;
co = Colour(co+1);
}
typedef enum{
first = 2,
second = 6,
third = 17
}MyEnum;
static const int enumItems[] = {
first,
second,
third
}
static const int EnumLength = sizeof(enumItems) / sizeof(int);
for(int i = 0; i < EnumLength; i++){
//Do something with enumItems[i]
}
Расширение ответа @Eponymous: это здорово, но не дает общего синтаксиса. Вот что я придумал:
// Common/EnumTools.h
#pragma once
#include <array>
namespace Common {
// Here we forward-declare metafunction for mapping enums to their values.
// Since C++<23 doesn't have reflection, you have to populate it yourself :-(
// Usage: After declaring enum class E, add this overload in the namespace of E:
// inline constexpr auto allValuesArray(const E&, Commob::EnumAllValuesTag) { return std::array{E::foo, E::bar}; }
// Then `AllValues<NS::E>` will call `allValuesArray(NS::E{}, EnumAllValuesTag)` which will resolve
// by ADL.
// Just be sure to keep it sync'd with your enum!
// Here's what you want to use in, e.g., loops: "for (auto val : Common::AllValues<MyEnum>) {"
struct EnumAllValuesTag {}; // So your allValuesArray function is clearly associated with this header.
template <typename Enum>
static inline constexpr auto AllValues = allValuesArray(Enum{}, EnumAllValuesTag{});
// ^ Just "constexpr auto" or "constexpr std::array<Enum, allValuesArray(Enum{}, EnumAllValuesTag{}).size()>" didn't work on all compilers I'm using, but this did.
} // namespace Common
затем в вашем пространстве имен:
#include "Common/EnumTools.h
namespace MyNamespace {
enum class MyEnum {
foo,
bar = 4,
baz = 42,
};
// Making this not have to be in the `Common` namespace took some thinking,
// but is a critical feature since otherwise there's no hope in keeping it sync'd with the enum.
inline constexpr auto allValuesArray(const MyEnum&, Common::EnumAllValuesTag) {
return std::array{ MyEnum::foo, MyEnum::bar, MyEnum::baz };
}
} // namespace MyNamespace
затем везде, где вам нужно:
for (const auto& e : Common::AllValues<MyNamespace::MyEnum>) { ... }
так что даже если вы напечатали:
namespace YourNS {
using E = MyNamespace::MyEnum;
} // namespace YourNS
for (const auto& e : Common::AllValues<YourNS::E>) { ... }
Я не могу придумать ничего лучше, кроме фактической языковой функции, которую хотят все, просматривающие эту страницу.
Будущая работа:
- Вы должны иметь возможность добавить
constexpr
функция (и, следовательно, метафункция), которая фильтруетCommon::AllValues<E>
предоставитьCommon::AllDistinctValues<E>
для случая перечислений с повторяющимися числовыми значениями, такими какenum { foo = 0, bar = 0 };
. - Бьюсь об заклад, есть способ использовать компилятор
switch
-крывает-все-enum
-значения написатьallValuesArray
так что он ошибается, если перечисление добавило значение.
(Принимая ответ Марски как большой намек...)
Поскольку тело определения перечисления совпадает со списком инициализаторов, это можно сделать без повторения списка элементов, если мы используем простой макрос для записи значений:
#define ITEM_LIST_MACRO Wolf, Goat, Cabbage
enum Item { ITEM_LIST_MACRO }; // Define the enum
// Now iterate through it
for (auto item : { ITEM_LIST_MACRO }) {
}
Плюсы: простота, отсутствие повторений и отсутствие необходимости поддерживать раздражающие значения первого/последнего дозорного. (Действительно, я думаю, что это может быть единственное решение, предложенное до сих пор, которое не требует от пользователя не забывать обновлять маркер «конец» при добавлении нового элемента в список.)
Минусы: не работает с перечислениями с ограниченной областью действия (класс enum), потому что для списка инициализаторов потребуется область действия (Item::Wolf и т. д.). Также не работает, если вы хотите указать значения членов перечисления, а не использовать их по умолчанию.
Для MS-компиляторов:
#define inc_enum(i) ((decltype(i)) ((int)i + 1))
enum enumtype { one, two, three, count};
for(enumtype i = one; i < count; i = inc_enum(i))
{
dostuff(i);
}
Примечание: это намного меньше кода, чем простой шаблонный пользовательский ответ итератора.
Вы можете заставить это работать с GCC, используя typeof
вместо decltype
, но у меня нет этого компилятора под рукой, чтобы убедиться, что он компилируется.
Один из ответов гласит: "Если вы знали, что значения перечисления были последовательными, например перечисление Qt:Key".
Значения Qt::Key не являются последовательными. Некоторые сегменты в перечислении есть.
Этот поток об итерации по всем значениям в enum. На самом деле это возможно в Qt благодаря использованию Meta Object System:
const QMetaObject *metaObject = qt_getQtMetaObject();
QMetaEnum keyEnum = metaObject->enumerator(metaObject->indexOfEnumerator("Key"));
for (int i = 0; i < keyEnum.keyCount(); ++i) {
qDebug() << keyEnum.key(i);
}
Смотрите также QObject::metaObject() и макрос Q_ENUM.
Я думаю, что такие вещи станут проще с C++20? Но я не смотрел на это.
Используя лямбду, я нашел это лучшим (современным) способом перебора перечислений. Это значительно улучшает абстракцию. Можно даже сделать его шаблоном, так что он применим к любому перечислению. Этот код не вызывает проблем с clang(-tidy).
#include <functional>
/// @brief Loop over all enum values where the last enum value is the invalid one
void forEachAction(std::function<void(Enum)> &&doThis) {
for (int value = 0; value = static_cast<int>(Enum::LastValue); ++value ) {
doThis(static_cast<Enum>(value ));
}
}
...
forEachAction([this](Enum value) {
... // what you want to execute for every enum
});
Если вы знали, что значения перечисления были последовательными, например, перечисление Qt:Key, вы могли бы:
Qt::Key shortcut_key = Qt::Key_0;
for (int idx = 0; etc...) {
....
if (shortcut_key <= Qt::Key_9) {
fileMenu->addAction("abc", this, SLOT(onNewTab()),
QKeySequence(Qt::CTRL + shortcut_key));
shortcut_key = (Qt::Key) (shortcut_key + 1);
}
}
Работает как положено.
В C++ нет самоанализа, поэтому вы не можете определять подобные вещи во время выполнения.
Большинство решений основаны на циклах в диапазоне (MIN, MAX), но не учитывают тот факт, что в перечислении могут быть дыры.
Мои предложения:
for (int i = MYTYPE_MIN; i <= MYTYPE_MAX; i++) {
if (MYTYPE_IsValid(i)) {
MYTYPE value = (MYTYPE)i;
// DoStuff(value)
}
}
Просто сделайте массив целых и сделайте цикл по массиву, но заставьте последний элемент сказать -1 и используйте его для условия выхода.
Если enum это:
enum MyEnumType{Hay=12,Grass=42,Beer=39};
затем создайте массив:
int Array[] = {Hay,Grass,Beer,-1};
for (int h = 0; Array[h] != -1; h++){
doStuff( (MyEnumType) Array[h] );
}
Это не ломается независимо от целых чисел в представлении, пока проверка -1 не сталкивается ни с одним из элементов, конечно.