循环遍历枚举的好方法是什么?

Com*_*fox 5 c++ embedded

背景

对于嵌入式项目中的 UI,我正在寻找一种很好的通用方式来存储“状态”并通过按下按钮(例如菜单项列表)循环浏览它。

通常,我喜欢为此目的使用枚举,例如:

enum class MenuItem {
    main,
    config,
    foo,
    bar,
};
Run Code Online (Sandbox Code Playgroud)

然后,在我的 UI 代码中,我可以存储一个currentMenuItem状态,如

MenuItem currentMenuItem = MenuItem::MAIN;
Run Code Online (Sandbox Code Playgroud)

并通过与currentMenuItem枚举中声明的任何可能值进行比较来根据当前状态执行操作。

问题

问题是,我现在想进入下一个菜单项。为此,我可以非常轻松地编写一个函数,通过强制转换为 int,递增 1,然后将其转换回枚举来执行此操作。我有多个不同的枚举,所以我什至可以使用这个模板化函数来为任意枚举执行此操作:

template <typename T>
void advanceEnum(T &e) {
    e = static_cast<T>(static_cast<int>(e) + 1);
}
Run Code Online (Sandbox Code Playgroud)

这样做的问题是它不会环绕:它会很高兴地继续增加底层整数,使其超出实际枚举中的元素数量。我可以很容易地解决这个问题(通过对上述函数中的元素数取模),只要有一种干净的方法来获取枚举的元素数。其中,据我所知,真的没有。

自定义枚举?

我正在考虑编写一个实现此行为的自定义“CyclicEnum”类,我随后可以从中派生。这样,我也可以把它写成一个重载的operator++.

但是,我仍然没有设计出如何在不实际使用枚举的情况下获得类似枚举的东西。例如,我得到了这样的东西:

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) {}
};
Run Code Online (Sandbox Code Playgroud)

但是,正如您所看到的,这仍然在自定义 CyclicEnum 类中使用了一个枚举,我又回到了同样的问题:我无法计算 Enum 元素的数量,所以我必须手动指定(我认为不好,因为它是多余的)。其次,通过这种方式,我还必须覆盖派生类中的构造函数,我希望避免它尽可能保持干净。

在搜索此问题时,许多人显然建议在枚举末尾添加一个“虚拟”值作为获取大小的技巧:

enum Value {
    main,
    config,
    foo,
    bar,
    _count,
};
Run Code Online (Sandbox Code Playgroud)

但坦率地说,我觉得这很丑陋,因为_count现在实际上是一个有效的选择。

有没有办法解决?我在滥用枚举吗?看看枚举显然(按设计)如此难以计数的事实,可能。但是,拥有像枚举提供的命名值这样的结构的好方法是什么?

编辑:

好的,我确信_count在最后使用一个元素并不是一个坏主意。不过,我想将其封装在某种结构中,例如类。

我还想过不使用继承,而是使用类模板来完成此操作,如下所示:

(注意,这不会编译):

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,
}> {};
Run Code Online (Sandbox Code Playgroud)

但是,这不起作用,因为“ISO C++ 禁止对‘枚举’类型的前向引用”和“不能在类型说明符中定义匿名枚举”...

有没有另一种方法可以使这个想法起作用(模板一个带有枚举的类),或者这不会起作用?

Cli*_*ord 1

不使用_count,而是将最后一个“哨兵”值设置为最后一个实际值。

enum Value {
    main,
    config,
    foo,
    bar,
    last = bar
};
Run Code Online (Sandbox Code Playgroud)

enum这样就可以避免值不是有效菜单选项的问题。例如,在您的增量中而不是:

v = static_cast<Value>( (static_cast<int>(v) + 1) % 
                        static_cast<int>(Value::_count) );
Run Code Online (Sandbox Code Playgroud)

你会有:

v = static_cast<Value>( (static_cast<int>(v) + 1) %
                        (static_cast<int>(Value::last) + 1) ) ;
Run Code Online (Sandbox Code Playgroud)

如果实际上这些枚举只是导致调用不同的菜单项处理程序函数,那么您可以使用指向函数的指针数组,而不是枚举/开关或其他任何东西。