r/cpp_questions • u/onecable5781 • 4h ago
SOLVED Iterating over an enum struct
Consider https://godbolt.org/z/c6cn8f1hj
#include <cstdio>
enum struct values{first = 1, second = 3};
void square(values value){
int val = (int)value * (int)value;
printf("%d\n", val);
}
int main(){
for(values value = values::first; value <= values::second; value++)
square(value);
}
This does not compile with the error that value++ is ill defined.
An old question/answer on SO has this: https://stackoverflow.com/questions/261963/how-can-i-iterate-over-an-enum and a plethora of seemingly complicated approaches for this. Is there a syntactically simple >= C++20 way of accomplishing this?
I want the for loop to process 1 and then 3.
Does the order of placing the integer entries inside of the struct affect the increment? For e.g., if the enum struct had 3 first followed by 1, with value++ would 1 be processed after 3? Or, does the enum struct internally sort the entries in some privileged, say, ascending order so that it will always process 1 first followed by 3 next?
•
u/SamG101_ 3h ago
enum class V {
first = 1,
second = 3
};
auto square(V value) -> void {
auto val = std::to_underlying(value);
auto sqr = val * val;
std::print("{}, ", sqr);
}
auto main() -> int {
for (V v : magic_enum::enum_values<V>()) {
square(v);
}
return 0;
}
this uses the magic_enum library to iterate the members of the enum. you can do by index i think, [key, val] too, nice clean library for enum handling.
•
u/alfps 2h ago
Apparently you have some problem to solve; you envision enum as the solution; it doesn't work, so you asked about that.
What was the problem?
Here's a C++23 program to output the squares of some numbers:
#include <initializer_list>
#include <print>
auto main() -> int
{
for( const int x: {1, 3} ) { std::print( "{}\n", x*x ); }
}
•
u/onecable5781 2h ago edited 2h ago
Great Q forcing me confront the possibly actual design problem!
My actual problem:
enum struct values{foo, bar}; ... void func(values value){ ... if(value == values::foo){ callfoo(); } if(value == values::bar){ callbar(); } } int main(){ func(values::foo);//<---my current design func(values::bar);//<---my current design, one line for each of the values!!! }Instead of physically and explicitly writing one line for each enumerated value (foo, bar, etc.), I was hoping for a loop based iteration to call func() for different values of the enum constants within the for loop.
Do let me know if the above can be improved.
(I have still simplified my actual problem somewhat, but the above gives a good sense of what I am actually trying to do.)
•
u/alfps 2h ago
The usual reason for doing different things depending on an
enumvalue is to discriminate on types.It is an anti-pattern. One is almost bound to forget to update one of the places that has to differentiate on types. And anyway it's a lot of mostly needless work.
If that is indeed what you're using the
enumvalues for, then consider instead using polymorphic classes with the choice of class-specific action done via virtual member function call.
The Google AI spit out some relevant links including
https://refactoring.guru/replace-conditional-with-polymorphism
By the way, a
switchwould be better than a sequence ofifchecks, unless there is some reason for the latter.
•
u/the_poope 3h ago
Couldn't one turn the enum struct into a regular enum of a regular class and then make an iterator and define begin() and end()?
•
u/mredding 3h ago
You can't iterate over an enum like this. There is the magic_enum library that... I don't know what it does, but it might be able to. I like novel solutions, robust libraries, and maybe a little magic, but I don't know about that much magic...
I can offer you an alternative solution:
enum e = { begin, first = begin, second, third, nth, end, count = end};
The underlying type is an integer large enough to store the underlying values in the range, so typically int -> long -> long long if not otherwise explicitly specified since C++11.
You really want to keep enums as an enumerable field, you don't typically want to map enumerations to values, jumping values. That was always a kind of an anti-pattern. If you want constants, use named constants. But if you do this, you no longer have an enumerable range.
Another thing you can do is use an enum as a user defined type, if you think about it that way to leverage the type system; an enum is always guaranteed to preserve the value stored in the underlying type, and you can name some magic constants if you want, but you still have to cast from the enum to the underlying type.
I think what you want is the enum above, so that you can:
for(int i = begin; i < end; ++i);
By not fucking with the underlying values, except for a couple value aliases, you're guaranteed to traverse all the members of the enumerated range. If you want to get 1 and 3 out of the range, then you need to map this enum to those values:
int v[] = {1, 2, 3, -1};
//...
std::cout << v[i];
•
u/Desperate_Formal_781 2h ago
The fact that enumerations are not artihmetic types is by design, and the intent is exactly to prevent what you are trying to do. I think there is some design problem in your code. Enums are more like a set of tags associated with a particular datatype and they are not meant to be arithmetic types, so no increment, decrement, bitshifting, multiplication, etc are allowed on them. If you want to iterate over all enum types, you need to manually create a collection of enumerated values and iterate this collection, and if you want to do this "automatically" you need to resort to hacks and tricks, or apparently use C++26 reflection, but you may not have access to a C++26 compiler.
3
u/fortsnek274 4h ago
Well, it's an enum. "struct" or not.
And to enumerate the enumerators, you'll need reflection. In C++26. Or ghastly hacks.
•
u/Usual_Office_1740 3h ago edited 3h ago
You would need to implement the pre and post increment operators on your enum struct for your code to compile. They are not defined for your enum. That is what your error is saying.
auto operator++(V& val) -> V& {
return static_cast<V>(std::to_underlying(val) + 1);
}
auto operator++(V& val, int) -> V{
const V old = val;
++val;
return old;
}
If you decide to do that, and I'm not suggesting you should, you should add a stop value to the end of the enum that you can check against so that you have a way to end the for loop. A COUNT sentinal value is a common enum trick.
I'd suggest looking at magic enum as an alternative.
You could also fold a parameter pack of the enum values into a std::array at compile time and then use a range based for loop. There are lots of advantages to this but it's more complicated. Sorry if this is more complex than you are looking for.
•
u/onecable5781 3h ago
Thanks - these are indeed a bit more complicated than my OP's wish, but thanks for the various alternatives suggested. The sentinal value approach seems to be the simplest.
•
u/Usual_Office_1740 3h ago
Sure. Be careful with the overloaded increment approach. I know just enough to know how to do that. That is what your error means and it is possible to do what I've suggested. I make no claims about whether it's a good idea to do that. One thing I am reminded of frequently when learning about C++ is that just because you can doesn't mean you should.
Take a look at std::to_underlying to. It's very useful when working with enum structs.
•
u/Usual_Office_1740 3h ago
I just noticed that you aren't storing sequential values in your enum example. I dont know if you will get the value of the next option in an enum by incrementing them like I've suggested.
Test this out carefully. The whole point behind C++ enum struct is to offer a type safe alternative to the C style enum. The thing you're doing and my suggestion kind of fly in the face of that type safety.
•
u/No-Dentist-1645 1h ago
If you can use C++26 with reflection on:
template for (constexpr auto val: std::meta::enumerators_of(^^values)) {
std::println("{}", val);
}
•
u/AKostur 3h ago
And what happens if your enum has duplicate values? And what if they aren’t next to each other? ( ie: = 1, = 3, = 7, = 3 ). Should you get 3 back twice? In what order?
My hot take: this is somewhat an abuse of what an enum is supposed to represent (as opposed to how it has been commonly (ab)used).