diff --git a/include/iris/colorize_format.hpp b/include/iris/colorize_format.hpp index 55ca870..e9d033d 100644 --- a/include/iris/colorize_format.hpp +++ b/include/iris/colorize_format.hpp @@ -6,8 +6,7 @@ #include // IWYU pragma: keep #include -#include -#include +#include #include #include @@ -474,10 +473,13 @@ static constexpr std::uint8_t emphasis_to_value(emphasis em) } // ansi_colorize template<> -struct bitops_enabled : std::true_type { +struct enum_traits +{ static constexpr int max_bit = 7; }; +static_assert(Enum); + namespace ansi_colorize { struct colorize_style @@ -672,7 +674,7 @@ struct colorizer style.fg_color = color; } else if (auto emphasis = detail::name_to_emphasis(specifier); emphasis != detail::emphasis{}) { - using namespace bitops_operators; + using namespace enum_bitops_operators; style.emphasis |= emphasis; } else if (specifier.starts_with("fg:")) { diff --git a/include/iris/enum/enum.hpp b/include/iris/enum/enum.hpp new file mode 100644 index 0000000..60dd449 --- /dev/null +++ b/include/iris/enum/enum.hpp @@ -0,0 +1,269 @@ +#ifndef IRIS_ZZ_ENUM_ENUM_HPP +#define IRIS_ZZ_ENUM_ENUM_HPP + +// SPDX-License-Identifier: MIT + +#include // IWYU pragma: keep + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef NDEBUG +#include +#endif + +#include +#include // IWYU pragma: keep + +namespace iris { + +namespace detail { + +struct enum_traits_not_defined {}; + +} // detail + +template +struct enum_traits : detail::enum_traits_not_defined +{}; + +template +concept Enum = std::is_enum_v && requires { + typename enum_traits; + requires !std::derived_from, detail::enum_traits_not_defined>; +}; + +namespace detail { + +template +concept enum_has_finite_minmax_element = requires { + requires std::same_as::min_element)>, EnumT>; + requires std::same_as::max_element)>, EnumT>; + requires std::to_underlying(enum_traits::min_element) <= std::to_underlying(enum_traits::max_element); +}; + +template +concept enum_has_min_bit = requires { + requires std::same_as::min_bit)>, int>; +}; +template +concept enum_has_proper_min_bit = !requires { enum_traits::min_bit; } || enum_has_min_bit; + +template +constexpr int enum_min_bit_v = 0; + +template + requires enum_has_min_bit +constexpr int enum_min_bit_v = enum_traits::min_bit; + + +template +concept enum_has_max_bit = requires { + requires std::same_as::max_bit)>, int>; +}; +template +concept enum_has_proper_max_bit = !requires { enum_traits::max_bit; } || enum_has_max_bit; + +template +concept enum_has_finite_minmax_bit = + std::unsigned_integral> && + enum_has_proper_min_bit && + enum_has_proper_max_bit && + requires { + requires enum_min_bit_v <= enum_traits::max_bit; + }; + +template +struct enum_count_impl; + +template + requires detail::enum_has_finite_minmax_bit +struct enum_count_impl + : std::integral_constant::max_bit + 1 - detail::enum_min_bit_v> +{}; + +template + requires detail::enum_has_finite_minmax_element && (!detail::enum_has_finite_minmax_bit) +struct enum_count_impl + : std::integral_constant< + std::size_t, + static_cast(enum_traits::max_element) + 1uz - + static_cast(enum_traits::min_element) + > +{}; + +} // detail + + +template +concept FiniteEnum = Enum && (detail::enum_has_finite_minmax_bit || detail::enum_has_finite_minmax_element); + +template +constexpr std::size_t enum_count_v = detail::enum_count_impl::value; + +template +concept BitopsEnabledEnum = + Enum && + std::unsigned_integral> && + detail::enum_has_proper_min_bit && + detail::enum_has_proper_max_bit; + + +template +[[nodiscard]] consteval auto enum_values() noexcept +{ + static_assert(FiniteEnum); + + if constexpr (detail::enum_has_finite_minmax_bit) { + return [](std::integer_sequence) noexcept { + return std::array>{ + static_cast(1u << (detail::enum_min_bit_v + Offsets))... + }; + }(std::make_integer_sequence(enum_count_v)>{}); + + } else { + constexpr auto first_val = std::to_underlying(enum_traits::min_element); + return [](std::integer_sequence) noexcept { + return std::array>{ + static_cast(first_val + Offsets)... + }; + }(std::make_integer_sequence, enum_count_v>{}); + } +} + + +inline namespace enum_bitops_operators { + +template +[[nodiscard]] constexpr EnumT operator~(EnumT a) noexcept +{ + return static_cast(~std::to_underlying(a)); +} + +template +[[nodiscard]] constexpr EnumT operator|(EnumT a, EnumT b) noexcept +{ + static_assert(std::is_unsigned_v>); + return static_cast(std::to_underlying(a) | std::to_underlying(b)); +} + +template +constexpr EnumT& operator|=(EnumT& a, EnumT b) noexcept +{ + static_assert(std::is_unsigned_v>); + return a = a | b; +} + +template +[[nodiscard]] constexpr EnumT operator&(EnumT a, EnumT b) noexcept +{ + static_assert(std::is_unsigned_v>); + return static_cast(std::to_underlying(a) & std::to_underlying(b)); +} + +template +constexpr EnumT& operator&=(EnumT& a, EnumT b) noexcept +{ + static_assert(std::is_unsigned_v>); + return a = a & b; +} + +template +[[nodiscard]] constexpr EnumT operator^(EnumT a, EnumT b) noexcept +{ + static_assert(std::is_unsigned_v>); + return static_cast(std::to_underlying(a) ^ std::to_underlying(b)); +} + +template +constexpr EnumT& operator^=(EnumT& a, EnumT b) noexcept +{ + static_assert(std::is_unsigned_v>); + return a = a ^ b; +} + +} // enum_bitops_operators + +template +[[nodiscard]] constexpr bool contains(EnumT a, EnumT b) noexcept +{ + static_assert(std::is_unsigned_v>); + return (std::to_underlying(a) & std::to_underlying(b)) == std::to_underlying(b); +} + +template +[[nodiscard]] constexpr bool contains_single_bit(EnumT a, EnumT b) noexcept +{ + static_assert(std::is_unsigned_v>); + assert(std::has_single_bit(std::to_underlying(b))); + return std::to_underlying(a) & std::to_underlying(b); +} + +template +[[nodiscard]] constexpr bool contains_any_bit(EnumT a, EnumT b) noexcept +{ + static_assert(std::is_unsigned_v>); + return std::to_underlying(a) & std::to_underlying(b); +} + + +namespace detail { + +struct each_bit_fn +{ + template + requires + BitopsEnabledEnum && + requires { + requires enum_traits::max_bit < std::numeric_limits>::digits; + } + [[nodiscard]] static constexpr auto operator()(EnumT flags) noexcept + { + return std::views::iota(enum_min_bit_v, enum_traits::max_bit + 1) + | std::views::filter([cat = std::to_underlying(flags)](int i) constexpr noexcept -> bool { return (cat >> i) & 1; }) + | std::views::transform([](int i) constexpr noexcept { return static_cast(static_cast>(1u) << i); }); + } +}; + +} // detail + +inline constexpr detail::each_bit_fn each_bit{}; + +} // iris + +template + requires requires(EnumT const& val) { + { to_string(val) } -> std::formattable; + } +struct std::formatter : std::formatter, CharT> +{ + using base_formatter = std::formatter, CharT>; + + template + constexpr auto parse(Context& ctx) + { + if (ctx.begin() == ctx.end()) return ctx.end(); + has_format_spec_ = true; + return base_formatter::parse(ctx); + } + + template + auto format(EnumT const& val, Context& ctx) const + { + if (has_format_spec_) { + return base_formatter::format(std::to_underlying(val), ctx); + } + return format_to(ctx.out(), "{}", to_string(val)); + } + +private: + bool has_format_spec_ = false; +}; + +#endif diff --git a/include/iris/enum/io.hpp b/include/iris/enum/io.hpp new file mode 100644 index 0000000..2f113d8 --- /dev/null +++ b/include/iris/enum/io.hpp @@ -0,0 +1,68 @@ +#ifndef IRIS_ZZ_ENUM_IO_HPP +#define IRIS_ZZ_ENUM_IO_HPP + +// SPDX-License-Identifier: MIT + +#include // IWYU pragma: keep + +#include +#include + +#include // IWYU pragma: keep +#include +#include +#include +#include + +namespace iris { + +inline namespace enum_operators { + +template +std::ostream& operator<<(std::ostream& os, T const& val) +{ + using std::to_string; + + if constexpr (requires { + { os << to_string(val) } -> std::convertible_to; + }) { + return os << to_string(val); + } else { + return os << std::to_underlying(val); + } +} + +} // enum_operators + +template +[[nodiscard]] constexpr T parse_flag(StringLike auto const& str) noexcept +{ + return enum_traits::parse(std::basic_string_view{str}); +} + +template +[[nodiscard]] constexpr T parse_flags(StringLike auto const& str, StringLike auto const& delim) noexcept +{ + using namespace iris::enum_bitops_operators; + + std::basic_string_view str_sv{str}; + std::basic_string_view delim_sv{delim}; + + T res{}; + + for (auto const& r : str_sv | std::views::split(delim_sv)) { + std::basic_string_view const part_str{r.begin(), r.end()}; + + auto const part = iris::parse_flag(part_str); + if (part == T{}) { + return T{}; + } + res |= part; + } + + return res; +} + +} // iris + +#endif diff --git a/include/iris/enum_bitops.hpp b/include/iris/enum_bitops.hpp deleted file mode 100644 index 4b01d11..0000000 --- a/include/iris/enum_bitops.hpp +++ /dev/null @@ -1,115 +0,0 @@ -#ifndef IRIS_ZZ_ENUM_BITOPS_HPP -#define IRIS_ZZ_ENUM_BITOPS_HPP - -// SPDX-License-Identifier: MIT - -#include // IWYU pragma: keep - -#include -#include -#include - -#ifndef NDEBUG -#include -#endif - -#include - -namespace iris { - -template -struct bitops_enabled : std::false_type {}; - -template -constexpr bool bitops_enabled_v = bitops_enabled::value; - -namespace detail { - -template -concept bitops_enum_has_min_bit = std::same_as::min_bit)>, int>; - -template -concept bitops_enum_has_max_bit = std::same_as::max_bit)>, int>; - -} // detail - -template -concept BitopsEnabledEnum = std::is_enum_v && bitops_enabled_v; - -inline namespace bitops_operators { - -template -[[nodiscard]] constexpr T operator~(T a) noexcept -{ - return static_cast(~std::to_underlying(a)); -} - -template -[[nodiscard]] constexpr T operator|(T a, T b) noexcept -{ - static_assert(std::is_unsigned_v>); - return static_cast(std::to_underlying(a) | std::to_underlying(b)); -} - -template -constexpr T& operator|=(T& a, T b) noexcept -{ - static_assert(std::is_unsigned_v>); - return a = a | b; -} - -template -[[nodiscard]] constexpr T operator&(T a, T b) noexcept -{ - static_assert(std::is_unsigned_v>); - return static_cast(std::to_underlying(a) & std::to_underlying(b)); -} - -template -constexpr T& operator&=(T& a, T b) noexcept -{ - static_assert(std::is_unsigned_v>); - return a = a & b; -} - -template -[[nodiscard]] constexpr T operator^(T a, T b) noexcept -{ - static_assert(std::is_unsigned_v>); - return static_cast(std::to_underlying(a) ^ std::to_underlying(b)); -} - -template -constexpr T& operator^=(T& a, T b) noexcept -{ - static_assert(std::is_unsigned_v>); - return a = a ^ b; -} - -} // bitops_operators - -template -[[nodiscard]] constexpr bool contains(T a, T b) noexcept -{ - static_assert(std::is_unsigned_v>); - return (std::to_underlying(a) & std::to_underlying(b)) == std::to_underlying(b); -} - -template -[[nodiscard]] constexpr bool contains_single_bit(T a, T b) noexcept -{ - static_assert(std::is_unsigned_v>); - assert(std::has_single_bit(std::to_underlying(b))); - return std::to_underlying(a) & std::to_underlying(b); -} - -template -[[nodiscard]] constexpr bool contains_any_bit(T a, T b) noexcept -{ - static_assert(std::is_unsigned_v>); - return std::to_underlying(a) & std::to_underlying(b); -} - -} // iris - -#endif diff --git a/include/iris/enum_bitops_algorithm.hpp b/include/iris/enum_bitops_algorithm.hpp deleted file mode 100644 index 875633f..0000000 --- a/include/iris/enum_bitops_algorithm.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef IRIS_ZZ_ENUM_BITOPS_ALGORITHM_HPP -#define IRIS_ZZ_ENUM_BITOPS_ALGORITHM_HPP - -// SPDX-License-Identifier: MIT - -#include // IWYU pragma: keep - -#include - -#include -#include -#include - -namespace iris { - -namespace detail { - -struct each_bit_fn -{ - template - [[nodiscard]] static constexpr auto operator()(T flags) noexcept - { - static_assert(std::is_unsigned_v>); - static_assert(detail::bitops_enum_has_max_bit); - static_assert(bitops_enabled::max_bit < std::numeric_limits>::digits); - - if constexpr (detail::bitops_enum_has_min_bit) { - static_assert(bitops_enabled::min_bit <= bitops_enabled::max_bit); - return std::views::iota(bitops_enabled::min_bit, bitops_enabled::max_bit + 1) - | std::views::filter([cat = std::to_underlying(flags)](int i) constexpr noexcept -> bool { return (cat >> i) & 1; }) - | std::views::transform([](int i) constexpr noexcept { return static_cast(static_cast>(1) << i); }); - } else { - return std::views::iota(0, bitops_enabled::max_bit + 1) - | std::views::filter([cat = std::to_underlying(flags)](int i) constexpr noexcept -> bool { return (cat >> i) & 1; }) - | std::views::transform([](int i) constexpr noexcept { return static_cast(static_cast>(1) << i); }); - } - } -}; - -} // detail - -inline constexpr detail::each_bit_fn each_bit{}; - -} // iris - -#endif diff --git a/include/iris/enum_bitops_io.hpp b/include/iris/enum_bitops_io.hpp deleted file mode 100644 index 2e76cde..0000000 --- a/include/iris/enum_bitops_io.hpp +++ /dev/null @@ -1,99 +0,0 @@ -#ifndef IRIS_ZZ_ENUM_BITOPS_IO_HPP -#define IRIS_ZZ_ENUM_BITOPS_IO_HPP - -// SPDX-License-Identifier: MIT - -#include // IWYU pragma: keep - -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace iris { - -inline namespace bitops_operators { - -template -std::ostream& operator<<(std::ostream& os, T const& val) -{ - using std::to_string; - - if constexpr (requires { - { to_string(val) } -> std::convertible_to; - }) { - return os << to_string(val); - } else { - return os << std::to_underlying(val); - } -} - -} // bitops_operators - -template -[[nodiscard]] constexpr T parse_flag(StringLike auto const& str) noexcept -{ - return bitops_enabled::parse(std::basic_string_view{str}); -} - -template -[[nodiscard]] constexpr T parse_flags(StringLike auto const& str, StringLike auto const& delim) noexcept -{ - using namespace iris::bitops_operators; - - std::basic_string_view str_sv{str}; - std::basic_string_view delim_sv{delim}; - - T res{}; - - for (auto const& r : str_sv | std::views::split(delim_sv)) { - std::basic_string_view const part_str{r.begin(), r.end()}; - - auto const part = iris::parse_flag(part_str); - if (part == T{}) { - return T{}; - } - res |= part; - } - - return res; -} - -} // iris - -namespace std { - -template<::iris::BitopsEnabledEnum T, class CharT> -struct formatter : formatter, CharT> -{ - using base_formatter = formatter, CharT>; - - template - constexpr auto parse(Context& ctx) - { - if (ctx.begin() == ctx.end()) return ctx.end(); - has_format_spec_ = true; - return base_formatter::parse(ctx); - } - - template - auto format(T const& val, Context& ctx) const - { - if (has_format_spec_) { - return base_formatter::format(std::to_underlying(val), ctx); - } - return format_to(ctx.out(), "{}", to_string(val)); - } - -private: - bool has_format_spec_ = false; -}; - -} // std - -#endif diff --git a/test/enum.cpp b/test/enum.cpp index 6de3afd..285b0fa 100644 --- a/test/enum.cpp +++ b/test/enum.cpp @@ -2,37 +2,95 @@ #include "iris_test.hpp" -#include -#include -#include +#include +#include #include -#include #include #include #include #include +enum PlainEnum {}; +enum struct PlainScopedEnum {}; + +enum struct Enum_0_4 : unsigned {}; +enum struct Enum_2_4 : unsigned {}; + +enum struct BitEnum_0_4 : unsigned {}; +enum struct BitEnum_2_4 : unsigned {}; + +template<> +struct iris::enum_traits +{ + static constexpr Enum_0_4 max_element = Enum_0_4{0}; +}; +template<> +struct iris::enum_traits +{ + static constexpr Enum_2_4 min_element = Enum_2_4{2}; + static constexpr Enum_2_4 max_element = Enum_2_4{4}; +}; + +template<> +struct iris::enum_traits +{ + static constexpr int max_bit = 4; +}; +template<> +struct iris::enum_traits +{ + static constexpr int min_bit = 2; + static constexpr int max_bit = 4; +}; + +TEST_CASE("enum") +{ + STATIC_CHECK(!iris::Enum); + STATIC_CHECK(!iris::Enum); + + STATIC_CHECK(iris::BitopsEnabledEnum); + STATIC_CHECK(!iris::FiniteEnum); + + STATIC_CHECK(iris::BitopsEnabledEnum); + STATIC_CHECK(iris::FiniteEnum); + STATIC_CHECK(iris::enum_count_v == 3); + STATIC_CHECK(iris::enum_values() == std::array{Enum_2_4{2}, Enum_2_4{3}, Enum_2_4{4}}); + + STATIC_CHECK(iris::BitopsEnabledEnum); + STATIC_CHECK(iris::FiniteEnum); + STATIC_CHECK(iris::enum_count_v == 5); + STATIC_CHECK(iris::enum_values() == std::array{BitEnum_0_4{1u << 0}, BitEnum_0_4{1u << 1}, BitEnum_0_4{1u << 2}, BitEnum_0_4{1u << 3}, BitEnum_0_4{1u << 4}}); + + STATIC_CHECK(iris::BitopsEnabledEnum); + STATIC_CHECK(iris::FiniteEnum); + STATIC_CHECK(iris::enum_count_v == 3); + STATIC_CHECK(iris::enum_values() == std::array{BitEnum_2_4{1u << 2}, BitEnum_2_4{1u << 3}, BitEnum_2_4{1u << 4}}); +} + enum class MyFlags : std::uint8_t { - FOO = 1 << 0, - BAR = 1 << 1, - BAZ = 1 << 2, + NO_FLAGS = 0u, + FOO = 1u << 0, + BAR = 1u << 1, + BAZ = 1u << 2, }; enum class SpellType : std::uint8_t { - TYPE_ATTACK = 1 << 0, - TYPE_DEFENSE = 1 << 1, + NO_SPELL_TYPE = 0u, + + TYPE_ATTACK = 1u << 0, + TYPE_DEFENSE = 1u << 1, - ATTR_FIRE = 1 << 2, - ATTR_WATER = 1 << 3, - ATTR_THUNDER = 1 << 4, + ATTR_FIRE = 1u << 2, + ATTR_WATER = 1u << 3, + ATTR_THUNDER = 1u << 4, }; template<> -struct iris::bitops_enabled : std::true_type +struct iris::enum_traits { static MyFlags parse(std::string_view sv) noexcept { @@ -45,18 +103,20 @@ struct iris::bitops_enabled : std::true_type }; template<> -struct iris::bitops_enabled : std::true_type +struct iris::enum_traits { static constexpr int min_bit = 2; static constexpr int max_bit = 4; }; -TEST_CASE("enum") +TEST_CASE("enum: flags") { - using namespace iris::bitops_operators; + using namespace iris::enum_bitops_operators; using enum MyFlags; + static_assert(iris::Enum); + CHECK((~FOO == static_cast(~std::to_underlying(FOO)))); CHECK(((FOO & BAR) == static_cast(std::to_underlying(FOO) & std::to_underlying(BAR)))); @@ -96,6 +156,8 @@ TEST_CASE("enum") CHECK((iris::parse_flags("foo|yay", "|") == MyFlags{})); CHECK((iris::parse_flags("foo,bar", "|") == MyFlags{})); + static_assert(iris::detail::enum_has_finite_minmax_bit); + CHECK(std::ranges::equal( iris::each_bit(SpellType::TYPE_ATTACK | SpellType::ATTR_FIRE | SpellType::ATTR_THUNDER), std::vector{SpellType::ATTR_FIRE, SpellType::ATTR_THUNDER} diff --git a/test/library_config.cpp b/test/library_config.cpp index 9f025b3..3b4dbd4 100644 --- a/test/library_config.cpp +++ b/test/library_config.cpp @@ -53,9 +53,8 @@ #include #include #include -#include -#include -#include +#include +#include #include #include #include