如何将附加信息捆绑到枚举中

How can I bundle additional information to an enum?

本文关键字:枚举 信息      更新时间:2023-10-16

假设我有一个关于我正在开发的游戏的状态枚举。首先,我有这样的内容:

enum class Status {
    OK, HURT, DYING, DEAD
};

这一切都很好,但现在我想打印状态的名称。当然,如果这是一个普通的类,我会在Status的实例上调用getName()。但是,对于枚举不存在这样的选项。

解决这个问题的一个方法是像这样:

const char * getName(Status status) {
switch(status) {
case Status::OK:
    return "OK";
    break;
/*and so on */
}

然而,这显然不是很可扩展。当您有大量的枚举和大量的数据绑定在一起时,这就成了一个问题。你也没有以一种非常有意义的方式将相关数据联系在一起。使用统一的调用语法,您可以得到类似这样的结果:

Status::OK.getName();

但这还不是标准。

另一种解决这个问题的方法是在类中使用static const成员,如下所示:
class Status {
    std::string name;
    Status(std::string name):name(std::move(name)) {}
public:
    const std::string & getName() const { return name; }
    static const Status OK, HURT, DYING, DEAD;
};
//in some .cpp
const Status Status::OK("OK"), Status::HURT("Hurt"), Status::DYING("Dying"), Status::DEAD("Dead");

这一切都很好,而且在一段时间内也很好。但是现在,我想写一些代码来处理每个Status,所以本能地我更喜欢switch而不是if - else链。

我这样写:

Status status = getStatus();
switch(status) {
case Status::OK:
    //do something
default:
    //do something else     
}

当然,这不起作用。我需要一个转换操作符!所以我加上

operator int() { return /*some sort of unique value*/; }

…还是什么都没有。原因是操作符必须是constexpr。这是显而易见的,知道switch的细节,但它并没有真正的帮助。因此,下一个合乎逻辑的步骤是将static const s变为static constexpr s。

class Status {
    const char * name; //we are in constexpr land now
    constexpr Status(const char * name):name(name) {}
public:
    constexpr const char * getName() const { return name; }
    constexpr operator int() const { return /*some sort of unique value*/; }
    static constexpr Status OK = Status("OK"),
        HURT = Status("Hurt"),
        DYING = Status("Dying"),
        DEAD = Status("Dead");
};
//in some .cpp
constexpr Status Status::OK, Status::HURT, Status::DYING, Status::DEAD;

必须放置static s的原因是constexpr必须当场初始化。这将工作得很好,老实说,它涵盖了我所有的需求。唯一的问题是它不能编译。这是有道理的:一个尚未定义的类如何被初始化?这对const来说不是问题,因为它们在实例化时是延迟的。

所以,现在唯一合理的方法是通过另一个类,这样当我们创建constexpr s时,Status的大小是已知的。

class Status {
    const char * name;
    constexpr Status(const char * name):name(name) {}
public:
    constexpr const char * getName() const { return name; }
    constexpr operator int() const { return /*some sort of unique value*/; }
    struct constants;
};
struct Status::constants {
    static constexpr Status OK = Status("OK"), 
        HURT = Status("Hurt"),
        DYING = Status("Dying"),
        DEAD = Status("Dead");
};
//in some .cpp
constexpr Status Status::constants::OK, Status::constants::HURT, Status::constants::DYING, Status::constants::DEAD;

这与Status::NAME的自然语法有一段距离,而不得不说Status::constants::NAME之类的东西。另一个问题是operator int()。(据我所知)没有办法让它像enum那样被填充。在static const实现中,明显的选择是使用私有的static int,将其在actor中自增,并将其存储,使用它作为转换操作符的返回值。然而,这在constexpr版本中不再是一个选项。也许有某种模板魔法可以做到这一点,如果是这样的话,我很想看到它。所以我的问题是,是否有任何改进要做使用现代c++的方式,我们捆绑信息到枚举?

根据Columbo的回答:当一个类的定义被传递给基类时,它就完成了吗?,可以实现扩展枚举。

template<class T>
class Constants {
public:
    static const T OK, HURT, DYING, DEAD;
};
template <typename T>
constexpr T Constants<T>::OK = T("OK");
template <typename T>
constexpr T Constants<T>::HURT = T("Hurt");
template <typename T>
constexpr T Constants<T>::DYING = T("Dying");
template <typename T>
constexpr T Constants<T>::DEAD = T("Dead");
class Status : public Constants<Status> {
    friend class Constants<Status>;
    const char * name;
    constexpr Status(const char * name):name(name) {}
public:
    constexpr const char * getName() const { return name; }
    constexpr operator int() const { return /*some sort of unique value*/; }
};

虽然这个实现仍然存在一些问题,例如缺乏返回唯一值(可能只能让actor获取一个值并手动执行),但它看起来相当不错。但请注意,这不会在MSVC(由于缺乏良好的constexpr支持)或Clang(由于bug 24541)上编译。但是,嘿,这是目前为止我们得到的最好的。