Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is a nice way to cycle through an enum?

Tags:

c++

embedded

Background

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.

Problem

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.

Custom enum?

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?

like image 939
Compizfox Avatar asked Jul 21 '21 19:07

Compizfox


People also ask

Can we loop through enum?

Enums don't have methods for iteration, like forEach() or iterator(). Instead, we can use the array of the Enum values returned by the values() method.

How do you use enums correctly?

You should always use enums when a variable (especially a method parameter) can only take one out of a small set of possible values. Examples would be things like type constants (contract status: “permanent”, “temp”, “apprentice”), or flags (“execute now”, “defer execution”).

Can we loop through enum in C?

you can iterate the elements like: for(int i=Bar; i<=Last; i++) { ... } Note that this exposes the really-just-an-int nature of a C enum. In particular, you can see that a C enum doesn't really provide type safety, as you can use an int in place of an enum value and vice versa.

What is the use of enum in C++?

It is mainly used to assign names to integral constants, the names make a program easy to read and maintain. The keyword ‘enum’ is used to declare new enumeration types in C and C++. Following is an example of enum declaration. // The name of enumeration is "flag" and the constant // are the values of the flag.

What are some interesting facts about initialization of enum?

Interesting facts about initialization of enum. 1. Two enum names can have same value. For example, in the following C program both ‘Failed’ and ‘Freezed’ have same value 0.

What is enumeration in C++?

Last Updated : 21 Dec, 2018. Enumeration (or enum) is a user defined data type in C. It is mainly used to assign names to integral constants, the names make a program easy to read and maintain. enum State {Working = 1, Failed = 0}; The keyword ‘enum’ is used to declare new enumeration types in C and C++.

What are the requirements for ENUM names?

The value assigned to enum names must be some integeral constant, i.e., the value must be in range from minimum possible integer value to maximum possible integer value. 5. All enum constants must be unique in their scope. For example, the following program fails in compilation.


Video Answer


3 Answers

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.

like image 192
Lundin Avatar answered Oct 23 '22 07:10

Lundin


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

like image 3
Koronis Neilos Avatar answered Oct 23 '22 07:10

Koronis Neilos


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.

like image 1
Clifford Avatar answered Oct 23 '22 08:10

Clifford