Search code examples
c++enumscastingc++20enum-class

Avoiding repetitive copy-paste of static_cast<size_t>(enum_type) for casting an enum class to its underlying type


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.


Solution

  • 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?