zeus

Unnamed repository; edit this file 'description' to name the repository.
git clone Unknown
Log | Files | Refs

commit 10dce9768bcab497d517e82b8e6e487440339549
parent 394a2664d61b9f5b05e7830cd21661b6f1d9a354
author Dimitrije Dobrota < mail@dimitrijedobrota.com >
date Sun, 8 Jun 2025 19:45:12 +0200

Strong enum experiment

Diffstat:
A enum_strong.cpp | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

1 files changed, 631 insertions(+), 0 deletions(-)


diff --git a/ enum_strong.cpp b/ enum_strong.cpp

@@ -0,0 +1,631 @@

// Adapted from:
// https://gist.github.com/HertzDevil/43e8acbe5d73aba7610dcc1e79f17f3d

#include <concepts>
#include <limits>
#include <type_traits>

// The default enumeration category. Conversion is equivalent to static_cast.
// Unspecialized enumeration traits use this category.
struct enum_default {};

// The standard-layout enumeration category. Values outside the given range are
// mapped to a given none-element. Requires that:
// - If both EnumT::min and EnumT::max are given, then EnumT::min <= EnumT::max
// - EnumT::none must be given, and !(EnumT::min <= EnumT::none <= EnumT::max)
// EnumT::min and EnumT::max default to the minimum / maximum representable
// value of the underlying type if not given.
// Standard enum class types additionally support operator!.
struct enum_standard {};

// The linear enumeration category. Values not equal to the given none-element
// are clipped to the given range. Requires that:
// - If both EnumT::min and EnumT::max are given, then EnumT::min <= EnumT::max
// - EnumT::none must be given, and !(EnumT::min <= EnumT::none <= EnumT::max)
// EnumT::min and EnumT::max default to the minimum / maximum representable
// value of the underlying type respectively if not given.
// Linear enum class types additionally support !, ++, and --.
struct enum_linear {};

// The bitmask enumeration category. Bits common to the given range limits are
// kept constant. Requires that:
// - If both EnumT::min and EnumT::max are given, then all of the set bits of
// EnumT::min must also be set in EnumT::max
// - The underlying type of EnumT must be unsigned
// EnumT::min and EnumT::max default to the minimum / maximum representable
// value of the underlying type respectively if not given.
// Bitmask enum class types additionally support &, &=, |, |=, ^, ^=, and ~.
struct enum_bitmask {};

// The discrete enumeration category. Values not equal to any of the given
// template parameters are mapped to the given none-element.
// zero. Requires that:
// - EnumT::none must be given, and Vals... must not contain EnumT::none
// Discrete enum class types additionally support operator!.
template <class EnumT, EnumT... Vals> struct enum_discrete {};

// The enumeration traits class template. Contains only one member typedef:
// - category: The enumeration category to use for this enumeration type
// User-defined enumeration types may specialize this template.
template <class EnumT> struct enum_traits {
using category = enum_default;
};

// The enumeration category traits class template. Contains:
// - typedef CatT category;
// - template <class EnumT> static bool valid()
// Checks during compile-time whether the given enumeration type satisfies
// the category's constraints. (will become a concept bool later)
// - template <class EnumT, class ValT> static EnumT enum_cast(ValT x)
// Maps a given value to the enumeration type.
template <class CatT> struct enum_category_traits;

namespace details {

template <class EnumT>
constexpr bool is_scoped_enum_f(std::false_type) noexcept {
return false;
}

template <class EnumT>
constexpr bool is_scoped_enum_f(std::true_type) noexcept {
return !std::is_convertible_v<EnumT, std::underlying_type_t<EnumT>>;
}

template <class EnumT>
struct is_scoped_enum
: std::integral_constant<bool,
is_scoped_enum_f<EnumT>(std::is_enum<EnumT>())> {};

template <class EnumT>
inline constexpr bool is_scoped_enum_v = is_scoped_enum<EnumT>::value;

template <class EnumT> constexpr auto value_cast_impl(EnumT x) noexcept {
return static_cast<std::underlying_type_t<EnumT>>(x);
}

template <class CatT> struct is_enum_category_discrete : std::false_type {};

template <class EnumT, EnumT... Vals>
struct is_enum_category_discrete<enum_discrete<EnumT, Vals...>>
: std::true_type {};

template <class CatT>
concept is_enum_category =
requires { typename enum_category_traits<CatT>::category; };

template <class EnumT>
concept has_enum_category = requires {
typename enum_traits<EnumT>::category;
requires(is_enum_category<typename enum_traits<EnumT>::category>);
};

template <class EnumT> struct get_enum_category {
using type = enum_default;
};

template <class EnumT>
requires has_enum_category<EnumT>
struct get_enum_category<EnumT> {
using type = typename enum_traits<EnumT>::category;
};

} // namespace details

