-2

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 consts become static constexprs.

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 statics 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 consts, 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 constexprs.

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?

user975989
  • 2,578
  • 1
  • 20
  • 38
  • 2
    although I embrace manu Java utilities,Java-style enums are one of the things you simply don't need. – David Haim Oct 21 '15 at 15:16
  • 9
    A way of solving what problem exactly? – interjay Oct 21 '15 at 15:18
  • 3
    Please explain, what the exact problem is you want to solve for non-java programmers. – MikeMB Oct 21 '15 at 15:20
  • It is useful to bundle additional information to an enumeration, which is simply not possible without making the calling methods very verbose. I always thought of having static information like this useful. For example, consider designing a state machine. Wouldn't it be useful to use a switch statement for each state? – user975989 Oct 21 '15 at 15:23
  • you are mix value types for enum.it's wrong. c++11 now support strong typed enums : http://www.cprogramming.com/c++11/c++11-nullptr-strongly-typed-enum-class.html – kain64b Oct 21 '15 at 15:25
  • Still don't understand what you want, but would a conversion operator solve your problem? – MikeMB Oct 21 '15 at 15:25
  • 1
    Possible duplicate of [How to write a Java-enum-like class with multiple data fields in C++?](http://stackoverflow.com/questions/1965249/how-to-write-a-java-enum-like-class-with-multiple-data-fields-in-c) – BlackDwarf Oct 21 '15 at 15:28
  • But you can't use a `switch` with a non-`constexpr` conversion operator, and since you can't have a `constexpr` as a member, when the type is the class itself, you can't use the answer of that question. – user975989 Oct 21 '15 at 15:30
  • 1
    The problem is that `enum` types in Java are what that language calls reference types. A Java statement like `if (color == Color.RED)` thus makes a *pointer* comparison, i.e. it takes object identity and not equality into account. You cannot translate this directly to C++ enums, which are value types. You would have to use pointers to instances to emulate the Java mechanism. That's not a good idea. – Christian Hackl Oct 21 '15 at 15:30
  • Couldn't we just use a value comparison of the underlying type? – user975989 Oct 21 '15 at 15:35

1 Answers1

0

Based on Columbo's answer here: Is a class definition complete when it is passed to a base class?, it is possible to achieve expanded enums.

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*/; }
};

While there are still some issues with this implementation, such as a lack of returning a unique value (probably have to just make the ctor take one and do it manually), it looks pretty good. Note however this won't compile on MSVC (due to lack of good constexpr support) or Clang (due to bug 24541). But hey, it's the best we've got so far.

Community
  • 1
  • 1
user975989
  • 2,578
  • 1
  • 20
  • 38