Is there a way for the C++ compiler to warn for unsafe enum class to integer overflow?

133 Views Asked by At

There are uses of enum classes in my codebase, where we need to convert to integers for low level functions.

There are static casts to uint8_t all over, assuming the enum has a small number of values.

As the size of our enums grow past fitting in a uint8_t, for example adding the 257th element to the enum, the casts will output the wrong value. Is there a way for the compiler to warn me of such narrowing/overflow?

#include <stdint.h>
#include <iostream>

enum class Color: uint32_t{ red, green, blue, orange=0xFFFFFFFF };
enum class Animal { dog, cat, frog=0xFFF };

int main(){
    Color g = Color::green;
    auto gg = static_cast<uint8_t>(g);
    Color f = Color::orange;
    auto ff = static_cast<uint8_t>(f);
    Animal frog = Animal::frog;
    auto nFrog = static_cast<uint8_t>(frog);
    printf("Casted value of red: 0x%x\n", gg);
    printf("Casted value of orange: 0x%x\n", ff);
    printf("Casted value of frog: 0x%x\n", nFrog);

    return 0;
}
Casted value of red: 0x1
Casted value of orange: 0xff
Casted value of frog: 0xff

I tried adding -Wall, -WPedantic, however nothing warns me that the integer value of orange will overflow in the cast.

I can slowly adopt the usage of using std::underlying_type, but I'd like the compiler to yell at me instead.

Requirements:

  • Works with scoped enum (enum class) Desires:
  • Maintainable for large enums that are messy/out of order
1

There are 1 best solutions below

4
chtz On

You could encapsulate your enums into a struct with a conversion operator which checks if the target type is large enough:

struct Animal {
    // someone added cow which makes it not fit into uint8 anymore:
    enum Animal_enum { dog, cat, frog=0xFF, cow } value;

    Animal(Animal_enum x) : value(x) {}

    template<class T>
    inline operator T() {
        static_assert( cow <= std::numeric_limits<T>::max(), "conversion is unsafe!");
        return T(value);
    }
};

Usage:

int main() {
    Animal a = Animal::cow; // You can still use prior enum names
    int16_t x = a; // this is fine

    // these fail at compile time:
    // uint8_t y = a;
    // static_cast<uint8_t>(a); // fails even if value is not used

    // this works:
    uint8_t y = static_cast<uint16_t>(a); // implicit conversion after explicit conversion

    return x + y;
}