Skip to content

Commit

Permalink
Use macros to define enum operations.
Browse files Browse the repository at this point in the history
* Implement comparison and bitwise operators for enums in terms of
  macros POTASSCO_ENABLE_{CMP,BIT}_OPS instead of templates. This way,
  operators are added to the namespace of their associated enum and
  are thus found via ADL.

* Add convenience macros for setting and reflecting enum entries.
  • Loading branch information
BenKaufmann committed Jan 15, 2025
1 parent 483775c commit d3f8249
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 139 deletions.
16 changes: 4 additions & 12 deletions potassco/basic_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,21 +119,13 @@ POTASSCO_SET_DEFAULT_ENUM_MAX(BodyType::count);

//! Type representing a truth or external value.
enum class TruthValue : unsigned { free = 0, true_ = 1, false_ = 2, release = 3 };
[[maybe_unused]] consteval auto enable_meta(std::type_identity<TruthValue>) {
using enum TruthValue;
using namespace std::literals;
return EnumEntries(free, "free"sv, true_, "true"sv, false_, "false"sv, release, "release"sv);
}
POTASSCO_SET_ENUM_ENTRIES(TruthValue, {free, "free"sv}, {true_, "true"sv}, {false_, "false"sv}, {release, "release"sv});

//! Supported modifications for domain heuristic.
enum class DomModifier : unsigned { level = 0, sign = 1, factor = 2, init = 3, true_ = 4, false_ = 5 };
[[maybe_unused]] consteval auto enable_ops(std::type_identity<DomModifier>) -> CmpOps;
[[maybe_unused]] consteval auto enable_meta(std::type_identity<DomModifier>) {
using enum DomModifier;
using namespace std::literals;
return EnumEntries(level, "level"sv, sign, "sign"sv, factor, "factor"sv, init, "init"sv, true_, "true"sv, false_,
"false"sv);
}
POTASSCO_SET_ENUM_ENTRIES(DomModifier, {level, "level"sv}, {sign, "sign"sv}, {factor, "factor"sv}, {init, "init"sv},
{true_, "true"sv}, {false_, "false"sv});
POTASSCO_ENABLE_CMP_OPS(DomModifier);

