Performance and Tradeoffs of Enum Inheritance in C++

I have some methods that allow the "Heart" of a Cell to be a subset of the total legal types (e.g. a QUOTED! cell doesn't have a 'quoted' heart, it has whatever the heart of the quoted thing is... hence TYPE_QUOTED and TYPE_WORD are different categories of thing.)

The tricks I use allow me to check enums at compile-time, but it only works in MSVC and GCC. Clang doesn't allow the trick.

So I was curious if there was any better... and there are ways of doing it. But they end up running a function call in debug builds every time you invoke an enumerated type constant. The optimizer strips it out, but I run debug builds 99% of the time so I don't want to rely on the optimizer if I don't have to.

I asked ChatGPT about it, and it gave the following startegy as an alternative to what I was doing. It wasn't really worth it so I backed it out. Keeping a note about it here for future reference, since the technique did work...but it had a few downsides beyond just adding runtime cost in debug builds.


#include <stdint.h>
#include <type_traits>
#include <stdio.h>

//
// Underlying enum
//

enum Type : uint8_t
{
    _TYPE_INTEGER   = 1,
    _TYPE_BLOCK     = 2,
    _TYPE_QUOTED    = 128,
    _TYPE_QUASIFORM = 129
};


//
// Compile-time classification
//

template<Type T>
struct is_heart : std::false_type {};

template<> struct is_heart<_TYPE_INTEGER> : std::true_type {};
template<> struct is_heart<_TYPE_BLOCK>   : std::true_type {};


//
// Forward declaration
//

class Heart;


//
// Public constant type
//

template<Type V>
struct TypeConstant
{
    static constexpr Type value = V;

    // implicit conversion for switch, storage, etc.
    constexpr operator Type() const { return V; }

    // implicit conversion to Heart (compile-time validated)
    constexpr operator Heart() const;
};


//
// Heart type
//

class Heart
{
    uint8_t value_;

    constexpr explicit Heart(uint8_t v) : value_(v) {}

    template<Type V>
    friend struct TypeConstant;

public:

    constexpr operator Type() const
    {
        return static_cast<Type>(value_);
    }
};


//
// Heart conversion implementation
//

template<Type V>
constexpr TypeConstant<V>::operator Heart() const
{
    static_assert(is_heart<V>::value,
        "TYPE_XXX is not a valid Heart");

    return Heart(V);
}


//
// Public constants (these are what you use everywhere)
//

constexpr TypeConstant<_TYPE_INTEGER>   TYPE_INTEGER{};
constexpr TypeConstant<_TYPE_BLOCK>     TYPE_BLOCK{};
constexpr TypeConstant<_TYPE_QUOTED>    TYPE_QUOTED{};
constexpr TypeConstant<_TYPE_QUASIFORM> TYPE_QUASIFORM{};


//
// Test functions
//

void test_switch(Type t)
{
    switch (t)
    {
        case TYPE_INTEGER:
            printf("TYPE_INTEGER\n");
            break;

        case TYPE_BLOCK:
            printf("TYPE_BLOCK\n");
            break;

        case TYPE_QUOTED:
            printf("TYPE_QUOTED\n");
            break;

        case TYPE_QUASIFORM:
            printf("TYPE_QUASIFORM\n");
            break;
    }
}

void test_heart(Heart h)
{
    switch (static_cast<Type>(h))
    {
        case TYPE_INTEGER:
            printf("Heart INTEGER\n");
            break;

        case TYPE_BLOCK:
            printf("Heart BLOCK\n");
            break;
    }
}


//
// Main
//

int main()
{
    test_switch(TYPE_INTEGER);
    test_switch(TYPE_QUOTED);

    test_heart(TYPE_INTEGER);
    test_heart(TYPE_BLOCK);

    // Uncommenting either line produces compile-time error:
    // test_heart(TYPE_QUOTED);
    // test_heart(TYPE_QUASIFORM);

    return 0;
}