based

Opinionated utility library
git clone git://git.dimitrijedobrota.com/based.git
Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING

commit 951e89cdfcbc8e30c2a3535bf33de7ec1439e3bd
parent 2346d85f705b04c4a9d1847a6c49e1cde7f81665
author Dimitrije Dobrota < mail@dimitrijedobrota.com >
date Thu, 19 Jun 2025 11:52:10 +0200

Rework enums, work in progress

Diffstat:
M include/based/enum/enum.hpp | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --------------
D include/based/enum/enum_flag.hpp | ---------------------------------------------------------------------------------
M test/CMakeLists.txt | ++ --
A test/source/enum/bitmask_test.cpp | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A test/source/enum/standard_test.cpp | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
D test/source/enum_flag_test.cpp | ---------------------------------------------------------------------------------
D test/source/enum_test.cpp | ---------------------------------------------------------------------------------

7 files changed, 928 insertions(+), 501 deletions(-)


diff --git a/ include/based/enum/enum.hpp b/ include/based/enum/enum.hpp

@@ -1,144 +1,677 @@

#pragma once

#include "based/assert.hpp"
#include "based/container/array.hpp"
#include "based/macro/foreach.hpp"
#include "based/macro/foreach_1.hpp"
#include "based/macro/foreach_2.hpp"
#include "based/types/types.hpp"
#include "based/utility/forward.hpp"
#include "based/algorithms/clamp.hpp"
#include "based/concepts/is/enum.hpp"
#include "based/concepts/is/same.hpp"

