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