// Obtains the category for a given enumeration type, defaulting to enum_default
// if a valid category cannot be found.
template <class EnumT>
using get_enum_category_t = typename details::get_enum_category<EnumT>::type;

template <class EnumT, class category>
concept is_enum_category = std::same_as<get_enum_category_t<EnumT>, category>;

namespace details {

template <class T> constexpr T clamp(T x, T lo, T hi) noexcept {
return x < lo ? lo : (x > hi ? hi : x);
}

template <class T>
requires std::is_unsigned_v<std::decay_t<T>>
constexpr T bitwise_clamp(T x, T lo, T hi) noexcept {
return (x & hi) | lo;
}

template <class EnumT>
concept enum_has_none_member = requires {
requires(std::is_enum_v<EnumT>);
{ EnumT::none } -> std::same_as<EnumT>;
};

template <class EnumT>
concept enum_has_min_member = requires {
requires(std::is_enum_v<EnumT>);
{ EnumT::min } -> std::same_as<EnumT>;
};

template <class EnumT>
concept enum_has_max_member = requires {
requires(std::is_enum_v<EnumT>);
{ EnumT::max } -> std::same_as<EnumT>;
};

} // namespace details

template <class EnumT>
concept is_enum = std::is_enum_v<EnumT>;

// Checks whether the given enumeration type has a none-element.
template <class EnumT>
concept enum_has_none =
requires { requires(details::enum_has_none_member<EnumT>); };

// Obtains the none-element of a given enumeration type.
template <enum_has_none EnumT> constexpr EnumT enum_none() noexcept {
if constexpr (details::enum_has_none_member<EnumT>) {
return EnumT::none;
}
}

// Checks whether the given enumeration type has a minimum element.
template <class EnumT>
concept enum_has_min = requires {
requires(is_enum<EnumT>);
requires(
!details::is_enum_category_discrete<get_enum_category_t<EnumT>>::value);
};

// Obtains the minimum element of a given enumeration type.
template <enum_has_min EnumT> constexpr EnumT enum_min() noexcept {
if constexpr (details::enum_has_min_member<EnumT>) {
return EnumT::min;
}

return EnumT{std::numeric_limits<std::underlying_type_t<EnumT>>::min()};
}

// Checks whether the given enumeration type has a maximum element.
template <class EnumT>
concept enum_has_max = requires {
requires(is_enum<EnumT>);
requires(
!details::is_enum_category_discrete<get_enum_category_t<EnumT>>::value);
};

// Obtains the maximum element of a given enumeration type.
template <enum_has_max EnumT> constexpr EnumT enum_max() noexcept {
if constexpr (details::enum_has_max_member<EnumT>) {
return EnumT::max;
}

return EnumT{std::numeric_limits<std::underlying_type_t<EnumT>>::max()};
}

// Casts an enumeration value to its underlying type.
template <is_enum EnumT,
class CatTraits = enum_category_traits<get_enum_category_t<EnumT>>>
constexpr auto value_cast(EnumT x) noexcept {
static_assert(CatTraits::template valid<EnumT>());
return details::value_cast_impl(x);
}

template <class ValT, class EnumT>
concept can_cast = requires {
requires(std::is_convertible_v<ValT, std::underlying_type_t<EnumT>>);
};

