Mir ist gerade aufgefallen, dass Sie keine mathematischen Standardoperatoren für Enumerationen wie ++ oder + = verwenden können
Was ist also der beste Weg, um alle Werte in einer C++ - Enumeration zu durchlaufen?
Der typische Weg ist wie folgt:
enum Foo {
One,
Two,
Three,
Last
};
for ( int fooInt = One; fooInt != Last; fooInt++ )
{
Foo foo = static_cast<Foo>(fooInt);
// ...
}
Natürlich bricht dies zusammen, wenn die Aufzählungswerte angegeben werden:
enum Foo {
One = 1,
Two = 9,
Three = 4,
Last
};
Dies zeigt, dass eine Aufzählung nicht wirklich durchlaufen werden soll. Eine typische Methode, um mit einer Enumeration umzugehen, ist die Verwendung in einer switch-Anweisung.
switch ( foo )
{
case One:
// ..
break;
case Two: // intentional fall-through
case Three:
// ..
break;
case Four:
// ..
break;
default:
assert( ! "Invalid Foo enum value" );
break;
}
Wenn Sie wirklich auflisten möchten, füllen Sie die Aufzählungswerte in einen Vektor und iterieren Sie darüber. Dadurch werden auch die angegebenen Aufzählungswerte korrekt behandelt.
#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;
}
Wenn Ihre Aufzählung mit 0 beginnt und das Inkrement immer 1 ist.
enum enumType
{
A = 0,
B,
C,
enumTypeEnd
};
for(int i=0; i<enumTypeEnd; i++)
{
enumType eCurrent = (enumType) i;
}
Wenn nicht, denke ich, ist das einzige, warum so etwas wie ein
vector<enumType> vEnums;
fügen Sie die Elemente hinzu und verwenden Sie normale Iteratoren ....
Mit c ++ 11 gibt es tatsächlich eine Alternative: Schreiben eines einfachen templatisierten benutzerdefinierten Iterators.
nehmen wir an, Ihre Aufzählung ist
enum class foo {
one,
two,
three
};
Dieser generische Code wird den Trick recht effizient erledigen - in einem generischen Header platzieren, dient er Ihnen für jedes Enum, das Sie eventuell wiederholen müssen:
#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; }
};
Sie müssen es spezialisieren
typedef Iterator<foo, foo::one, foo::three> fooIterator;
Und dann können Sie mit range-for iterieren
for (foo i : fooIterator() ) { //notice the parenteses!
do_stuff(i);
}
Die Annahme, dass Sie keine Lücken in Ihrem Enum haben, ist immer noch wahr. Es gibt keine Annahme über die Anzahl der Bits, die tatsächlich zum Speichern des Aufzählungswerts benötigt werden (dank std :: Underlying_type).
zu viel kompliziert diese lösung, ich mag das:
enum NodePosition { Primary = 0, Secondary = 1, Tertiary = 2, Quaternary = 3};
const NodePosition NodePositionVector[] = { Primary, Secondary, Tertiary, Quaternary };
for (NodePosition pos : NodePositionVector) {
...
}
Sie können nicht mit einer Aufzählung. Vielleicht passt eine Aufzählung nicht zu Ihrer Situation.
Eine übliche Konvention ist, den letzten Aufzählungswert in etwa MAX zu benennen und damit eine Schleife mit einem int zu steuern.
Etwas, das in den anderen Antworten nicht behandelt wurde = Wenn Sie stark typisierte C++ 11-Enumerationen verwenden, können Sie ++
oder + int
nicht für sie verwenden. In diesem Fall ist etwas unordentlicher Lösung erforderlich:
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)
}
Sie können das folgende Makro versuchen und definieren:
#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))
Jetzt können Sie es verwenden:
enum Count { zero, one, two, three };
for_range (Count, c, zero, three)
{
cout << "forward: " << c << endl;
}
Es kann verwendet werden, um durch vorzeichenlose, ganze Zahlen, Enumerationen und Zeichen vor und zurück zu iterieren:
for_range (unsigned, i, 10,0)
{
cout << "backwards i: " << i << endl;
}
for_range (char, c, 'z','a')
{
cout << c << endl;
}
Trotz seiner umständlichen Definition ist es sehr gut optimiert. Ich habe mir Disassembler in VC++ angesehen .. Der Code ist extrem effizient. Lassen Sie sich nicht scheuen, sondern die drei for-Anweisungen: Der Compiler erzeugt nach der Optimierung nur eine Schleife! Sie können sogar geschlossene Schleifen definieren:
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;
}
Sie können offensichtlich nicht durch aufgezählte Typen mit Lücken iterieren.
Ich mache das oft so
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))
{}
oder wenn nicht aufeinanderfolgend, aber mit einem regelmäßigen Schritt (z. B. Bitflags)
enum EAnimal
{
E_First,
E_None = E_First,
E_CanFly = 0x1,
E_CanWalk = 0x2
E_CanSwim = 0x4,
E_Last
}
for (EAnimali = E_First; i < E_Last; i = EAnimal(i << 1))
{}
Sie können auch die Inkrement-/Dekrement-Operatoren für Ihren Aufzählungstyp überladen.
Wenn Sie Ihre Aufzählung nicht mit einem endgültigen COUNT-Eintrag verschmutzen möchten (wenn Sie möglicherweise auch die Aufzählung in einem Schalter verwenden, wird der Compiler Sie dann über einen fehlenden Fall COUNT :) warnen:
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);
}
Für MS-Compiler:
#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);
}
Hinweis: Dies ist viel weniger Code als die Antwort des einfachen benutzerdefinierten Iterators.
Sie können dies mit GCC erreichen, indem Sie typeof
anstelle von decltype
verwenden, aber ich kann den Compiler im Moment nicht zur Hand haben, um sicherzustellen, dass er kompiliert wird.
Angenommen, die Aufzählung wird fortlaufend nummeriert, ist fehleranfällig. Darüber hinaus möchten Sie möglicherweise nur ausgewählte Enumeratoren durchlaufen. Wenn diese Teilmenge klein ist, kann es eine elegante Wahl sein, sie explizit zu durchlaufen:
enum Item { Man, Wolf, Goat, Cabbage }; // or enum class
for (auto item : {Wolf, Goat, Cabbage}) { // or Item::Wolf, ...
// ...
}
Hier ist eine weitere Lösung, die nur für zusammenhängende Enums funktioniert. Es gibt die erwartete Iteration, mit Ausnahme der Hässlichkeit im Inkrement, zu der es gehört, da dies in C++ defekt ist.
enum Bar {
One = 1,
Two,
Three,
End_Bar // Marker for end of enum;
};
for (Bar foo = One; foo < End_Bar; foo = Bar(foo + 1))
{
// ...
}
Wenn Sie wüssten, dass die Aufzählungswerte sequentiell sind, beispielsweise die Aufzählung "Qt: Key", könnten Sie
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);
}
}
Es funktioniert wie erwartet.
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]
}
In C++ gibt es keine Introspektion, daher können Sie diese Art von Daten zur Laufzeit nicht ermitteln.
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);
}
Ein constexpr std::array
kann auch nicht sequentielle Aufzählungen durchlaufen, ohne dass das Array vom Compiler instanziiert wird. Dies hängt von Dingen wie der Optimierungsheuristik des Compilers ab und davon, ob Sie die Adresse des Arrays verwenden.
In meinen Experimenten habe ich festgestellt, dass g++
9.1 mit -O3
das obige Array wegoptimiert, wenn es 2 nicht-sequentielle Werte oder einige sequentielle Werte gibt (ich habe bis zu 6 getestet). Dies funktioniert jedoch nur, wenn Sie eine if
-Anweisung haben. (Ich habe eine Anweisung ausprobiert, die einen ganzzahligen Wert verglichen hat, der größer als alle Elemente in einem sequenziellen Array ist, und die Iteration eingezeichnet, obwohl keine ausgeschlossen wurde. Als ich die if-Anweisung wegließ, wurden die Werte in den Speicher gestellt.) Außerdem wurde 5 eingezeichnet Werte aus einer nicht sequentiellen Aufzählung in [einem Fall | https://godbolt.org/z/XuGtoc] . Ich vermute, dass dieses merkwürdige Verhalten auf eine tiefe Heuristik zurückzuführen ist, die mit Caches und der Vorhersage von Verzweigungen zu tun hat.
Hier ist ein Link zu einer einfachen Testiteration auf Godbolt der demonstriert, dass das Array nicht immer instanziiert wird.
Der Preis dieser Technik besteht darin, die Enum-Elemente zweimal zu schreiben und die beiden Listen synchron zu halten.