Let's say I have an enumeration of statuses for a game I'm working on. At first, I have something like this:
enum class Status {
OK, HURT, DYING, DEAD
};
That's all fine and dandy, but now I want to print the name of the status. Naturally, if this was a normal class, I would call getName()
on an instance of Status
. However, no such option exists for an enumeration.
A way to solve this problem is to have something like this:
const char * getName(Status status) {
switch(status) {
case Status::OK:
return "OK";
break;
/*and so on */
}
However, this is clearly not very extensible. It becomes a problem when you have a lot of enums with lots of data tied to them. You also don't really tie related data together in a very meaningful way. With uniform call syntax, you could get something close, like this:
Status::OK.getName();
But that's not standard yet.
Another way to solve this problem is through static const
members inside a class, like so:
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");
That's all well and good, and it works out fine, for a while. But now, I want to write some code to deal with each Status
, so instinctively I prefer a switch
over an if
-else
chain.
I write this:
Status status = getStatus();
switch(status) {
case Status::OK:
//do something
default:
//do something else
}
Of course, this doesn't work. I need a converting operator! So I add that
operator int() { return /*some sort of unique value*/; }
And... still nothing. The reasoning is that the operator must be a constexpr
. Which is obvious, knowing the detail of switch
, but it doesn't really help. So the next logical step is to have the static const
s become 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;
The reason for having to put the static
s is because constexpr
have to be initialized on the spot. This would work fine, and honestly it covers all my needs. The only problem is that it doesn't compile. It makes sense: how can a class whose definition is not done yet be initialized? This is not a problem with the const
s, because they are delayed in their instantiation.
So now, the only reasonable way to do something like that is through a another class, so that the size of Status
is known when we create the constexpr
s.
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;
It's a distance away from the natural syntax of Status::NAME
, instead having to say something like Status::constants::NAME
. Another concern is the operator int()
. There is no way (that I know of) to make this be populated in a way such as an enum would be. In a static const
implementation, the obvious choice is to have a private static int
, increment it in the ctor, and store it, using that as the return value of the conversion operator. However, this is no longer an option in the constexpr
version. Maybe there's some sort of template magic to do it, and if so I'd love to see it. So my question is, are there any improvements to be done using modern C++ to the way we bundle information to an enum?