// Casts a value to the given enumeration type.
template <is_enum EnumT, can_cast<EnumT> ValT,
typename CatTraits = enum_category_traits<get_enum_category_t<EnumT>>>
constexpr EnumT enum_cast(ValT x) noexcept {
static_assert(CatTraits::template valid<EnumT>());
return CatTraits::template enum_cast<EnumT>(x);
}

// Constrains a given value by the given enumeration type, as if by casting it
// to and from the enumeration type.
template <is_enum EnumT, can_cast<EnumT> ValT>
constexpr std::underlying_type_t<EnumT> value_cast(ValT x) noexcept {
return value_cast(enum_cast<EnumT>(x));
}

// Constrains the given enumeration type, as if by casting it to and from its
// underlying type.
template <is_enum EnumT,
typename CatTraits = enum_category_traits<get_enum_category_t<EnumT>>>
constexpr EnumT enum_cast(EnumT x) noexcept {
return enum_cast<EnumT>(value_cast(x));
}

inline namespace enum_operators {

// Returns true if lhs is equal to EnumT::none. Only supports enum class types
// that have a none-element.
template <class EnumT>
requires(details::is_scoped_enum_v<EnumT> && enum_has_none<EnumT>)
constexpr bool operator!(const EnumT &lhs) noexcept {
return lhs == enum_none<EnumT>();
}

template <class EnumT>
concept is_linear_enum_class = requires {
requires(details::is_scoped_enum_v<EnumT>);
requires(is_enum_category<EnumT, enum_linear>);
};

template <class EnumT>
concept is_bitmask_enum_class = requires {
requires(details::is_scoped_enum_v<EnumT>);
requires(is_enum_category<EnumT, enum_bitmask>);
};

// Pre-increments lhs if it is equal to neither the none-element nor the maximum
// element. Only supports linear enum class types.
template <is_linear_enum_class EnumT>
constexpr EnumT &operator++(EnumT &lhs) noexcept {
if (!(!lhs || lhs == enum_max<EnumT>())) {
lhs = enum_cast<EnumT>(value_cast(lhs) + 1);
}
return lhs;
}

// Post-increments lhs if it is equal to neither the none-element nor the
// maximum element. Only supports linear enum class types.
template <is_linear_enum_class EnumT>
constexpr EnumT operator++(const EnumT &lhs, int) noexcept {
EnumT ret = lhs;
++lhs;
return ret;
}

// Pre-decrements lhs if it is equal to neither the none-element nor the maximum
// element. Only supports linear enum class types.
template <is_linear_enum_class EnumT>
constexpr EnumT &operator--(EnumT &lhs) noexcept {
if (!(!lhs || lhs == enum_min<EnumT>())) {
lhs = enum_cast<EnumT>(value_cast(lhs) - 1);
}
return lhs;
}

// Post-decrements lhs if it is equal to neither the none-element nor the
// maximum element. Only supports linear enum class types.
template <is_linear_enum_class EnumT>
constexpr EnumT operator--(const EnumT &lhs, int) noexcept {
EnumT ret = lhs;
--lhs;
return ret;
}

// If neither operand is EnumT::None, returns the union of the two bit masks.
// Otherwise returns EnumT::None. Only supports bitmask enum class types.
template <is_bitmask_enum_class EnumT>
constexpr EnumT operator|(const EnumT &lhs, const EnumT &rhs) noexcept {
if constexpr (enum_has_none<EnumT>) {
if (!lhs || !rhs) {
return enum_none<EnumT>();
}
}
return enum_cast<EnumT>(static_cast<std::underlying_type_t<EnumT>>(
value_cast(lhs) | value_cast(rhs)));
}

// If neither operand is EnumT::None, assigns (lhs | rhs) to lhs. The operands
// are not interchangeable because (lhs |= EnumT::None) != (lhs | EnumT::None).
// Only supports bitmask enum class types.
template <is_bitmask_enum_class EnumT>
constexpr EnumT &operator|=(EnumT &lhs, const EnumT &rhs) noexcept {
if constexpr (enum_has_none<EnumT>) {
if (!(!lhs || !rhs)) {
lhs = enum_cast<EnumT>(static_cast<std::underlying_type_t<EnumT>>(
value_cast(lhs) | value_cast(rhs)));
}
} else {
lhs = enum_cast<EnumT>(static_cast<std::underlying_type_t<EnumT>>(
value_cast(lhs) | value_cast(rhs)));
}
return lhs;
}

// If neither operand is EnumT::None, returns the intersection of the two bit
// masks. Otherwise returns EnumT::None. Only supports bitmask enum class types.
template <is_bitmask_enum_class EnumT>
constexpr EnumT operator&(const EnumT &lhs, const EnumT &rhs) noexcept {
if constexpr (enum_has_none<EnumT>) {
if (!lhs || !rhs) {
return enum_none<EnumT>();
}
}
return enum_cast<EnumT>(static_cast<std::underlying_type_t<EnumT>>(
value_cast(lhs) & value_cast(rhs)));
}

// If neither operand is EnumT::None, assigns (lhs & rhs) to lhs. The operands
// are not interchangeable because (lhs &= EnumT::None) != (lhs & EnumT::None).
// Only supports bitmask enum class types.
template <is_bitmask_enum_class EnumT>
constexpr EnumT &operator&=(EnumT &lhs, const EnumT &rhs) noexcept {
if constexpr (enum_has_none<EnumT>) {
if (!(!lhs || !rhs)) {
lhs = enum_cast<EnumT>(static_cast<std::underlying_type_t<EnumT>>(
value_cast(lhs) & value_cast(rhs)));
}
} else {
lhs = enum_cast<EnumT>(static_cast<std::underlying_type_t<EnumT>>(
value_cast(lhs) & value_cast(rhs)));
}
return lhs;
}

// If neither operand is EnumT::None, returns the symmetric difference of the
// two bit masks. Otherwise returns EnumT::None. Only supports bitmask enum
// class types.
template <is_bitmask_enum_class EnumT>
constexpr EnumT operator^(const EnumT &lhs, const EnumT &rhs) noexcept {
if constexpr (enum_has_none<EnumT>) {
if (!lhs || !rhs) {
return enum_none<EnumT>();
}
}
return enum_cast<EnumT>(static_cast<std::underlying_type_t<EnumT>>(
value_cast(lhs) ^ value_cast(rhs)));
}

// If neither operand is EnumT::None, assigns (lhs ^ rhs) to lhs. The operands
// are not interchangeable because (lhs ^= EnumT::None) != (lhs ^ EnumT::None).
// Only supports bitmask enum class types.
template <is_bitmask_enum_class EnumT>
constexpr EnumT &operator^=(EnumT &lhs, const EnumT &rhs) noexcept {
if constexpr (enum_has_none<EnumT>) {
if (!(!lhs || !rhs)) {
lhs = enum_cast<EnumT>(static_cast<std::underlying_type_t<EnumT>>(
value_cast(lhs) ^ value_cast(rhs)));
}
} else {
lhs = enum_cast<EnumT>(static_cast<std::underlying_type_t<EnumT>>(
value_cast(lhs) ^ value_cast(rhs)));
}
return lhs;
}

// If lhs is not equal to EnumT::None, toggles all bits in lhs. Otherwise
// returns EnumT::None. Only supports bitmask enum class types.
template <is_bitmask_enum_class EnumT>
constexpr EnumT operator~(const EnumT &lhs) noexcept {
if constexpr (enum_has_none<EnumT>) {
if (!lhs) {
return enum_none<EnumT>();
}
}
return enum_cast<EnumT>(~value_cast(lhs));
}

} // namespace enum_operators