//! Supported aspif statements.
enum class AspifType : unsigned {
Expand Down
2 changes: 1 addition & 1 deletion potassco/clingo.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ enum class ClauseType : unsigned {
transient = 2u, //!< Removable clause associated with current solving step.
transient_locked = 3u //!< Unremovable clause associated with current solving step.
};
[[maybe_unused]] consteval auto enable_ops(std::type_identity<ClauseType>) -> BitOps;
POTASSCO_ENABLE_BIT_OPS(ClauseType);

//! Named constants.
enum class StatisticsType {
Expand Down
188 changes: 81 additions & 107 deletions potassco/enum.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,6 @@ struct EnumMeta<EnumT, std::void_t<decltype(enable_meta(c_type<EnumT>))>> : std:
static constexpr std::string_view name(EnumT e) { return c_meta.name(e); }
};

struct BitOps {};
struct CmpOps {};
template <typename... O>
struct AllOps : O... {};
template <typename T, typename Op>
concept HasOps = requires(T) {
{ enable_ops(c_type<T>) } -> std::convertible_to<Op>;
};

template <std::size_t N>
struct FixedString {
constexpr FixedString(std::string_view s) {
Expand Down Expand Up @@ -143,16 +134,20 @@ constexpr void addEntry(std::pair<EnumT, std::string_view>*& out) {
*out++ = {V, enum_name<EnumT, V>()};
}
}
template <typename EnumT, typename... Args>
constexpr void addEntries(std::pair<EnumT, std::string_view>* out, EnumT e, std::string_view n, Args... args) {
*out++ = {e, n};
if constexpr (sizeof...(args) != 0) {
addEntries(out, args...);
}
}
// NOLINTEND
} // namespace Detail

/*!
* \addtogroup BasicTypes
*/
///@{
using Detail::AllOps;
using Detail::BitOps;
using Detail::CmpOps;

//! Meta type for simple (consecutive) enums with @c Count elements starting at @c First.
/*!
Expand Down Expand Up @@ -200,8 +195,6 @@ struct DefaultEnum {

//! Meta type for enums with @c N explicit elements, where each element is an enumerator and its (stringified) name.
/*!
* To associate entry metadata for some enum Foo defined in namespace X, define a consteval function
* <tt>auto enable_meta(std::type_identity<Foo>) { return Potassco::EnumEntries{...}; }</tt> in namespace X.
* \note Enumerators shall have unique numeric values.
*/
template <typename EnumT, std::size_t N>
Expand All @@ -210,13 +203,6 @@ struct EnumEntries {
using container_type = std::array<element_type, N>; // NOLINT
using UT = std::underlying_type_t<EnumT>; // NOLINT
static constexpr auto null_elem = element_type{};
template <typename... Args>
explicit constexpr EnumEntries(Args... args) {
static_assert(N != 0 && sizeof...(Args) == N * 2);
add(vals.data(), args...);
std::sort(vals.begin(), vals.end(),
[](const auto& lhs, const auto& rhs) { return to_underlying(lhs.first) < to_underlying(rhs.first); });
}
explicit constexpr EnumEntries(const element_type* base) {
for (std::size_t i = 0; i != N; ++i) { vals[i] = base[i]; }
std::sort(vals.begin(), vals.end(),
Expand All @@ -240,24 +226,38 @@ struct EnumEntries {
return it != vals.end() && it->first == e ? &*it : &null_elem;
}
constexpr const container_type& entries() const noexcept { return vals; }
template <typename... Args>
constexpr void add(element_type* out, EnumT e1, std::string_view s1, Args... rest) {
*out++ = element_type{e1, s1};
if constexpr (sizeof...(rest)) {
add(out, rest...);
}
}

container_type vals{};
};

//! Returns enum entries from the given arguments.
template <typename EnumT, typename... Args>
EnumEntries(EnumT e1, std::string_view n1, Args...) -> EnumEntries<EnumT, (sizeof...(Args) + 2) / 2>;
consteval auto makeEntries(EnumT e1, std::string_view n1,
Args... args) -> EnumEntries<EnumT, (sizeof...(Args) + 2) / 2> {
constexpr auto N = (sizeof...(Args) + 2) / 2;
std::pair<EnumT, std::string_view> r[N];
Detail::addEntries(r, e1, n1, args...);
return EnumEntries<EnumT, N>(r);
}

//! Convenience macro for specifying metadata for enums with explicitly named entries.
/*!
* \param E The enum type for which metadata is specified.
* \param ... List of entries given as {<dcl>, "<name>"} pairs, where <dcl> is an enumerator of E and "<name>" its
* name.
*/
#define POTASSCO_SET_ENUM_ENTRIES(E, ...) \
[[maybe_unused]] consteval auto enable_meta(std::type_identity<E>) { \
using enum E; \
using namespace std::literals; \
constexpr std::pair<E, std::string_view> e[] = {__VA_ARGS__}; \
return Potassco::EnumEntries<E, std::size(e)>(e); \
}

//! Returns valid enum entries of the given enum in the range [Min, Max].
template <typename EnumT, auto Min = 0, auto Max = 128>
requires(std::is_enum_v<EnumT> && Max >= Min)
constexpr auto reflectEntries() {
consteval auto reflectEntries() {
constexpr auto all = []<auto... Is>(std::index_sequence<Is...>) {
std::array<std::pair<EnumT, std::string_view>, sizeof...(Is)> r;
auto* p = r.data();
Expand All @@ -267,6 +267,16 @@ constexpr auto reflectEntries() {
return EnumEntries<EnumT, all.second>{all.first.data()};
}

//! Convenience macro for specifying metadata for enums with reflected names.
/*!
* \param E The enum type for which metadata is specified.
* \param ... Optional numeric reflection range (default is [0;128])
*/
#define POTASSCO_REFLECT_ENUM_ENTRIES(E, ...) \
[[maybe_unused]] consteval auto enable_meta(std::type_identity<E>) { \
return Potassco::reflectEntries<E, __VA_ARGS__>(); \
}

//! Concept for scoped enums.
template <typename EnumT>
concept ScopedEnum = std::is_enum_v<EnumT> && not std::is_convertible_v<EnumT, std::underlying_type_t<EnumT>>;
Expand All @@ -279,14 +289,6 @@ concept HasEnumMeta = std::is_enum_v<EnumT> && Detail::EnumMeta<EnumT>::value;
template <typename EnumT>
concept HasEnumEntries = HasEnumMeta<EnumT> && Detail::EnumMeta<EnumT>::meta_entries;

//! Concept for enums with support for heterogeneous comparison operators.
template <typename T>
concept HasCmpOps = ScopedEnum<T> && Detail::HasOps<T, CmpOps>;

//! Concept for enums with support for bit operations.
template <typename T>
concept HasBitOps = std::is_enum_v<T> && Detail::HasOps<T, BitOps>;

//! Returns the elements of the given enum as an array of (name, "name")-pairs.
/*!
* \tparam EnumT An enum type with full metadata.
Expand Down Expand Up @@ -356,79 +358,51 @@ requires(std::is_enum_v<T>)
return (to_underlying(x) & to_underlying(y)) == to_underlying(y);
}
///@}
//! Opt-in operators for scoped enums.
/*!
* To enable certain opt-in operators for some scoped enum Foo defined in namespace X, declare a (consteval) function
* <tt>consteval auto enable_ops(std::type_identity<Foo>) -> P</tt> in namespace X, where P is a type convertible to
* Potassco::CmpOps, Potassco::BitOps, or both.
* If namespace X is different from Potassco, then also add <tt>using namespace Potassco::Ops</tt> to namespace X.
*/
inline namespace Ops {
//! Opt-in heterogeneous comparison operators for scoped enums.

//! Opt-in macro for enabling heterogeneous comparison operators for a given scoped enum type.
/*!
* \note Scoped enums E for which <tt>enable_ops(std::type_identity<E>)</tt> returns a type convertible to
* Potassco::CmpOps can be implicitly compared to their underlying type.
* Furthermore, unary operator+ can be used to convert members of E to their underlying numeric value.
* A scoped enum E for which POTASSCO_ENABLE_CMP_OPS(E) has been called can be implicitly compared to its underlying
* type. Furthermore, the unary operator+ can be used to convert members of E to their underlying numeric value.
*
* \note If E is a class-local enum, POTASSCO_ENABLE_CMP_OPS(E, friend) can be used to enable comparison operators
* from within the class definition.
*/
///@{
template <HasCmpOps T>
[[nodiscard]] constexpr auto operator==(T lhs, std::underlying_type_t<T> rhs) noexcept -> bool {
return Potassco::to_underlying(lhs) == rhs;
}
template <HasCmpOps T>
[[nodiscard]] constexpr decltype(auto) operator<=>(T lhs, std::underlying_type_t<T> rhs) noexcept {
return Potassco::to_underlying(lhs) <=> rhs;
}
template <HasCmpOps T>
[[nodiscard]] constexpr auto operator+(T v) noexcept -> std::underlying_type_t<T> {
return Potassco::to_underlying(v);
}
//! Opt-in bit operations for enums.
#define POTASSCO_ENABLE_CMP_OPS(E, ...) \
[[nodiscard]] POTASSCO_E_OP(==, (E lhs, std::underlying_type_t<E> rhs), __VA_ARGS__)->bool { \
return Potassco::to_underlying(lhs) == rhs; \
} \
[[nodiscard]] POTASSCO_E_OP(<=>, (E lhs, std::underlying_type_t<E> rhs), __VA_ARGS__) { \
return Potassco::to_underlying(lhs) <=> rhs; \
} \
[[nodiscard]] POTASSCO_E_OP(+, (E v), __VA_ARGS__)->std::underlying_type_t<E> { \
return Potassco::to_underlying(v); \
} \
static_assert(Potassco::ScopedEnum<E>)

//! Opt-in macro for enabling bit operations for a given enum type.
/*!
* \note Scoped enums E for which <tt>enable_ops(std::type_identity<E>)</tt> returns a type convertible to
* Potassco::BitOps support bitwise operators on their underlying type.
* Use POTASSCO_ENABLE_BIT_OPS(E) to enable bitwise operators on the underlying type of enum E.
*
* \note If E is a class-local enum, POTASSCO_ENABLE_BIT_OPS(E, friend) can be used to enable bitwise operators from
* within the class definition.
*/
///@{
template <HasBitOps T>
[[nodiscard]] POTASSCO_FORCE_INLINE constexpr auto operator~(T a) noexcept -> T {
return static_cast<T>(~Potassco::to_underlying(a));
}
template <HasBitOps T>
[[nodiscard]] POTASSCO_FORCE_INLINE constexpr auto operator|(T a, T b) noexcept -> T {
return static_cast<T>(Potassco::to_underlying(a) | Potassco::to_underlying(b));
}
template <HasBitOps T>
POTASSCO_FORCE_INLINE constexpr auto operator|=(T& a, T b) noexcept -> T& {
return a = a | b;
}
template <HasBitOps T>
[[nodiscard]] POTASSCO_FORCE_INLINE constexpr auto operator&(T a, T b) noexcept -> T {
return static_cast<T>(Potassco::to_underlying(a) & Potassco::to_underlying(b));
}
template <HasBitOps T>
POTASSCO_FORCE_INLINE constexpr auto operator&=(T& a, T b) noexcept -> T& {
return a = a & b;
}
template <HasBitOps T>
[[nodiscard]] POTASSCO_FORCE_INLINE constexpr auto operator^(T a, T b) noexcept -> T {
return static_cast<T>(Potassco::to_underlying(a) ^ Potassco::to_underlying(b));
}
template <HasBitOps T>
POTASSCO_FORCE_INLINE constexpr auto operator^=(T& a, T b) noexcept -> T& {
return a = a ^ b;
}
///@}
#define POTASSCO_ENABLE_BIT_OPS(E, ...) \
[[nodiscard]] POTASSCO_E_OP(~, (E a), __VA_ARGS__)->E { return static_cast<E>(~Potassco::to_underlying(a)); } \
[[nodiscard]] POTASSCO_E_OP(|, (E a, E b), __VA_ARGS__)->E { \
return static_cast<E>(Potassco::to_underlying(a) | Potassco::to_underlying(b)); \
} \
POTASSCO_E_OP(|=, (E & a, E b), __VA_ARGS__)->E& { return a = a | b; } \
[[nodiscard]] POTASSCO_E_OP(&, (E a, E b), __VA_ARGS__)->E { \
return static_cast<E>(Potassco::to_underlying(a) & Potassco::to_underlying(b)); \
} \
POTASSCO_E_OP(&=, (E & a, E b), __VA_ARGS__)->E& { return a = a & b; } \
[[nodiscard]] POTASSCO_E_OP(^, (E a, E b), __VA_ARGS__)->E { \
return static_cast<E>(Potassco::to_underlying(a) ^ Potassco::to_underlying(b)); \
} \
POTASSCO_E_OP(^=, (E & a, E b), __VA_ARGS__)->E& { return a = a ^ b; } \
static_assert(std::is_enum_v<E>)

} // namespace Ops
#define POTASSCO_E_OP(op, arg, ...) \
[[maybe_unused]] POTASSCO_FORCE_INLINE __VA_ARGS__ constexpr auto operator op arg noexcept

} // namespace Potassco
using Potassco::Ops::operator&;
using Potassco::Ops::operator|;
using Potassco::Ops::operator^;
using Potassco::Ops::operator~;
using Potassco::Ops::operator&=;
using Potassco::Ops::operator|=;
using Potassco::Ops::operator^=;
using Potassco::Ops::operator==;
using Potassco::Ops::operator<=>;
using Potassco::Ops::operator+;
2 changes: 1 addition & 1 deletion tests/test_aspif.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ enum class DummyEnum : uint8_t {
eight = 8,
};
POTASSCO_SET_DEFAULT_ENUM_MAX(DummyEnum::eight);
[[maybe_unused]] consteval auto enable_ops(std::type_identity<DummyEnum>) -> CmpOps { return {}; }
POTASSCO_ENABLE_CMP_OPS(DummyEnum);

} // namespace

Expand Down
38 changes: 20 additions & 18 deletions tests/test_string_convert.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -295,48 +295,50 @@ TEST_CASE("String conversion", "[string]") {
}
}

enum class Foo : unsigned { Value1 = 0, Value2 = 1, Value3 = 2, Value4, Value5 = 7, Value6 = 7 + 1 };
[[maybe_unused]] consteval auto enable_meta(std::type_identity<Foo>) { return Potassco::reflectEntries<Foo, 0u, 8u>(); }
enum class Foo : unsigned { value1 = 0, value2 = 1, value3 = 2, value4, value5 = 7, value6 = 7 + 1 };
POTASSCO_REFLECT_ENUM_ENTRIES(Foo, 0u, 8u);

using namespace std::literals;
static_assert(Potassco::enum_count<Foo>() == 6, "Wrong count");
static_assert(Potassco::enum_name(Foo::Value3) == "Value3"sv, "Wrong name");
static_assert(Potassco::enum_name(Foo::value3) == "value3"sv, "Wrong name");
TEST_CASE("Enum entries", "[enum]") {
using P = std::pair<Foo, std::string_view>;
using A = std::array<P, 6>;
using enum Foo;

REQUIRE(Potassco::enum_entries<Foo>() == std::array{P(Value1, "Value1"sv), P(Value2, "Value2"sv),
P(Value3, "Value3"sv), P(Value4, "Value4"sv),
P(Value5, "Value5"sv), P(Value6, "Value6"sv)});
auto expected = std::array{P(value1, "value1"sv), P(value2, "value2"sv), P(value3, "value3"sv),
P(value4, "value4"sv), P(value5, "value5"sv), P(value6, "value6"sv)};
A got = Potassco::enum_entries<Foo>();
REQUIRE(got == expected);

REQUIRE(Potassco::enum_min<Foo>() == 0);
REQUIRE(Potassco::enum_max<Foo>() == 8);
REQUIRE_FALSE(Potassco::enum_cast<Foo>(4).has_value());
REQUIRE_FALSE(Potassco::enum_cast<Foo>(5).has_value());
REQUIRE_FALSE(Potassco::enum_cast<Foo>(6).has_value());
REQUIRE(Potassco::enum_cast<Foo>(7) == Foo::Value5);
REQUIRE(Potassco::enum_cast<Foo>(7) == Foo::value5);
enum NoMeta : uint8_t {};
REQUIRE(Potassco::enum_min<NoMeta>() == 0u);
REQUIRE(Potassco::enum_max<NoMeta>() == 255u);
}

TEST_CASE("Enum to string", "[enum]") {
REQUIRE(toString(Foo::Value1) == "Value1");
REQUIRE(toString(Foo::Value2) == "Value2");
REQUIRE(toString(Foo::Value3) == "Value3");
REQUIRE(toString(Foo::Value4) == "Value4");
REQUIRE(toString(Foo::Value5) == "Value5");
REQUIRE(toString(Foo::Value6) == "Value6");
REQUIRE(toString(Foo::value1) == "value1");
REQUIRE(toString(Foo::value2) == "value2");
REQUIRE(toString(Foo::value3) == "value3");
REQUIRE(toString(Foo::value4) == "value4");
REQUIRE(toString(Foo::value5) == "value5");
REQUIRE(toString(Foo::value6) == "value6");
Foo unknown{12};
REQUIRE(toString(unknown) == "12");
}

TEST_CASE("Enum from string", "[enum]") {
REQUIRE(string_cast<Foo>("Value3") == Foo::Value3);
REQUIRE(string_cast<Foo>("7") == Foo::Value5);
REQUIRE(string_cast<Foo>("Value4") == Foo::Value4);
REQUIRE(string_cast<Foo>("vAlUe4") == Foo::Value4);
REQUIRE(string_cast<Foo>("8") == Foo::Value6);
REQUIRE(string_cast<Foo>("Value3") == Foo::value3);
REQUIRE(string_cast<Foo>("7") == Foo::value5);
REQUIRE(string_cast<Foo>("Value4") == Foo::value4);
REQUIRE(string_cast<Foo>("vAlUe4") == Foo::value4);
REQUIRE(string_cast<Foo>("8") == Foo::value6);
REQUIRE_FALSE(string_cast<Foo>("9").has_value());
REQUIRE_FALSE(string_cast<Foo>("Value98").has_value());
REQUIRE_FALSE(string_cast<Foo>("Value").has_value());
Expand Down

0 comments on commit d3f8249

Please sign in to comment.