Home > OS >  Avoiding repetitive copy-paste of static_cast<size_t>(enum_type) for casting an enum class to
Avoiding repetitive copy-paste of static_cast<size_t>(enum_type) for casting an enum class to

Time:12-12

I have an enum type in my code, like this:

enum class GameConsts: size_t { NUM_GHOSTS = 4 };

I find myself repeating the required static_cast to get the enum value:

Ghost ghosts[static_cast<size_t>(GameConsts::NUM_GHOSTS)];
// and...
for(size_t i = 0; i < static_cast<size_t>(GameConsts::NUM_GHOSTS);   i) { ... }

What is the best way to avoid this repetitive static_cast?

The option of allowing implementation of a casting operator was raised in this ISO proposal discussion, but seems to drop.

Related SO question: Overloading cast operator for enum class


Davis Herring added in a comment that C 23 added: std::to_underlying which should be the answer when we have C 23 compiler support. But the question is for C 20.

CodePudding user response:

Your first option is to use a constexpr instead of an enum:

constexpr size_t NUM_GHOSTS = 4;

You can put it inside a proper context, such as GameConsts struct:

struct GameConsts {
    static constexpr size_t NUM_GHOSTS = 4;
};

Then there is no need for a casting:

Ghost ghosts[GameConsts::NUM_GHOSTS];

In case you actually need an enum, as you have a list of related values that you want to manage together, then you may use unscoped enum (i.e. drop the class from the enum class and use a regular plain-old enum) but put it inside a struct to preserve the context. I will use here an example of another enum, managing several related values.

enum class GameKeys: char { UP = 'W', RIGHT = 'D', DOWN = 'X', LEFT = 'A' };

The repeated use of static_cast may happen for the above enum, in a switch-case like that:

char key_pressed;
// ...
switch(key_pressed) {
    case static_cast<char>(GameKeys::UP): // ...
        break;
    case static_cast<char>(GameKeys::RIGHT): // ...
        break;
    case static_cast<char>(GameKeys::DOWN): // ...
        break;
    case static_cast<char>(GameKeys::LEFT): // ...
        break;
}

To avoid the repeated need for static_cast you may go with:


Option 1: Use simple unscoped enum inside a struct

struct GameKeys {
    enum: char { UP = 'W', RIGHT = 'D', DOWN = 'X', LEFT = 'A' };
};

And since old-style enum can cast implicitly to its underlying type, you can get away of the casting:

switch(key_pressed) {
    case GameKeys::UP: // ...
        break;
    case GameKeys::RIGHT: // ...
        break;
    case GameKeys::DOWN: // ...
        break;
    case GameKeys::LEFT: // ...
        break;
}

Option 2: Add your own conversion function

If you actually prefer, or have to use enum class you may have a simple conversion function for which the copy-paste is just calling the function, being less cumbersome than the full static_cast syntax:

enum class GameKeys: char { UP = 'W', RIGHT = 'D', DOWN = 'X', LEFT = 'A' };

// a simple "val" function - specific for our GameKeys
constexpr char val(GameKeys key) { return static_cast<char>(key); }

And:

switch(key_pressed) {
    case val(GameKeys::UP): // ...
        break;
    case val(GameKeys::RIGHT): // ...
        break;
    case val(GameKeys::DOWN): // ...
        break;
    case val(GameKeys::LEFT): // ...
        break;
}

If you choose the last option, you may want to generalize it for any type of enum, with this code:

// creating a "concept" for enums
template<typename E>
concept EnumType = std::is_enum_v<E>;

// creating a generic "val" function for getting the underlying_type value of an enum
template<EnumType T>
constexpr auto val(T value) {
    return static_cast<std::underlying_type_t<T>>(value);
}

Option 3: Cast to the enum and not from the enum

As suggested by @Nathan Pierson and @apple apple in the comments, the casting can be to the enum, with this code:

char key_pressed = 'E';
// cast to the enum
GameKeys key = static_cast<GameKeys>(key_pressed);
switch(key) {
    case GameKeys::UP: // ...
        break;
    case GameKeys::RIGHT: // ...
        break;
    case GameKeys::DOWN: // ...
        break;
    case GameKeys::LEFT: // ...
        break;
    default: // ignore any other keys
        break;
}

This should work fine even if key_pressed is not any of the enum values, as we have a fixed enum (having a stated underlying type, note that an enum class is always fixed, even if not stated explicitly). See also: What happens if you static_cast invalid value to enum class?

CodePudding user response:

If you are constantly converting a scoped enum to an integer... you don't actually want a scoped enum. What you seem to want is a bunch of static constexpr variables in their own namespace:

struct GameConsts
{
  static constexpr size_t NumGhosts = 4;
  ...
};
  • Related