Я только что заметил, что вы не можете использовать стандартные математические операторы в перечислении, например ++ или + =
Итак, как лучше всего перебирать все значения в перечислении C++?
Эти ответы, похоже, не решают проблему, заключающуюся в том, что int может быть недостаточно большим! ([C++03: 7.2/5])
Интересно, что вы можете определять operator++ в перечислениях; однако так можно сделать for(Enum_E e = (Enum_E)0; e < ENUM_COUNT; e++). Обратите внимание, что вам нужно преобразовать 0 в Enum_E, потому что C++ запрещает операторы присваивания для перечислений.
Если бы существовал оператор времени компиляции, аналогичный тому, как работает sizeof, который мог бы испускать литерал std :: initializer_list, состоящий из значений перечисления, у нас было бы решение и не было бы никаких накладных расходов во время выполнения.
Один из множества подходов: Когда перечисления просто недостаточно: классы перечисления для C++. А если вы хотите чего-то более инкапсулированного, попробуйте этот подход от Джеймса Канце.





Вы не можете использовать перечисление. Возможно, перечисление не подходит для вашей ситуации.
Обычное соглашение - назвать последнее значение перечисления чем-то вроде MAX и использовать это для управления циклом с помощью int.
Здесь есть несколько примеров, демонстрирующих обратное. В своем собственном утверждении вы сами себе противоречите (вторая строка).
Типичный способ выглядит следующим образом:
enum Foo {
One,
Two,
Three,
Last
};
for ( int fooInt = One; fooInt != Last; fooInt++ )
{
Foo foo = static_cast<Foo>(fooInt);
// ...
}
Обратите внимание, что перечисление Last должно пропускаться итерацией. Используя это «поддельное» перечисление Last, вам не нужно обновлять условие завершения в цикле for до последнего «реального» перечисления каждый раз, когда вы хотите добавить новое перечисление.
Если вы хотите добавить больше перечислений позже, просто добавьте их перед Last. Цикл в этом примере по-прежнему будет работать.
Конечно, это не работает, если указаны значения перечисления:
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;
}
Если вы действительно хотите перечислить, вставьте значения перечисления в вектор и перебирайте его. Это также будет правильно работать с указанными значениями перечисления.
Обратите внимание, что в первой части примера, если вы хотите использовать 'i' в качестве перечисления Foo, а не int, вам необходимо статическое преобразование, например: static_cast <Foo> (i)
Также вы пропускаете Последний в цикле. Должно быть <= Последний
@Tony Last должен быть пропущен. Если вы хотите добавить другие перечисления позже, добавьте их перед Last ... цикл в первом примере по-прежнему будет работать. Используя «фальшивое» последнее перечисление, вам не нужно обновлять условие завершения в цикле for до последнего «реального» перечисления каждый раз, когда вы хотите добавить новое перечисление.
За исключением того, что вы фактически выделили память, когда перечисление, при условии, что оно имеет нулевой индекс и строго непрерывно, может выполнять эту задачу без выделения памяти.
Было бы здорово вставить комментарий @Tony в ответ. Меня это действительно сбило с толку.
Вы также можете использовать макросы, но это означает, что вам также необходимо определить свое перечисление через макрос. Таким образом, вы действительно избегаете кастинга и вообще не должны использовать вектор!
@tsusanka На самом деле он работает по той же логике, что и итерация по контейнеру STL. Так же, как std::end() возвращает итератор «после окончания», поэтому вы можете использовать i < std::end(whatever) или it != std::end(whatever), может быть полезно добавить фиктивное значение в конце enum, чтобы вы могли использовать i < Last. Точно так же, если перечисление предназначено для использования для итерации, также может быть полезно добавить фиктивный элемент First, базовое значение которого совпадает с фактическим значением первого элемента. В этом ответе, например, это может быть что-то вроде First = One.
Обратите внимание: чтобы это определение перечисления было безопасным для обновлений, необходимо определить значение UNKNOWN = 0. Кроме того, я бы предложил просто отбросить случай default при переключении значений перечисления, поскольку он может скрыть случаи, когда обработка значений была забыта до времени выполнения. Вместо этого следует жестко закодировать все значения и использовать поле UNKNOWN для обнаружения несовместимости.
@timidpueo Вот почему я предпочитаю называть последнюю запись Count. Делает это немного более очевидным.
В C++ нет интроспекции, поэтому вы не можете определить такие вещи во время выполнения.
Не могли бы вы объяснить мне, почему для перебора перечисления потребуется «самоанализ»?
Может термин Отражение?
Я пытаюсь сказать 2 вещи: 1) По многим другим ответам С ++ может это сделать, поэтому, если вы собираетесь сказать, что это невозможно, требуется ссылка или дальнейшее разъяснение. 2) В нынешнем виде это в лучшем случае комментарий, но никак не ответ.
Тогда проголосуйте против моего ответа - я думаю, вы его более чем оправдали
Я снова впихну 2 комментария: 1) Я не голосую против, потому что считаю, что получение отрицательного голоса демотивирует участие на сайте, я считаю это контрпродуктивным 2) Я все еще не понимаю, что вы пытаетесь сказать, но это звучит как вы понимаете то, что я не понимаю, и в этом случае я бы предпочел, чтобы вы подробно остановились, а не удаляли отклоненный ответ.
C++ действительно поддерживает самоанализ типов. См. Typeid и dynamic_cast
Самоанализ - правильный термин (никаких манипуляций, только осмотр). Хотя утверждение ответов неверно из-за методов RTTI через typeid и dynamic_cast, оба они не являются решением проблемы. Так что технически «самоанализ C++ не охватывает поиск значений перечисления» или что-то еще было бы лучшим ответом.
Если ваше перечисление начинается с 0, а приращение всегда равно 1.
enum enumType
{
A = 0,
B,
C,
enumTypeEnd
};
for(int i=0; i<enumTypeEnd; i++)
{
enumType eCurrent = (enumType) i;
}
Если нет, я думаю, единственная причина - создать что-то вроде
vector<enumType> vEnums;
добавить элементы и использовать обычные итераторы ....
Вы также можете перегрузить операторы увеличения / уменьшения для вашего перечислимого типа.
Вы не можете перегружать какие-либо операторы перечислимых типов C или C++. Если только вы не создали структуру / класс, имитирующий перечисление значений.
C++ допускает перегрузку операторов перечислений. См. stackoverflow.com/questions/2571456/….
Перегрузка приращения / декремента требует принятия решения о том, что делать при переполнении.
Вы можете попробовать определить следующий макрос:
#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;
}
Его можно использовать для перебора назад и вперед по беззнаковым, целым числам, перечислениям и символам:
for_range (unsigned, i, 10,0)
{
cout << "backwards i: " << i << endl;
}
for_range (char, c, 'z','a')
{
cout << c << endl;
}
Несмотря на неудобное определение, он очень хорошо оптимизирован. Посмотрел дизассемблер на VC++. Код чрезвычайно эффективен. Не откладывайте, но три оператора for: компилятор произведет только один цикл после оптимизации! Вы даже можете определять замкнутые циклы:
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++.
_A1 - недопустимое имя, это ведущее подчеркивание с последующей заглавной буквой.
То, что не было рассмотрено в других ответах = если вы используете строго типизированные перечисления 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)
}
... но С ++ 11 вводит диапазон, основанный на том, что показано в других ответах. :-)
слишком сложно эти решения, мне это нравится:
enum NodePosition { Primary = 0, Secondary = 1, Tertiary = 2, Quaternary = 3};
const NodePosition NodePositionVector[] = { Primary, Secondary, Tertiary, Quaternary };
for (NodePosition pos : NodePositionVector) {
...
}
Я не знаю, почему это было отклонено. Это разумное решение.
Я полагаю, это произошло потому, что записи нужно хранить в двух местах.
Разрешает ли C++ синтаксис for (NodePosition pos : NodePositionVector)? Насколько мне известно, это синтаксис Java, и вам понадобятся итераторы в C++ для выполнения чего-то эквивалентного.
@thegreatjedi Начиная с C++ 11 вы можете, даже проще: for (auto pos: NodePositionVector) {..}
@thegreatjedi Было бы быстрее найти или даже скомпилировать тестовую программу, чем задавать этот вопрос. Но да, начиная с C++ 11, это совершенно правильный синтаксис C++, который компилятор переводит в эквивалентный (и гораздо более подробный / менее абстрагирующий) код, обычно через итераторы; см. cppreference. И, как сказал Энзойз, C++ 11 также добавил auto, поэтому вам не нужно явно объявлять тип элементов, если вам (A) не нужно использовать оператор преобразования или (B) не нравится auto для некоторая причина. Большинство пользователей диапазона for используют auto AFAICT
Вот расширение этого подхода: enum class Foo { a, b }; template <typename T> struct enum_values; template <> struct enum_values<Foo> { static constexpr std::array<Foo,2> values() { return { Foo::a, Foo::b }; } }; Затем вы можете использовать for (Foo f : enum_values<Foo>::values()), поэтому вам не нужно иметь имена переменных, названные по типам (NodePositionVector). Конечно, вам все еще нужно поддерживать его. Было бы неплохо, если бы язык предоставлял такую функциональность.
@Enzojz: Просто хочу подчеркнуть, что этот код не работает с C++ старше 11: range-based 'for' loops only available with '-std=c++11'
#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;
}
Спасибо! Обратите внимание, что если вы пересекаете файлы / классы и если совместимость с MS вызывает проблемы с объявленными в заголовке нецелочисленными константами, мой компилятор помогает явно указать размер в типе в заголовке: static const Type All[3];, а затем я возможность инициализации в источнике: const MyEnum::Type MyEnum::All[3] = { a, b, c }; Перед этим я получал неприятные ошибки Error in range-based for... (потому что массив имел неизвестный размер). Разобрался благодаря связанный ответ
Версия с массивом очень удобна для копирования и вставки. Кроме того, наиболее удовлетворительный ответ - «НЕТ» или «только по порядку». Возможно, даже для макросов.
это может быть хорошим решением для перечислений с небольшим количеством элементов, но для перечислений с большим количеством элементов оно не должно подходить хорошо.
В 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;
И затем вы можете выполнить итерацию, используя range-for
for (foo i : fooIterator() ) { //notice the parentheses!
do_stuff(i);
}
Предположение, что в вашем перечислении нет пробелов, остается верным; нет никаких предположений о количестве битов, действительно необходимых для хранения значения перечисления (благодаря std :: lower_type)
обратите внимание, что «общий код» привязан к «foo» (см. код посередине), поэтому вы не можете использовать его с другими определениями перечислений.
@lepe? Вы просто делаете другой typedef для другого перечисления.
@lepe Это все равно что сказать, что std::vector не является универсальным, потому что std::vector<foo> привязан к foo.
@AndrewLazarus, @Kyle: Итак, если у меня есть другое перечисление, например: enum color { green = 20, white = 30, red = 40 }, как я могу использовать приведенный выше код, не меняя часть foo operator*() { ...? Может кто-нибудь объяснить с помощью примера кода? (Извините, мои навыки C++ еще не настолько развиты).
@lepe Из-за пропуска вам придется специализировать оператор ++, чтобы использовать val + = 10 вместо ++ val.
@AndrewLazarus: извините, моя проблема ... Я установил эти значения, чтобы они отличались от foo, но меня это не беспокоит. Пожалуйста, измените мой предыдущий комментарий и установите enum равным: enum color { green, black, white, blue, red }. Как мне использовать код, предложенный Франческо, чтобы я мог повторять либо foo, либо color, не изменяя его код? Если возможно, я хотел бы увидеть пример кода.
typedef Iterator<color, color::green, color::red> colorIterator; Убедитесь, что вы понимаете, как работают экземпляры шаблонов.
@lepe У каждого перечисления будет свой тип итератора своя (специализированный на шаблоне), точно так же, как вам понадобится другой тип std::vector для хранения различных типов объектов. Это достигается за счет специализации шаблона, как в предыдущем комментарии. Дело в том, что вам не нужно переопределять шаблон Iterator для каждого уникального типа перечисления, точно так же, как вам не нужно переопределять шаблон std::vector для каждого типа объекта; вы просто специализируете это, что можно сделать с однострочным typedef.
@lepe Чтобы решить проблему «пропусков» более общим (хотя и ограниченным) способом, см. мой ответ ниже. Также обратите внимание, что вы можете отметить в комментарии только одного человека (поэтому я не получал уведомления, когда вы пытались отметить меня).
О, я вижу проблему - foo operator*() { ... должен быть C operator*() { ....
@KyleStrand: Ты понял! теперь это имеет смысл. Следует ли обновлять код? Спасибо всем за ваши объяснения.
На самом деле я создал здесь несколько более общую версию: github.com/BatmanAoD/PublicPaste/blob/master/GenericCppCode/…
И под «более общим» я подразумеваю, что он полагается на перегруженную глобальную функцию IsEnumValid (которую можно легко переместить в пространство имен), чтобы определить, являются ли определенные int-значения допустимыми значениями перечисления, что решает проблему «пропуска». Один из альтернативных вариантов - сделать функцию проверки достоверности одним из параметров шаблона. Другой вариант - потребовать наличия статического массива для итерации, как в stackoverflow.com/a/26910769/1858225
Когда это последовательное порядковое числовое перечисление, это будет работать. Как насчет случая, когда у вас есть непересекающийся набор битовых флагов? Включая их комбинации.
Для компиляторов 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, но на данный момент у меня нет под рукой этого компилятора, чтобы убедиться, что он компилируется.
Это было написано примерно через 5 лет после того, как decltype стал стандартом C++, поэтому вам не следует рекомендовать устаревший typeof из древнего GCC. Смутно недавний GCC отлично справляется с decltype. Есть и другие проблемы: приведение типов в стиле C не рекомендуется, а макросы - хуже. Правильные функции C++ могут дать такую же общую функциональность. Это было бы лучше переписать, чтобы использовать static_cast и функцию шаблона: template <typename T> auto inc_enum(T const t) { return static_cast<T>(static cast<int>(t) + 1); }. И приведения не требуются для не-enum class. В качестве альтернативы операторы могут быть перегружены по типу enum (TIL)
Если вам не нравится загрязнять ваше перечисление последним элементом 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);
}
Если бы вы знали, что значения перечисления были последовательными, например перечисление 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);
}
}
Работает как положено.
Я часто так делаю
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 EAnimalCaps
{
E_First,
E_None = E_First,
E_CanFly = 0x1,
E_CanWalk = 0x2
E_CanSwim = 0x4,
E_Last
}
class MyAnimal
{
EAnimalCaps m_Caps;
}
class Frog
{
Frog() :
m_Caps(EAnimalCaps(E_CanWalk | E_CanSwim))
{}
}
for (EAnimalCaps= E_First; i < E_Last; i = EAnimalCaps(i << 1))
{}
но что толку от побитовой печати значений?
Использовать перечисления для создания битовых масок. например объединить несколько вариантов в одной переменной, а затем использовать FOR для проверки каждого варианта. Исправил мой пост с лучшим примером.
Я все еще не могу его использовать (и в вашем сообщении все еще показан старый пример)! Использование enum в качестве битовых масок действительно полезно, но не может соединить точки! Не могли бы вы подробно рассказать о своем примере, вы также можете добавить дополнительный код.
@anu К сожалению, не видел вашего комментария. Добавлен класс Frog как пример битовой маски
Разве вам не нужно начинать с элемента 0x1? В противном случае вы будете сдвигать много нулей и, таким образом, оставаться в первом элементе бесконечно
Просто создайте массив целых чисел и выполните цикл по массиву, но сделайте последний элемент равным -1 и используйте его для условия выхода.
Если перечисление:
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 не сталкивается с одним из элементов.
Вот еще одно решение, которое работает только для непрерывных перечислений. Он дает ожидаемую итерацию, за исключением уродства в приращении, которому он и принадлежит, поскольку это то, что сломано в 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))
{
// ...
}
Увеличение можно сократить до foo = Bar(foo + 1).
Спасибо, HolyBlackCat, я учел ваше отличное предложение! Я также заметил, что у Riot есть почти то же самое решение, но оно соответствует строгой типизации (и, следовательно, более многословно).
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://godbolt.org/z/XuGtoc]. Я подозреваю, что такое странное поведение связано с глубокой эвристикой, связанной с кешами и предсказанием ветвлений.
Вот ссылка на простую итерацию теста на Godbolt, который демонстрирует, что массив не всегда создается.
Цена этого метода состоит в том, чтобы дважды написать элементы перечисления и синхронизировать два списка.
Мне нравится простая семантика цикла for, подобная диапазону, и я думаю, что она будет развиваться еще больше, поэтому мне нравится это решение.
Предположение, что перечисление пронумеровано последовательно, подвержено ошибкам. Более того, вы можете захотеть перебрать только выбранные перечислители. Если это подмножество невелико, явный цикл по нему может быть элегантным выбором:
enum Item { Man, Wolf, Goat, Cabbage }; // or enum class
for (auto item : {Wolf, Goat, Cabbage}) { // or Item::Wolf, ...
// ...
}
Думаю, это хороший вариант. Должен быть частью более новой спецификации C++, чем я использовал, когда задавал предполагаемый вопрос?
да. Он выполняет итерацию по std :: initializer_list <Item>. связь.
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]
}
Это решение создаст излишне статические переменные в памяти, в то время как цель enum - просто создать `` маску '' для встроенных констант.
Если не поменял на constexpr static const int enumItems[]
В книге Бьярна Страуструпа по языку программирования 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, поскольку они строго типизированы и могут предотвратить ошибку во время компиляции.
Этот пост был отклонен. Есть ли причина, по которой он не ответит на вопрос?
Причина, вероятно, в том, что это ужасное решение с архитектурной точки зрения: оно заставляет вас писать глобальную логику, привязанную к конкретному компоненту (вашему перечислению), более того, если ваше перечисление действительно изменяется по какой-либо причине, вы вынуждены редактировать свой + +, поскольку подход не является устойчивым для любого средне-крупного проекта, неудивительно, что он исходит из рекомендации Бьярна Страуструпа, в те времена архитектура программного обеспечения была похожа на научную фантастику
Первоначальный вопрос касается наличия оператора на enum. Это не был архитектурный вопрос. Я не верю, что в 2013 году C++ был научной фантастикой.
Я не думаю, что люди здесь ищут плохие решения, а это определенно и есть, и поэтому проголосовали против. Я тоже добавил свой отрицательный голос, кстати
Расширение ответа @ 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-Covers-all-enum-values компилятора для записи allValuesArray, что приведет к ошибке, если перечисление добавило значение.Это интересно. В итоге я использовал только inline constexpr auto allValuesArray() { return std::array{ MyEnum::foo, MyEnum::bar, MyEnum::baz }; }, так как мне все равно нужно объяснять его по одному. Для меня это самое простое решение. (Между прочим: с вашим кодом я увидел, как clang-7 вылетает. Fun Fun Fun XD)
Плюсы: перечисления могут иметь любые значения, которые вам нравятся, в любом порядке, и их по-прежнему легко перебирать. Имена и значения определяются один раз в первом #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
Уловка объявления списка с неопределенной оболочкой макроса и последующего определения оболочки по-разному в различных ситуациях имеет много других приложений, кроме этого.
Большинство решений основаны на циклах в диапазоне (MIN, MAX), но не учитывают тот факт, что в перечислении могут быть дыры.
Мои предложения:
for (int i = MYTYPE_MIN; i <= MYTYPE_MAX; i++) {
if (MYTYPE_IsValid(i)) {
MYTYPE value = (MYTYPE)i;
// DoStuff(value)
}
}
У связанных элементов есть несколько интересных ответов.