template <> struct enum_category_traits<enum_default> {
using category = enum_default;

template <is_enum_category<category> EnumT>
static constexpr bool valid() noexcept {
return true;
}

template <is_enum_category<category> EnumT, class ValT>
static constexpr EnumT enum_cast(ValT x) noexcept {
return static_cast<EnumT>(x);
}
};

template <> struct enum_category_traits<enum_standard> {
using category = enum_standard;

template <is_enum_category<category> EnumT>
static constexpr bool valid() noexcept {
if constexpr (enum_has_none<EnumT>) {
constexpr auto xnone = details::value_cast_impl(enum_none<EnumT>());
constexpr auto xmin = details::value_cast_impl(enum_min<EnumT>());
constexpr auto xmax = details::value_cast_impl(enum_max<EnumT>());
return xmin <= xmax && xnone != details::clamp(xnone, xmin, xmax);
} else {
return false;
}
}

template <is_enum_category<category> EnumT, class ValT>
static constexpr EnumT enum_cast(ValT x) noexcept {
constexpr auto xnone = details::value_cast_impl(enum_none<EnumT>());
constexpr auto xmin = details::value_cast_impl(enum_min<EnumT>());
constexpr auto xmax = details::value_cast_impl(enum_max<EnumT>());
return static_cast<EnumT>(x == details::clamp(x, xmin, xmax) ? x : xnone);
}
};