// NOLINTBEGIN(*macro-usage*)
namespace based::enumeration
{

#define BASED_E_DETAIL_NUMARGS(...) based::u(std::array {__VA_ARGS__}.size())
namespace category
{

#define BASED_E_DETAIL_LIST_ELEM_STR(Name, Index) #Name,
/*
* The default enumeration category
*
* Conversion is equivalent to static_cast
* Unspecified enum_traits use this category
*/
struct def
{
};

#define BASED_E_DETAIL_LIST_STR(...) \
BASED_FOREACH(BASED_E_DETAIL_LIST_ELEM_STR, __VA_ARGS__)
/*
* The standard enumeration category
*
* Values outside the given range are mapped to a given none-element
*
* Requires:
* - If both Enum::min and Enum::max are given, then Enum::min <= Enum::max
* - Enum::none must be given, and !(Enum::min <= Enum::none <= Enum::max)
* Enum::min and Enum::max default to min/max value of the underlying type
*
* Additional operators:
* - operator!
*/
struct standard
{
};

#define BASED_E_DETAIL_SET(Var, Value) \
/* NOLINTNEXTLINE(*macro-parentheses*) */ \
decltype(Var) Var = decltype(Var) {Value};
/*
* The linear enumeration category
*
* Values outside the given range are clipped to the given range
*
* Requires:
* - If both Enum::min and Enum::max are given, then Enum::min <= Enum::max
* - Enum::none must be given, and !(Enum::min <= Enum::none <= Enum::max)
* Enum::min and Enum::max default to min/max value of the underlying type
*
* Additional operators:
* - operator!
* - operator++
* - operator--
*/
struct linear
{
};

#define BASED_E_DETAIL_DECLARE_VAL(Name, Var, Index) static const Name Var;
/*
* The bitmask enumeration category
*
* Bits common to the given range limits are kept constant
*
* Requires:
* - If both Enum::min and Enum::max are given, all of the bits set in
* Enum::min must also be set in Enum::max
* - The underlying type of Enum must be unsigned
* Enum::min and Enum::max default to min/max value of the underlying type
*
* Additional operators:
* - operator&
* - operator&=
* - operator|
* - operator|=
* - operator^
* - operator^=
* - operator~
*/
struct bitmask
{
};

#define BASED_E_DETAIL_DECLARE_CASE(Qualifier, Var, Index) \
case Qualifier::Var(): \
return Var;
/*
* The descrete enumeration category
*
* Values not equal to any of the given template parameters are mapped to the
* given none-element
*
* Requires:
* - Enum::none must be given, and vals... must contain Enum::none
* Enum::min and Enum::max default to min/max value of the underlying type
*
* Additional operators:
* - operator!
*/
template<class Enum, Enum... vals>
struct discrete
{
};

#define BASED_E_DETAIL_DEFINE_VAL(Qualifier, Initial, Var, Index) \
inline constexpr BASED_E_DETAIL_SET( \
Qualifier::Var, \
Qualifier::value_type {Initial} + Qualifier::size \
- Qualifier::value_type {Index} - Qualifier::value_type {1_u} \
)
} // namespace category

#define BASED_E_DETAIL_DEFINE_NAMES(Qualifier, ...) \
inline constexpr BASED_E_DETAIL_SET( \
Qualifier::names, BASED_E_DETAIL_LIST_STR(__VA_ARGS__) \
)
namespace enum_traits
{

#define BASED_E_DETAIL_DEFINE_GET(Qualifier, Type, ...) \
inline const Qualifier& Qualifier::get(Type idx) \
{ \
/* NOLINTNEXTLINE(*paths-covered*) */ \
switch (static_cast<Type::basic_type>(idx)) { \
BASED_FOREACH_1(Qualifier, BASED_E_DETAIL_DECLARE_CASE, __VA_ARGS__) \
default: \
break; \
} \
assert(0); /* NOLINT(*assert*,cert-dcl03-c) */ \
}

#define BASED_E_DETAIL_DEFINE(Qualifier, Type, Initial, ...) \
BASED_FOREACH_2(Qualifier, Initial, BASED_E_DETAIL_DEFINE_VAL, __VA_ARGS__) \
BASED_E_DETAIL_DEFINE_NAMES(Qualifier, __VA_ARGS__) \
BASED_E_DETAIL_DEFINE_GET(Qualifier, Type, __VA_ARGS__)

#define BASED_DECLARE_ARRAY(Name, Initial) \
template<typename T> \
class array : public based::array<T, Name::size_type, Name::size> \
{ \
using enum_type = Name; \
using base = based::array<T, Name::size_type, enum_type::size>; \
using base::operator[]; \
using base::at; \
\
public: \
constexpr array() noexcept \
: base() \
{ \
} \
\
template<class... Args> \
requires(enum_type::size_type(sizeof...(Args)) == enum_type::size) \
constexpr explicit array(Args&&... args \
) noexcept /* NOLINTNEXTLINE(*decay*) */ \
: base({based::forward<Args>(args)...}) \
{ \
} \
\
const T& operator[](enum_type val) const \
{ \
return base::operator[](val.value - enum_type::value_type {Initial}); \
} \
\
T& operator[](enum_type val) \
{ \
return base::operator[](val.value - enum_type::value_type {Initial}); \
} \
\
const T& at(enum_type val) const \
{ \
return base::operator[](val.value - enum_type::value_type {Initial}); \
} \
\
T& at(enum_type val) \
{ \
return base::operator[](val.value - enum_type::value_type {Initial}); \
} \
/*
* Enumeration traits class template
*
* Requires:
* - IsScopedEnum<T>
*
* Contains:
* - category_type: The Enumeration category used for this type
* - value_type: The underlying type for this type
*/
template<class T>
requires IsScopedEnum<T>
// TODO: check for proper int type
struct traits
{
/* TODO (provide defaults)
using category = enum_default;
using value_type = based::u8;
*/
};

template<class Enum>
concept has_category = requires { typename traits<Enum>::category; };

template<class Enum>
concept has_value_type = requires { typename traits<Enum>::value_type; };

template<has_category Enum>
using category_type = typename traits<Enum>::category;

template<has_value_type Enum>
using value_type = typename traits<Enum>::value_type;

template<has_value_type Enum>
using basic_type = typename traits<Enum>::value_type::basic_type;

template<class Enum>
concept has_none = requires {
{
Enum::none
};
};

template<class Enum>
concept has_min = requires {
{
Enum::min
};
};

template<class Enum>
concept has_max = requires {
{
Enum::max
};
};

template<has_none Enum>
static constexpr Enum none = Enum::none;

template<has_min Enum>
static constexpr Enum min = Enum::min;

template<has_max Enum>
static constexpr Enum max = Enum::max;

template<class Enum, class Category>
concept is_category = requires {
requires(has_category<Enum>);
requires(SameAs<Category, category_type<Enum>>);
};

template<class Enum>
concept is_default = is_category<Enum, category::def>;

template<class Enum>
concept is_standard = requires {
requires(is_category<Enum, category::standard>);
requires(has_none<Enum>);
requires(
!has_min<Enum> || !has_max<Enum>
|| !(min<Enum> <= none<Enum> && none<Enum> <= max<Enum>)
);
};

template<class Enum>
concept is_linear = requires {
requires(is_category<Enum, category::linear>);
requires(has_none<Enum>);
requires(
!has_min<Enum> || !has_max<Enum>
|| !(min<Enum> <= none<Enum> && none<Enum> <= max<Enum>)
);
};

template<class Enum>
concept is_bitmask = requires {
requires(is_category<Enum, category::bitmask>);
requires(has_min<Enum>);
requires(has_max<Enum>);
};

} // namespace enum_traits

namespace detail
{

template<class T>
// TODO: requires T to be unsigned
constexpr T bitwise_clamp(T value, T low, T high) noexcept
{
return (value & high) | low;
}

template<class T>
constexpr T clamp(T value, T low, T high) noexcept
{
return based::clamp(value, low, high);
}

template<class Enum>
constexpr enum_traits::value_type<Enum> value_cast_impl(Enum value) noexcept
{
return static_cast<enum_traits::value_type<Enum>>(
static_cast<enum_traits::basic_type<Enum>>(value)
);
}

template<class Enum>
constexpr Enum enum_cast_impl(enum_traits::value_type<Enum> value) noexcept
{
// NOLINTNEXTLINE(*EnumCastOutOfRange*)
return static_cast<Enum>(static_cast<enum_traits::basic_type<Enum>>(value));
}

} // namespace detail

namespace category_traits
{

/*
* Enumeration category traits class template
*
* Contains:
* - typename category
* - template<class Enum> static bool valid()
* Check during compile-time whether the given enumeration type satisfies
* the category's constraints
* - template<class Enum> static Enum enum_cast(traits::value_type<Enum>)
* Maps a given value to the enumeration type
*/
template<class Category>
struct traits;

template<>
struct traits<category::def>
{
using category = category::def;

template<enum_traits::is_category<category> Enum>
static constexpr bool valid() noexcept
{
return true;
}

template<enum_traits::is_category<category> Enum>
static constexpr Enum enum_cast(enum_traits::value_type<Enum> value) noexcept
{
return static_cast<Enum>(value);
}
};

template<>
struct traits<category::standard>
{
using category = category::standard;

template<enum_traits::is_category<category> Enum>
static constexpr bool valid() noexcept
{
if constexpr (enum_traits::has_none<Enum>) {
constexpr auto xnone = detail::value_cast_impl(enum_traits::none<Enum>);
constexpr auto xmin = detail::value_cast_impl(enum_traits::min<Enum>);
constexpr auto xmax = detail::value_cast_impl(enum_traits::max<Enum>);
return xmin <= xmax && xnone != detail::clamp(xnone, xmin, xmax);
}

return false;
}

template<enum_traits::is_category<category> Enum>
static constexpr Enum enum_cast(enum_traits::value_type<Enum> value) noexcept
{
constexpr auto xnone = detail::value_cast_impl(enum_traits::none<Enum>);
constexpr auto xmin = detail::value_cast_impl(enum_traits::min<Enum>);
constexpr auto xmax = detail::value_cast_impl(enum_traits::max<Enum>);
return detail::enum_cast_impl<Enum>(
value == detail::clamp(value, xmin, xmax) ? value : xnone
);
}
};

template<>
struct traits<category::linear>
{
using category = category::linear;

template<enum_traits::is_category<category> Enum>
static constexpr bool valid() noexcept
{
if constexpr (enum_traits::has_none<Enum>) {
constexpr auto xnone = detail::value_cast_impl(enum_traits::none<Enum>);
constexpr auto xmin = detail::value_cast_impl(enum_traits::min<Enum>);
constexpr auto xmax = detail::value_cast_impl(enum_traits::max<Enum>);
return xmin <= xmax && xnone != detail::clamp(xnone, xmin, xmax);
} else {
return false;
}
}

template<enum_traits::is_category<category> Enum>
static constexpr Enum enum_cast(enum_traits::value_type<Enum> x) noexcept
{
constexpr auto xnone = detail::value_cast_impl(enum_traits::none<Enum>);
constexpr auto xmin = detail::value_cast_impl(enum_traits::min<Enum>);
constexpr auto xmax = detail::value_cast_impl(enum_traits::max<Enum>);
return detail::enum_cast_impl<Enum>(
x == xnone ? xnone : detail::clamp(x, xmin, xmax)
);
}
};

template<>
struct traits<category::bitmask>
{
using category = category::bitmask;

template<enum_traits::is_category<category> Enum>
static constexpr bool valid() noexcept
{
constexpr auto xmin = detail::value_cast_impl(enum_traits::min<Enum>);
constexpr auto xmax = detail::value_cast_impl(enum_traits::max<Enum>);

if constexpr ((xmin & xmax) == xmin) {
if constexpr (enum_traits::has_none<Enum>) {
constexpr auto xnone = detail::value_cast_impl(enum_traits::none<Enum>);
return xnone != detail::bitwise_clamp(xnone, xmin, xmax);
}
return true;
}

return false;
}

template<enum_traits::is_category<category> Enum>
static constexpr Enum enum_cast(enum_traits::value_type<Enum> value) noexcept
{
constexpr auto xmin = detail::value_cast_impl(enum_traits::min<Enum>);
constexpr auto xmax = detail::value_cast_impl(enum_traits::max<Enum>);

if constexpr (!enum_traits::has_none<Enum>) {
return detail::enum_cast_impl<Enum>(
detail::bitwise_clamp<decltype(xmin)>(value, xmin, xmax)
);
} else {
constexpr auto xnone = detail::value_cast_impl(enum_traits::none<Enum>);
return detail::enum_cast_impl<Enum>(
value == xnone
? xnone
: detail::bitwise_clamp<decltype(xmin)>(value, xmin, xmax)
);
}
}
};

template<class Enum, Enum... vals>
struct traits<category::discrete<Enum, vals...>>
{
using category = category::discrete<Enum, vals...>;

template<enum_traits::is_category<category> EnumI>
requires SameAs<Enum, EnumI>
static constexpr bool valid() noexcept
{
if constexpr (enum_traits::has_none<Enum>) {
return !(... || (vals == Enum::none));
} else {
return false;
}
}

template<enum_traits::is_category<category> EnumI>
requires SameAs<Enum, EnumI>
static constexpr Enum enum_cast(enum_traits::value_type<Enum> value) noexcept
{
if ((... || (detail::value_cast_impl(vals) == value))) {
return detail::enum_cast_impl<Enum>(value);
}
return Enum::none;
}
};

template<class Enum>
static constexpr auto valid() noexcept
{
return traits<enum_traits::category_type<Enum>>::template valid<Enum>();
}

template<class Enum>
static constexpr auto enum_cast(enum_traits::value_type<Enum> value) noexcept
{
return traits<enum_traits::category_type<Enum>>::template enum_cast<Enum>(
value
);
}

} // namespace category_traits

template<class Enum>
constexpr enum_traits::value_type<Enum> value_cast(Enum value) noexcept
{
static_assert(category_traits::valid<Enum>());
return detail::value_cast_impl(value);
}

template<class Enum>
constexpr Enum enum_cast(enum_traits::value_type<Enum> value) noexcept
{
static_assert(category_traits::valid<Enum>());
return category_traits::enum_cast<Enum>(value);
}

template<class Enum>
constexpr Enum enum_cast(Enum value) noexcept
{
static_assert(category_traits::valid<Enum>());
return category_traits::enum_cast<Enum>(value_cast<Enum>(value));
}

inline namespace operators
{

template<enum_traits::has_none Enum>
constexpr bool operator!(const Enum& lhs) noexcept
{
return lhs == enum_traits::none<Enum>;
}

inline namespace op_inc
{

template<enum_traits::is_linear Enum>
constexpr Enum& operator++(Enum& lhs) noexcept
{
if (!(!lhs || lhs == enum_traits::max<Enum>)) {
lhs = enum_cast<Enum>(value_cast(lhs) + 1);
}
return lhs;
}

template<enum_traits::is_linear Enum>
constexpr Enum operator++(const Enum& lhs, int) noexcept
{
Enum ret = lhs;
++lhs;
return ret;
}

} // namespace op_inc

inline namespace op_dec
{

#define BASED_DECLARE_ENUM(Name, Type, Initial, ...) \
struct Name \
template<enum_traits::is_linear Enum>
constexpr Enum& operator--(Enum& lhs) noexcept
{
if (!(!lhs || lhs == enum_traits::min<Enum>)) {
lhs = enum_cast<Enum>(value_cast(lhs) - 1);
}
return lhs;
}

template<enum_traits::is_linear Enum>
constexpr Enum operator--(const Enum& lhs, int) noexcept
{
Enum ret = lhs;
--lhs;
return ret;
}

} // namespace op_dec

inline namespace op_or
{

template<enum_traits::is_bitmask Enum>
constexpr Enum operator|(const Enum& lhs, const Enum& rhs) noexcept
{
if constexpr (enum_traits::has_none<Enum>) {
if (!lhs || !rhs) {
return enum_traits::none<Enum>;
}
}
return enum_cast<Enum>(value_cast(lhs) | value_cast(rhs));
}

template<enum_traits::is_bitmask Enum>
constexpr Enum& operator|=(Enum& lhs, const Enum& rhs) noexcept
{
if constexpr (enum_traits::has_none<Enum>) {
if (!(!lhs || !rhs)) {
return lhs = enum_cast<Enum>(value_cast(lhs) | value_cast(rhs));
}
} else {
return lhs = enum_cast<Enum>(value_cast(lhs) | value_cast(rhs));
}
return lhs;
}

} // namespace op_or

inline namespace op_and
{

template<enum_traits::is_bitmask Enum>
constexpr Enum operator&(const Enum& lhs, const Enum& rhs) noexcept
{
if constexpr (enum_traits::has_none<Enum>) {
if (!lhs || !rhs) {
return enum_traits::none<Enum>;
}
}
return enum_cast<Enum>(value_cast(lhs) & value_cast(rhs));
}

template<enum_traits::is_bitmask Enum>
constexpr Enum& operator&=(Enum& lhs, const Enum& rhs) noexcept
{
if constexpr (enum_traits::has_none<Enum>) {
if (!(!lhs || !rhs)) {
return lhs = enum_cast<Enum>(value_cast(lhs) & value_cast(rhs));
}
} else {
return lhs = enum_cast<Enum>(value_cast(lhs) & value_cast(rhs));
}
return lhs;
}

} // namespace op_and

inline namespace op_xor
{

template<enum_traits::is_bitmask Enum>
constexpr Enum operator^(const Enum& lhs, const Enum& rhs) noexcept
{
if constexpr (enum_traits::has_none<Enum>) {
if (!lhs || !rhs) {
return enum_traits::none<Enum>;
}
}
return enum_cast<Enum>(value_cast(lhs) ^ value_cast(rhs));
}

template<enum_traits::is_bitmask Enum>
constexpr Enum& operator^=(Enum& lhs, const Enum& rhs) noexcept
{
if constexpr (enum_traits::has_none<Enum>) {
if (!(!lhs || !rhs)) {
return lhs = enum_cast<Enum>(value_cast(lhs) ^ value_cast(rhs));
}
} else {
return lhs = enum_cast<Enum>(value_cast(lhs) ^ value_cast(rhs));
}
return lhs;
}

} // namespace op_xor

inline namespace op_not
{

template<enum_traits::is_bitmask Enum>
constexpr Enum operator~(const Enum& lhs) noexcept
{
if constexpr (enum_traits::has_none<Enum>) {
if (!lhs) {
return enum_traits::none<Enum>;
}
}
return enum_cast<Enum>(~value_cast(lhs));
}

} // namespace op_not

inline namespace shorthand
{

template<enum_traits::is_bitmask Enum>
constexpr Enum& set(Enum& lhs, const Enum& rhs) noexcept
{
return lhs |= rhs;
}

template<enum_traits::is_bitmask Enum>
constexpr Enum& mask(Enum& lhs, const Enum& rhs) noexcept
{
return lhs &= rhs;
}

template<enum_traits::is_bitmask Enum>
constexpr Enum& tgl(Enum& lhs, const Enum& rhs) noexcept
{
return lhs ^= rhs;
}

template<enum_traits::is_bitmask Enum>
constexpr Enum& neg(Enum& lhs) noexcept
{
return lhs = ~lhs;
}

template<enum_traits::is_bitmask Enum>
constexpr Enum& clear(Enum& lhs, const Enum& rhs) noexcept
{
return lhs &= ~rhs;
}

template<enum_traits::is_bitmask Enum>
constexpr bool test(const Enum& lhs, const Enum& rhs) noexcept
{
return (lhs & rhs) == rhs;
}

} // namespace shorthand

} // namespace operators

} // namespace based::enumeration

// NOLINTBEGIN(*macro*)

#define BASED_ENUM_TRAITS(Name, Type, Category) \
template<> \
struct based::enumeration::enum_traits::traits<Name> \
{ \
constexpr explicit Name(Type enum_value) \
: value(enum_value) \
{ \
} \
\
public: \
using category = Category; \
using value_type = Type; \
using size_type = Type; \
\
static constexpr size_type size = \
BASED_E_DETAIL_NUMARGS(BASED_E_DETAIL_LIST_STR(__VA_ARGS__)); \
\
BASED_DECLARE_ARRAY(Name, Initial) \
static const array<const char*> names; \
\
static const Name& get(Type idx); \
\
constexpr auto operator()() const \
{ \
return static_cast<Type::basic_type>(value); \
} \
\
friend bool operator==(Name lhs, Name rhs) \
{ \
return lhs.value == rhs.value; \
} \
\
Type value; \
\
BASED_FOREACH_1(Name, BASED_E_DETAIL_DECLARE_VAL, __VA_ARGS__) \
};

#define BASED_DEFINE_ENUM(Name, Type, Initial, ...) \
BASED_E_DETAIL_DEFINE(Name, Type, Initial, __VA_ARGS__)
#define BASED_ENUM_TRAITS_STANDARD(Name, Type) \
BASED_ENUM_TRAITS(Name, Type, based::enumeration::category::standard)

#define BASED_ENUM_TRAITS_LINEAR(Name, Type) \
BASED_ENUM_TRAITS(Name, Type, based::enumeration::category::linear)

#define BASED_ENUM_TRAITS_BITMASK(Name, Type) \
BASED_ENUM_TRAITS(Name, Type, based::enumeration::category::bitmask)

#define BASED_ENUM(Name, Type, Category) \
enum class Name : typename Type::basic_type

#define BASED_ENUM_STANDARD(Name, Type) \
BASED_ENUM(Name, Type, based::enumeration::category::standard)

#define BASED_ENUM_LINEAR(Name, Type) \
BASED_ENUM(Name, Type, based::enumeration::category::linear)

#define BASED_DEFINE_ENUM_CLASS(Class, Name, Type, Initial, ...) \
BASED_E_DETAIL_DEFINE(Class::Name, Type, Initial, __VA_ARGS__)
#define BASED_ENUM_BITMASK(Name, Type) \
BASED_ENUM(Name, Type, based::enumeration::category::bitmask)

// NOLINTEND(*macro-usage*)
// NOLINTEND(*macro*)

diff --git a/ include/based/enum/enum_flag.hpp b/ include/based/enum/enum_flag.hpp

@@ -1,201 +0,0 @@

#pragma once

#include <array>

#include "based/assert.hpp"
#include "based/macro/foreach.hpp"
#include "based/macro/foreach_1.hpp"
#include "based/types/types.hpp"

// NOLINTBEGIN(*macro-usage*)

#define BASED_EF_DETAIL_NUMARGS(...) based::u(std::array {__VA_ARGS__}.size())

#define BASED_EF_DETAIL_LIST_ELEM_STR(Name, Index) #Name,

#define BASED_EF_DETAIL_LIST_STR(...) \
BASED_FOREACH(BASED_EF_DETAIL_LIST_ELEM_STR, __VA_ARGS__)

#define BASED_EF_DETAIL_SET(Var, Value) \
/* NOLINTNEXTLINE(*macro-parentheses*) */ \
decltype(Var) Var = decltype(Var) {Value};

#define BASED_EF_DETAIL_DECLARE_ENUM_VAL(Name, Var, Index) \
static const Name Var;

#define BASED_EF_DETAIL_DECLARE_ENUM_CASE(Qualifier, Var, Index) \
case Qualifier::Var(): \
return Var;

#define BASED_EF_DETAIL_DEFINE_VAL(Qualifier, Var, Index) \
inline constexpr BASED_EF_DETAIL_SET( \
Qualifier::Var, \
Qualifier::value_type {1} << Qualifier:: \
value_type {Qualifier::size - Qualifier::value_type {Index} - Qualifier::value_type {2}} \
)

#define BASED_EF_DETAIL_DEFINE_VALS(Qualifier, First, ...) \
BASED_FOREACH_1(Qualifier, BASED_EF_DETAIL_DEFINE_VAL, __VA_ARGS__) \
inline constexpr BASED_EF_DETAIL_SET(Qualifier::First, based::u {0})

#define BASED_EF_DETAIL_DEFINE_GET(Qualifier, Type, ...) \
inline const Qualifier& Qualifier::get(Type idx) \
{ \
/* NOLINTNEXTLINE(*paths-covered*) */ \
switch (static_cast<Type::basic_type>(idx)) { \
BASED_FOREACH_1( \
Qualifier, BASED_EF_DETAIL_DECLARE_ENUM_CASE, __VA_ARGS__ \
) \
default: \
break; \
} \
assert(0); /* NOLINT(*assert*,cert-dcl03-c) */ \
}

#define BASED_EF_DETAIL_DEFINE(Qualifier, Type, ...) \
BASED_EF_DETAIL_DEFINE_VALS(Qualifier, __VA_ARGS__) \
BASED_EF_DETAIL_DEFINE_GET(Qualifier, Type, __VA_ARGS__)

#define BASED_DECLARE_ENUM_FLAG(Name, Type, ...) \
struct Name \
{ \
friend struct based::enum_flag_wrapper<Name>; \
\
constexpr explicit Name(Type enum_value) \
: value(enum_value) \
{ \
} \
\
public: \
using value_type = Type; \
using size_type = Type; \
\
static constexpr size_type size = \
BASED_EF_DETAIL_NUMARGS(BASED_EF_DETAIL_LIST_STR(__VA_ARGS__)); \
\
static const Name& get(Type idx); \
\
Name& set(Name val) \
{ \
return *this |= val; \
} \
\
Name& mask(Name val) \
{ \
return *this &= val; \
} \
\
Name& tgl(Name val) \
{ \
return *this ^= val; \
} \
\
Name& neg() \
{ \
return *this = ~*this; \
} \
\
Name& clear(Name val) \
{ \
return *this &= ~val; \
} \
\
bool test(Name val) const \
{ \
return (*this & val) == val; \
} \
\
friend bool operator==(Name lhs, Name rhs) \
{ \
return lhs.value == rhs.value; \
} \
\
friend Name operator|(Name lhs, Name rhs) \
{ \
return Name(lhs.value | rhs.value); \
} \
\
friend Name operator&(Name lhs, Name rhs) \
{ \
return Name(lhs.value & rhs.value); \
} \
\
friend Name operator^(Name lhs, Name rhs) \
{ \
return Name(lhs.value ^ rhs.value); \
} \
\
Name operator~() const \
{ \
return Name(~value); \
} \
\
Name& operator|=(Name rhs) \
{ \
value |= rhs.value; \
return *this; \
} \
\
Name& operator&=(Name rhs) \
{ \
value &= rhs.value; \
return *this; \
} \
\
Name& operator^=(Name rhs) \
{ \
value ^= rhs.value; \
return *this; \
} \
\
constexpr auto operator()() const \
{ \
return static_cast<Type::basic_type>(value); \
} \
\
Type value; \
\
BASED_FOREACH_1(Name, BASED_EF_DETAIL_DECLARE_ENUM_VAL, __VA_ARGS__) \
};

#define BASED_DEFINE_ENUM_FLAG(Name, Type, ...) \
BASED_EF_DETAIL_DEFINE(Name, Type, __VA_ARGS__)

#define BASED_DEFINE_ENUM_FLAG_CLASS(Class, Name, Type, ...) \
BASED_EF_DETAIL_DEFINE(Class::Name, Type, __VA_ARGS__)

namespace based
{

template<class FlagType>
struct enum_flag_wrapper : public FlagType
{
using value_type = FlagType::value_type;

explicit enum_flag_wrapper(value_type& value)
: FlagType(value)
, m_value(&value)
{
}

enum_flag_wrapper& operator=(FlagType flag)
{
*m_value = flag.value;
return *this;
}

~enum_flag_wrapper() { *m_value = FlagType::value; }

enum_flag_wrapper(const enum_flag_wrapper&) = delete;
enum_flag_wrapper& operator=(const enum_flag_wrapper&) = delete;

enum_flag_wrapper(enum_flag_wrapper&&) = default;
enum_flag_wrapper& operator=(enum_flag_wrapper&&) = default;

private:
value_type* m_value;
};

} // namespace based

// NOLINTEND(*macro-usage*)

diff --git a/ test/CMakeLists.txt b/ test/CMakeLists.txt

@@ -75,8 +75,8 @@ add_test(functional function_test)


## ------ Enum -----

add_test(. enum_test)
add_test(. enum_flag_test)
add_test(enum standard_test)
add_test(enum bitmask_test)

## ----- List -----

diff --git a/ test/source/enum/bitmask_test.cpp b/ test/source/enum/bitmask_test.cpp

@@ -0,0 +1,203 @@

#define CATCH_CONFIG_RUNTIME_STATIC_REQUIRE

#include "based/enum/enum.hpp"

#include <catch2/catch_test_macros.hpp>

#include "based/concepts/is/same.hpp"
#include "based/types/types.hpp"

BASED_ENUM_BITMASK(var, based::u8) {
a = 1,
b = 2,
c = 4,
min = 0,
max = a | b | c, // NOLINT
};
BASED_ENUM_TRAITS_BITMASK(var, based::u8)

TEST_CASE("types", "[enum/enum_flag]")
{
STATIC_REQUIRE(requires { typename var; });

STATIC_REQUIRE(requires { var::a; });
STATIC_REQUIRE(requires { var::b; });
STATIC_REQUIRE(requires { var::c; });
}

TEST_CASE("operations", "[enum/enum_flag]")
{
using based::SameAs;

SECTION("operators")
{
using namespace based::enumeration; // NOLINT(*namespace*)

SECTION("eql")
{
STATIC_REQUIRE(requires(var a, var b) {
{
a == b
} -> SameAs<bool>;
});

STATIC_REQUIRE(requires(var a, var b) {
{
a != b
} -> SameAs<bool>;
});
}

SECTION("or")
{
STATIC_REQUIRE(requires(var a, var b) {
{
a | b
} -> SameAs<var>;
});

STATIC_REQUIRE(requires(var a, var b) {
{
a |= b
} -> SameAs<var&>;
});
}

SECTION("and")
{
STATIC_REQUIRE(requires(var a, var b) {
{
a& b
} -> SameAs<var>;
});

STATIC_REQUIRE(requires(var a, var b) {
{
a &= b
} -> SameAs<var&>;
});
}

SECTION("xor")
{
STATIC_REQUIRE(requires(var a, var b) {
{
a ^ b
} -> SameAs<var>;
});

STATIC_REQUIRE(requires(var a, var b) {
{
a ^= b
} -> SameAs<var&>;
});
}

SECTION("not")
{
STATIC_REQUIRE(requires(var a) {
{
~a
} -> SameAs<var>;
});
}

SECTION("set")
{
STATIC_REQUIRE(requires(var a, var b) {
{
set(a, b)
} -> SameAs<var&>;
});
}

SECTION("mask")
{
STATIC_REQUIRE(requires(var a, var b) {
{
mask(a, b)
} -> SameAs<var&>;
});
}

SECTION("tgl")
{
STATIC_REQUIRE(requires(var a, var b) {
{
tgl(a, b)
} -> SameAs<var&>;
});
}

SECTION("neg")
{
STATIC_REQUIRE(requires(var a) {
{
neg(a)
} -> SameAs<var&>;
});
}

SECTION("clear")
{
STATIC_REQUIRE(requires(var a, var b) {
{
clear(a, b)
} -> SameAs<var&>;
});
}

SECTION("test")
{
STATIC_REQUIRE(requires(var a, var b) {
{
test(a, b)
} -> SameAs<bool>;
});
}
}

SECTION("functions")
{
using namespace based::enumeration; // NOLINT(*namespace*)

const auto var1 = var::a | var::b;

REQUIRE(test(var1, var::a));
REQUIRE(test(var1, var::b));
REQUIRE(!test(var1, var::c));

const auto var2 = ~var1;

REQUIRE(!test(var2, var::a));
REQUIRE(!test(var2, var::b));
REQUIRE(test(var2, var::c));

const auto var3 = var1 & var2;

REQUIRE(!test(var3, var::a));
REQUIRE(!test(var3, var::b));
REQUIRE(!test(var3, var::c));

const auto var4 = var1 ^ var2;

REQUIRE(test(var4, var::a));
REQUIRE(test(var4, var::b));
REQUIRE(test(var4, var::c));
}
}

/*
TEST_CASE("enum_flag_wrapper", "[enum/enum_flag_wrapper]")
{
auto flags = 0_u8;
{
auto wrapper = based::enum_flag_wrapper<var>(flags);

wrapper |= var::a;
wrapper |= var::c;
}

REQUIRE(flags == 0x5_u8);
}
*/

diff --git a/ test/source/enum/standard_test.cpp b/ test/source/enum/standard_test.cpp

@@ -0,0 +1,66 @@

#define CATCH_CONFIG_RUNTIME_STATIC_REQUIRE

#include <catch2/catch_test_macros.hpp>

#include "based/concepts/is/invocable.hpp"
#include "based/enum/enum.hpp"
#include "based/types/types.hpp"

struct test
{
BASED_ENUM_STANDARD(var, based::i8) {
a,
b,
c,
min = a,
max = c,
none = -1,
};

[[nodiscard]] int get_var(var req) const;

private:
int m_a = 1;
int m_b = 2;
int m_c = 3;
};

BASED_ENUM_TRAITS_STANDARD(test::var, based::u8)

inline int test::get_var(var req) const
{
switch (based::enumeration::enum_cast<var>(req)) {
case var::a:
return m_a;
case var::b:
return m_b;
case var::c:
return m_c;
case var::none:
return -1;
}
}

TEST_CASE("types", "[enum/standard]")
{
STATIC_REQUIRE(requires { typename test::var; });

STATIC_REQUIRE(requires { test::var::a; });
STATIC_REQUIRE(requires { test::var::b; });
STATIC_REQUIRE(requires { test::var::c; });
STATIC_REQUIRE(requires { test::var::min; });
STATIC_REQUIRE(requires { test::var::max; });
STATIC_REQUIRE(requires { test::var::none; });
}

TEST_CASE("safety", "[enum/standard]")
{
const test crnt;

REQUIRE(crnt.get_var(test::var::a) == 1);
REQUIRE(crnt.get_var(test::var::b) == 2);
REQUIRE(crnt.get_var(test::var::c) == 3);

REQUIRE(!based::Invocable<decltype(&test::get_var), based::u8>);
REQUIRE(!based::Invocable<decltype(&test::get_var), int>);
}

diff --git a/ test/source/enum_flag_test.cpp b/ test/source/enum_flag_test.cpp

@@ -1,87 +0,0 @@

#define CATCH_CONFIG_RUNTIME_STATIC_REQUIRE

#include "based/enum/enum_flag.hpp"

#include <catch2/catch_test_macros.hpp>

#include "based/concepts/comparable/equality.hpp"
#include "based/concepts/comparable/greater.hpp"
#include "based/concepts/comparable/greater_equal.hpp"
#include "based/concepts/comparable/less.hpp"
#include "based/concepts/comparable/less_equal.hpp"
#include "based/concepts/is/same.hpp"
#include "based/types/literals.hpp"
#include "based/types/types.hpp"

using namespace based::literals; // NOLINT(*namespace*)

BASED_DECLARE_ENUM_FLAG(var, based::u8, empty, a, b, c)
BASED_DEFINE_ENUM_FLAG(var, based::u8, empty, a, b, c)

TEST_CASE("types", "[enum/enum_flag]")
{
STATIC_REQUIRE(requires { typename var; });
STATIC_REQUIRE(requires { var::empty; });
STATIC_REQUIRE(requires { var::a; });
STATIC_REQUIRE(requires { var::b; });
STATIC_REQUIRE(requires { var::c; });
STATIC_REQUIRE(var::size == 4_u8);
STATIC_REQUIRE(var::empty.value == 0_u8);
STATIC_REQUIRE(var::a.value == 1_u8);
STATIC_REQUIRE(var::b.value == 2_u8);
STATIC_REQUIRE(var::c.value == 4_u8);
}

TEST_CASE("operations", "[enum/enum_flag]")
{
using based::SameAs;

SECTION("COMPARE")
{
STATIC_REQUIRE(based::EqualityComparable<var>);
STATIC_REQUIRE(!based::LessComparable<var>);
STATIC_REQUIRE(!based::GreaterComparable<var>);
STATIC_REQUIRE(!based::LessEqualComparable<var>);
STATIC_REQUIRE(!based::GreaterEqualComparable<var>);
}

SECTION("functions")
{
const auto var1 = var::a | var::b;

REQUIRE(var1.test(var::a));
REQUIRE(var1.test(var::b));
REQUIRE(!var1.test(var::c));

const auto var2 = ~var1;

REQUIRE(!var2.test(var::a));
REQUIRE(!var2.test(var::b));
REQUIRE(var2.test(var::c));

const auto var3 = var1 & var2;

REQUIRE(!var3.test(var::a));
REQUIRE(!var3.test(var::b));
REQUIRE(!var3.test(var::c));

const auto var4 = var1 ^ var2;

REQUIRE(var4.test(var::a));
REQUIRE(var4.test(var::b));
REQUIRE(var4.test(var::c));
}
}

TEST_CASE("enum_flag_wrapper", "[enum/enum_flag_wrapper]")
{
auto flags = 0_u8;
{
auto wrapper = based::enum_flag_wrapper<var>(flags);

wrapper |= var::a;
wrapper |= var::c;
}

REQUIRE(flags == 0x5_u8);
}

diff --git a/ test/source/enum_test.cpp b/ test/source/enum_test.cpp

@@ -1,87 +0,0 @@

#define CATCH_CONFIG_RUNTIME_STATIC_REQUIRE

#include "based/enum/enum.hpp"

#include <catch2/catch_test_macros.hpp>

#include "based/concepts/comparable/equality.hpp"
#include "based/concepts/comparable/greater.hpp"
#include "based/concepts/comparable/greater_equal.hpp"
#include "based/concepts/comparable/less.hpp"
#include "based/concepts/comparable/less_equal.hpp"
#include "based/concepts/is/invocable.hpp"
#include "based/concepts/is/same.hpp"
#include "based/types/literals.hpp"
#include "based/types/types.hpp"

using namespace based::literals; // NOLINT(*namespace*)

struct test
{
BASED_DECLARE_ENUM(var, based::u8, 0_u8, a, b, c)

[[nodiscard]] int get_var(var req) const;

private:
int m_a = 1;
int m_b = 2;
int m_c = 3;
};

BASED_DEFINE_ENUM_CLASS(test, var, based::u8, 0_u8, a, b, c)

inline int test::get_var(var req) const
{
switch (req()) {
case var::a():
return m_a;
case var::b():
return m_b;
case var::c():
return m_c;
default:
return -1;
}
}

TEST_CASE("types", "[enum/enum]")
{
STATIC_REQUIRE(requires { typename test::var; });
STATIC_REQUIRE(requires { test::var::a; });
STATIC_REQUIRE(requires { test::var::b; });
STATIC_REQUIRE(requires { test::var::c; });
STATIC_REQUIRE(test::var::size == 3_u8);
STATIC_REQUIRE(test::var::a() == 0);
STATIC_REQUIRE(test::var::b() == 1);
STATIC_REQUIRE(test::var::c() == 2);
}

TEST_CASE("safety", "[enum/enum]")
{
const test crnt;

REQUIRE(crnt.get_var(test::var::a) == 1);
REQUIRE(crnt.get_var(test::var::b) == 2);
REQUIRE(crnt.get_var(test::var::c) == 3);

REQUIRE(!based::Invocable<decltype(&test::get_var), based::u8>);
REQUIRE(!based::Invocable<decltype(&test::get_var), int>);
}

TEST_CASE("names", "[enum/enum]")
{
REQUIRE(std::strcmp(test::var::names[test::var::a], "a") == 0);
REQUIRE(std::strcmp(test::var::names[test::var::b], "b") == 0);
REQUIRE(std::strcmp(test::var::names[test::var::c], "c") == 0);
}

TEST_CASE("operations", "[enum/enum]")
{
using based::SameAs;

STATIC_REQUIRE(based::EqualityComparable<test::var>);
STATIC_REQUIRE(!based::LessComparable<test::var>);
STATIC_REQUIRE(!based::GreaterComparable<test::var>);
STATIC_REQUIRE(!based::LessEqualComparable<test::var>);
STATIC_REQUIRE(!based::GreaterEqualComparable<test::var>);
}