Reputation: 748
For a UI in an embedded project, I'm looking for a nice generic way to store a "state" and cycle through it with a button press, e.g. a list of menu items.
Normally, I like to use enums for this purpose, for example:
enum class MenuItem {
main,
config,
foo,
bar,
};
Then, in my UI code, I can store a currentMenuItem
state like
MenuItem currentMenuItem = MenuItem::MAIN;
and do things depending on the current state by comparing currentMenuItem
to any of its possible values, as declared in the enum.
The thing is, I would now like to advance to the next menu item. For that, I can quite trivially write a function that does that by casting to int, incrementing by one, and casting it back to the enum. I have multiple distinct enums, so I can even use this templated function that does this for arbitrary enums:
template <typename T>
void advanceEnum(T &e) {
e = static_cast<T>(static_cast<int>(e) + 1);
}
The problem with this is that it doesn't wrap around: it will happily keep increasing the underlying integer beyond the number of elements in the actual enum. I could quite easily solve this (by taking the modulo with the number of elements in the above function), if only there was an clean way to get the element count of an enum. Which, as far as I could find, there isn't really.
I was thinking of writing a custom 'CyclicEnum' class that implements this behaviour, that I can subsequently derive from. That way, I could also write this as an overloaded operator++
.
However, I still haven't devised how to can get an enum-like thing without actually using an enum. For example, I got to something like this:
class CyclicEnum {
public:
uint8_t v;
CyclicEnum& operator++() {
v = (v+1) % count;
return *this;
}
CyclicEnum operator++(int) {
CyclicEnum old = *this;
operator++();
return old;
}
private:
uint8_t count;
protected:
CyclicEnum(uint8_t v, uint8_t count) : v(v), count(count) {}
};
struct Tab : public CyclicEnum {
enum Value {
main,
config,
foo,
bar,
};
Tab(Value v) : CyclicEnum(v, 4) {}
};
However, as you can see, this still uses an enum inside the custom CyclicEnum class, and I'm back to the same issue: I can't count the number of Enum elements, so I have to specify that manually (which I think is not nice because it's redundant). Secondly, this way I also have to override the constructor in the derived class which I would like to avoid to keep it as clean as possible.
Upon searching this issue, many people apparently recommend to add a "dummy" value at the end of the enum as a trick to get the size:
enum Value {
main,
config,
foo,
bar,
_count,
};
but frankly, I find that just ugly, since _count
is now actually a valid option.
Is there any way around this? Am I abusing enums? Looking at the fact that enums are apparently (by design) so hard to count, probably. But what would be a nice way to have a structure like this with named values like an enum provides?
EDIT:
OK, I'm convinced that using a _count
element at the end isn't so bad of an idea. Still, I'd like to encapsulate this in some kind of structure, like a class.
I also thought of instead of using inheritance, using a class template to accomplish this, something like this:
(caution, this doesn't compile):
template<typename T>
struct CyclicEnum {
T v;
enum Values = T;
CyclicEnum& operator++() {
v = (v+1) % T::_count;
return *this;
}
CyclicEnum operator++(int) {
CyclicEnum old = *this;
operator++();
return old;
}
CyclicEnum(T v) : v(v) {}
};
struct MenuItem : public CyclicEnum<enum class {
main,
config,
foo,
bar,
_count,
}> {};
However, this does not work because "ISO C++ forbids forward references to 'enum' types" and "anonymous enum cannot be defined in a type specifier"...
Is there another way to make this idea work (template a class with an enum), or is this not going to work?
Upvotes: 5
Views: 1690
Reputation: 93534
Rather then using _count
, set the last "sentinel" value to the last actual value.
enum Value {
main,
config,
foo,
bar,
last = bar
};
Then you avoid the problem of having an enum
value that is not a valid menu option. in your increment for example instead of :
v = static_cast<Value>( (static_cast<int>(v) + 1) %
static_cast<int>(Value::_count) );
you'd have:
v = static_cast<Value>( (static_cast<int>(v) + 1) %
(static_cast<int>(Value::last) + 1) ) ;
If in fact these enums simply cause different menu item handler functions to be called, then you could instead used an array of pointer-to-functions rather then an enum/switch or whatever.
Upvotes: 1
Reputation: 782
You could use the magic_enum library to reflect on enums.
Example which gets the names of all enum elements as std::array < std::string_view > and prints them.
#include <algorithm>
#include <iostream>
#include <magic_enum.hpp>
enum struct Apple
{
Fuji = 2,
Honeycrisp = -3,
Envy = 4
};
int
main ()
{
constexpr auto &appleNames = magic_enum::enum_names<Apple> (); // get an std::array<std::string_view> with the names for the enum sorted by value
std::copy (appleNames.begin (), appleNames.end (), std::ostream_iterator<std::string_view> (std::cout, "\n")); // print all the names
}
Prints:
Honeycrisp
Fuji
Envy
There are some limitations please read magic enum limitations
Upvotes: 4
Reputation: 214310
The thing is, I would now like to advance to the next menu item.
++
comes to mind. Keep it simple.
That way, I could also write this as an overloaded operator++
Yeah... or again, keep it simple, you could just drop the whole class. There's no need to write an abstraction layer around a simple integer. Really.
Upon searching this issue, many people apparently recommend to add a "dummy" value at the end of the enum as a trick to get the size
Sure why not. This is incredibly common practice.
but frankly, I find that just ugly, since LAST is now actually a valid option.
It's canonical code, it isn't ugly. Just give it a sensible name, maybe something like MENU_ITEMS_N
to suggest that this a counter variable then use it as such. for(int i=0; i<MENU_ITEMS_N; i++) ...
Am I abusing enums?
Enums are just named integer values. Don't over-engineer your code. It's bad for performance, it's bad for maintenance, it adds needless complexity.
Upvotes: 4