From 29ffdcbc330c8f9cd3ee73e44cf1442bd9d6e7a5 Mon Sep 17 00:00:00 2001 From: Benjamin Kaufmann Date: Wed, 15 Jan 2025 18:25:08 +0100 Subject: [PATCH] Use macros to define enum operations. * 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. --- potassco/basic_types.h | 16 +-- potassco/clingo.h | 2 +- potassco/enum.h | 190 +++++++++++++++------------------- tests/test_aspif.cpp | 2 +- tests/test_string_convert.cpp | 38 +++---- 5 files changed, 109 insertions(+), 139 deletions(-) diff --git a/potassco/basic_types.h b/potassco/basic_types.h index 0467688..06a5f82 100644 --- a/potassco/basic_types.h +++ b/potassco/basic_types.h @@ -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) { - 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) -> CmpOps; -[[maybe_unused]] consteval auto enable_meta(std::type_identity) { - 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 { diff --git a/potassco/clingo.h b/potassco/clingo.h index 32ee9b5..aa4a2c8 100644 --- a/potassco/clingo.h +++ b/potassco/clingo.h @@ -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) -> BitOps; +POTASSCO_ENABLE_BIT_OPS(ClauseType); //! Named constants. enum class StatisticsType { diff --git a/potassco/enum.h b/potassco/enum.h index da71a64..715a7da 100644 --- a/potassco/enum.h +++ b/potassco/enum.h @@ -90,15 +90,6 @@ struct EnumMeta))>> : std: static constexpr std::string_view name(EnumT e) { return c_meta.name(e); } }; -struct BitOps {}; -struct CmpOps {}; -template -struct AllOps : O... {}; -template -concept HasOps = requires(T) { - { enable_ops(c_type) } -> std::convertible_to; -}; - template struct FixedString { constexpr FixedString(std::string_view s) { @@ -143,6 +134,13 @@ constexpr void addEntry(std::pair*& out) { *out++ = {V, enum_name()}; } } +template +constexpr void addEntries(std::pair* out, EnumT e, std::string_view n, Args... args) { + *out++ = {e, n}; + if constexpr (sizeof...(args) != 0) { + addEntries(out, args...); + } +} // NOLINTEND } // namespace Detail @@ -150,9 +148,6 @@ constexpr void addEntry(std::pair*& out) { * \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. /*! @@ -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 - * auto enable_meta(std::type_identity) { return Potassco::EnumEntries{...}; } in namespace X. * \note Enumerators shall have unique numeric values. */ template @@ -210,13 +203,6 @@ struct EnumEntries { using container_type = std::array; // NOLINT using UT = std::underlying_type_t; // NOLINT static constexpr auto null_elem = element_type{}; - template - 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(), @@ -240,24 +226,39 @@ struct EnumEntries { return it != vals.end() && it->first == e ? &*it : &null_elem; } constexpr const container_type& entries() const noexcept { return vals; } - template - 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 -EnumEntries(EnumT e1, std::string_view n1, Args...) -> EnumEntries; +consteval auto makeEntries(EnumT e1, std::string_view n1, + Args... args) -> EnumEntries { + constexpr auto N = (sizeof...(Args) + 2) / 2; + std::pair r[N]; + Detail::addEntries(r, e1, n1, args...); + return EnumEntries(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 {, ""} pairs, where is an enumerator of E and "" its + * name. + */ +#define POTASSCO_SET_ENUM_ENTRIES(E, ...) \ + [[maybe_unused]] consteval auto enable_meta(std::type_identity) { \ + using enum E; \ + using namespace std::literals; \ + constexpr std::pair e[] = {__VA_ARGS__}; \ + return Potassco::EnumEntries(e); \ + } \ + static_assert(Potassco::HasEnumEntries) //! Returns valid enum entries of the given enum in the range [Min, Max]. template requires(std::is_enum_v && Max >= Min) -constexpr auto reflectEntries() { +consteval auto reflectEntries() { constexpr auto all = [](std::index_sequence) { std::array, sizeof...(Is)> r; auto* p = r.data(); @@ -267,6 +268,17 @@ constexpr auto reflectEntries() { return EnumEntries{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) { \ + return Potassco::reflectEntries(); \ + } \ + static_assert(Potassco::HasEnumEntries) + //! Concept for scoped enums. template concept ScopedEnum = std::is_enum_v && not std::is_convertible_v>; @@ -279,14 +291,6 @@ concept HasEnumMeta = std::is_enum_v && Detail::EnumMeta::value; template concept HasEnumEntries = HasEnumMeta && Detail::EnumMeta::meta_entries; -//! Concept for enums with support for heterogeneous comparison operators. -template -concept HasCmpOps = ScopedEnum && Detail::HasOps; - -//! Concept for enums with support for bit operations. -template -concept HasBitOps = std::is_enum_v && Detail::HasOps; - //! Returns the elements of the given enum as an array of (name, "name")-pairs. /*! * \tparam EnumT An enum type with full metadata. @@ -356,79 +360,51 @@ requires(std::is_enum_v) 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 - * consteval auto enable_ops(std::type_identity) -> P 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 using namespace Potassco::Ops 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 enable_ops(std::type_identity) 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 -[[nodiscard]] constexpr auto operator==(T lhs, std::underlying_type_t rhs) noexcept -> bool { - return Potassco::to_underlying(lhs) == rhs; -} -template -[[nodiscard]] constexpr decltype(auto) operator<=>(T lhs, std::underlying_type_t rhs) noexcept { - return Potassco::to_underlying(lhs) <=> rhs; -} -template -[[nodiscard]] constexpr auto operator+(T v) noexcept -> std::underlying_type_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 rhs), __VA_ARGS__)->bool { \ + return Potassco::to_underlying(lhs) == rhs; \ + } \ + [[nodiscard]] POTASSCO_E_OP(<=>, (E lhs, std::underlying_type_t rhs), __VA_ARGS__) { \ + return Potassco::to_underlying(lhs) <=> rhs; \ + } \ + [[nodiscard]] POTASSCO_E_OP(+, (E v), __VA_ARGS__)->std::underlying_type_t { \ + return Potassco::to_underlying(v); \ + } \ + static_assert(Potassco::ScopedEnum) + +//! Opt-in macro for enabling bit operations for a given enum type. /*! - * \note Scoped enums E for which enable_ops(std::type_identity) 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 -[[nodiscard]] POTASSCO_FORCE_INLINE constexpr auto operator~(T a) noexcept -> T { - return static_cast(~Potassco::to_underlying(a)); -} -template -[[nodiscard]] POTASSCO_FORCE_INLINE constexpr auto operator|(T a, T b) noexcept -> T { - return static_cast(Potassco::to_underlying(a) | Potassco::to_underlying(b)); -} -template -POTASSCO_FORCE_INLINE constexpr auto operator|=(T& a, T b) noexcept -> T& { - return a = a | b; -} -template -[[nodiscard]] POTASSCO_FORCE_INLINE constexpr auto operator&(T a, T b) noexcept -> T { - return static_cast(Potassco::to_underlying(a) & Potassco::to_underlying(b)); -} -template -POTASSCO_FORCE_INLINE constexpr auto operator&=(T& a, T b) noexcept -> T& { - return a = a & b; -} -template -[[nodiscard]] POTASSCO_FORCE_INLINE constexpr auto operator^(T a, T b) noexcept -> T { - return static_cast(Potassco::to_underlying(a) ^ Potassco::to_underlying(b)); -} -template -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(~Potassco::to_underlying(a)); } \ + [[nodiscard]] POTASSCO_E_OP(|, (E a, E b), __VA_ARGS__)->E { \ + return static_cast(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(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(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) -} // 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+; diff --git a/tests/test_aspif.cpp b/tests/test_aspif.cpp index 6f68a89..d8e23c4 100644 --- a/tests/test_aspif.cpp +++ b/tests/test_aspif.cpp @@ -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) -> CmpOps { return {}; } +POTASSCO_ENABLE_CMP_OPS(DummyEnum); } // namespace diff --git a/tests/test_string_convert.cpp b/tests/test_string_convert.cpp index 7d7c642..8c18034 100644 --- a/tests/test_string_convert.cpp +++ b/tests/test_string_convert.cpp @@ -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) { return Potassco::reflectEntries(); } +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() == 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; + using A = std::array; using enum Foo; - REQUIRE(Potassco::enum_entries() == 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)}; + const A& got = Potassco::enum_entries(); + REQUIRE(got == expected); REQUIRE(Potassco::enum_min() == 0); REQUIRE(Potassco::enum_max() == 8); REQUIRE_FALSE(Potassco::enum_cast(4).has_value()); REQUIRE_FALSE(Potassco::enum_cast(5).has_value()); REQUIRE_FALSE(Potassco::enum_cast(6).has_value()); - REQUIRE(Potassco::enum_cast(7) == Foo::Value5); + REQUIRE(Potassco::enum_cast(7) == Foo::value5); enum NoMeta : uint8_t {}; REQUIRE(Potassco::enum_min() == 0u); REQUIRE(Potassco::enum_max() == 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("Value3") == Foo::Value3); - REQUIRE(string_cast("7") == Foo::Value5); - REQUIRE(string_cast("Value4") == Foo::Value4); - REQUIRE(string_cast("vAlUe4") == Foo::Value4); - REQUIRE(string_cast("8") == Foo::Value6); + REQUIRE(string_cast("Value3") == Foo::value3); + REQUIRE(string_cast("7") == Foo::value5); + REQUIRE(string_cast("Value4") == Foo::value4); + REQUIRE(string_cast("vAlUe4") == Foo::value4); + REQUIRE(string_cast("8") == Foo::value6); REQUIRE_FALSE(string_cast("9").has_value()); REQUIRE_FALSE(string_cast("Value98").has_value()); REQUIRE_FALSE(string_cast("Value").has_value());