template <> struct enum_category_traits<enum_linear> {
using category = enum_linear;

template <is_enum_category<category> EnumT>
static constexpr bool valid() noexcept {
if constexpr (enum_has_none<EnumT>) {
constexpr auto xnone = details::value_cast_impl(enum_none<EnumT>());
constexpr auto xmin = details::value_cast_impl(enum_min<EnumT>());
constexpr auto xmax = details::value_cast_impl(enum_max<EnumT>());
return xmin <= xmax && xnone != details::clamp(xnone, xmin, xmax);
} else
return false;
}

template <is_enum_category<category> EnumT, class ValT>
static constexpr EnumT enum_cast(ValT x) noexcept {
constexpr auto xnone = details::value_cast_impl(enum_none<EnumT>());
constexpr auto xmin = details::value_cast_impl(enum_min<EnumT>());
constexpr auto xmax = details::value_cast_impl(enum_max<EnumT>());
return static_cast<EnumT>(x == xnone ? xnone
: details::clamp(x, xmin, xmax));
}
};

template <> struct enum_category_traits<enum_bitmask> {
using category = enum_bitmask;

template <is_enum_category<category> EnumT>
static constexpr bool valid() noexcept {
constexpr auto xmin = details::value_cast_impl(enum_min<EnumT>());
constexpr auto xmax = details::value_cast_impl(enum_max<EnumT>());
if constexpr (std::is_unsigned_v<std::underlying_type_t<EnumT>> &&
(xmin & xmax) == xmin) {
if constexpr (enum_has_none<EnumT>) {
constexpr auto xnone = details::value_cast_impl(enum_none<EnumT>());
return xnone != details::bitwise_clamp(xnone, xmin, xmax);
} else {
return true;
}
} else {
return false;
}
}

template <is_enum_category<category> EnumT, class ValT>
static constexpr EnumT enum_cast(ValT x) noexcept {
constexpr auto xmin = details::value_cast_impl(enum_min<EnumT>());
constexpr auto xmax = details::value_cast_impl(enum_max<EnumT>());
if constexpr (!enum_has_none<EnumT>)
return static_cast<EnumT>(
details::bitwise_clamp<decltype(xmin)>(x, xmin, xmax));
else {
constexpr auto xnone = details::value_cast_impl(enum_none<EnumT>());
return static_cast<EnumT>(
x == xnone ? xnone
: details::bitwise_clamp<decltype(xmin)>(x, xmin, xmax));
}
}
};

template <class EnumT, EnumT... Vals>
struct enum_category_traits<enum_discrete<EnumT, Vals...>> {
using category = enum_discrete<EnumT, Vals...>;

template <is_enum_category<category> EnumT_>
static constexpr bool valid() noexcept {
if constexpr (enum_has_none<EnumT_>) {
return std::is_same_v<EnumT_, EnumT> && !(... || (Vals == EnumT_::none));
} else {
return false;
}
}

template <is_enum_category<category> EnumT_, class ValT>
requires std::is_convertible_v<EnumT_, EnumT>
static constexpr EnumT enum_cast(ValT x) noexcept {
if ((... || (details::value_cast_impl(Vals) == x))) {
return static_cast<EnumT>(x);
}
return EnumT::none;
}
};

#define ENUM_CLASS_WITH_CATEGORY(NAME, TYPE, CATEGORY) \
enum class NAME : TYPE; \
template <> struct enum_traits<NAME> { \
using category = CATEGORY; \
}; \
enum class NAME : TYPE

// Defines a standard-layout enum class as by:
// enum class NAME : TYPE
// with a specialization of enum_traits<NAME>.
#define ENUM_CLASS_STANDARD(NAME, TYPE) \
ENUM_CLASS_WITH_CATEGORY(NAME, TYPE, enum_standard)

// Defines a linear enum class as by:
// enum class NAME : TYPE
// with a specialization of enum_traits<NAME>.
#define ENUM_CLASS_LINEAR(NAME, TYPE) \
ENUM_CLASS_WITH_CATEGORY(NAME, TYPE, enum_linear)

// Defines a bitmask enum class as by:
// enum class NAME : TYPE
// with a specialization of enum_traits<NAME>.
#define ENUM_CLASS_BITMASK(NAME, TYPE) \
ENUM_CLASS_WITH_CATEGORY(NAME, TYPE, enum_bitmask)

enum StructDefault : int { Default0, Default1, Default2 };

void a() {
value_cast(Default0); // (int)0
enum_cast<StructDefault>(0); // Default0
enum_cast<StructDefault>(3); // (StructDefault)3
}

ENUM_CLASS_STANDARD(StructStandard, unsigned){
s0, s1, s2, s3, none = (unsigned)-1, min = s0, max = s3,
};

void b() {
value_cast(StructStandard::s0); // (unsigned)0
enum_cast<StructStandard>(1u); // StructStandard::s1
enum_cast<StructStandard>((unsigned)-1); // StructStandard::none
enum_cast<StructStandard>(4u); // StructStandard::none
value_cast<StructStandard>(4u); // (unsigned)-1
enum_cast(StructStandard{4u}); // StructStandard::none
}

ENUM_CLASS_LINEAR(StructLinear, int){
l0, l1, l2, l3, none = -1, min = l0, max = l3,
};

void c() {
enum_cast<StructLinear>(0); // StructLinear::l0
enum_cast<StructLinear>(-1); // StructLinear::none
enum_cast<StructLinear>(3); // StructLinear::l3
enum_cast<StructLinear>(4); // StructLinear::l3
enum_cast<StructLinear>(-128); // StructLinear::l0
auto l = StructLinear::l2;
++l; // StructLinear::l3
++l; // StructLinear::l3
--l;
--l;
--l;
--l; // StructLinear::l0
}

ENUM_CLASS_BITMASK(StructBitmask, unsigned){
b0 = 1, b1 = 2, b3 = 8, min = 0, max = b0 | b1 | b3,
};

void d() {
enum_cast<StructBitmask>(0); // StructBitmask::min
enum_cast<StructBitmask>(2); // StructBitmask::b1
enum_cast<StructBitmask>(4); // StructBitmask::min
enum_cast<StructBitmask>(13); // StructBitmask::b0 | StructBitmask::b3
auto b = ~StructBitmask::b0; // StructBitmask::b1 | StructBitmask::b3
b ^= StructBitmask::max; // StructBitmask::b0
}

enum class StructDiscrete {
d0 = 123,
d1 = 456,
d2 = 789,
none = -1,
};
template <> struct enum_traits<StructDiscrete> {
using category = enum_discrete<StructDiscrete, StructDiscrete::d0,
StructDiscrete::d1, StructDiscrete::d2>;
};

void e() {
enum_cast<StructDiscrete>(0); // StructDiscrete::none
enum_cast<StructDiscrete>(123); // StructDiscrete::d0
enum_cast<StructDiscrete>(456); // StructDiscrete::d1
enum_cast<StructDiscrete>(789); // StructDiscrete::d2
enum_cast<StructDiscrete>(788); // StructDiscrete::none
}

#include <iostream>

void test(StructStandard d) {
std::cout << value_cast(d) << std::endl;
const auto t = enum_cast<StructStandard>(d);
std::cout << value_cast(t) << std::endl;
}

int main() { test(static_cast<StructStandard>(150)); }