From 32ff7f53fab85b80ce12b6cd466e3548a6c1bcf9 Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Tue, 10 Sep 2024 21:33:16 -0700 Subject: [PATCH] Rich enum combos --- .clang-tidy | 1 + BUILD.bazel | 1 + test/enum_utils_test.cpp | 278 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 280 insertions(+) diff --git a/.clang-tidy b/.clang-tidy index 1506231e..356edaa1 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -74,6 +74,7 @@ Checks: > -bugprone-easily-swappable-parameters, -bugprone-switch-missing-default-case, -bugprone-unchecked-optional-access, + -performance-avoid-endl, # Turn all the warnings from the checks above into errors. WarningsAsErrors: "*" diff --git a/BUILD.bazel b/BUILD.bazel index 16aa39d2..2d045f75 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1111,6 +1111,7 @@ cc_test( deps = [ ":concepts", ":consteval_compare", + ":enum_set", ":enum_utils", ":enums_test_common", "@com_google_googletest//:gtest", diff --git a/test/enum_utils_test.cpp b/test/enum_utils_test.cpp index 460b0f25..eea2b9e5 100644 --- a/test/enum_utils_test.cpp +++ b/test/enum_utils_test.cpp @@ -4,14 +4,20 @@ #include "fixed_containers/concepts.hpp" #include "fixed_containers/consteval_compare.hpp" +#include "fixed_containers/enum_map.hpp" +#include "fixed_containers/enum_set.hpp" +#include "fixed_containers/fixed_map.hpp" +#include "fixed_containers/pair.hpp" #include #include #include #include +#include #include #include +#include namespace fixed_containers::rich_enums_detail { @@ -173,6 +179,278 @@ TEST(RichEnum, EnumConstantParityWithBuiltinEnums) } } +namespace nested_enums +{ + +enum class ColorBaseBackingEnum +{ + RED, + BLUE, +}; + +class ColorBase : public SkeletalRichEnum +{ + friend SkeletalRichEnum::ValuesFriend; + using SkeletalRichEnum::SkeletalRichEnum; + +public: + FIXED_CONTAINERS_RICH_ENUM_CONSTANT_GEN_HELPER(ColorBase, RED) + FIXED_CONTAINERS_RICH_ENUM_CONSTANT_GEN_HELPER(ColorBase, BLUE) +}; + +enum class ColorVariantRed +{ + PINK, + ORANGE, +}; +enum class ColorVariantBlue +{ + CYAN, + AZURE, +}; + +enum class ColorVariantAll +{ + INVALID, +}; + +inline constexpr std::size_t MAXIMUM_COLOR_VARIANT_COUNT = + std::max(magic_enum::enum_count(), magic_enum::enum_count()); + +template +class ColorVariant; + +// This rich enum specialization is not strictly needed, but it gets us: +// - Optional semantics +// - Ability to reference to variants with the original color via ColorVariant +// This is show-cased with RED, whereas BLUE uses a normal enum. +template <> +class ColorVariant + : public SkeletalRichEnum, ColorVariantRed> +{ + friend SkeletalRichEnum::ValuesFriend; + using SkeletalRichEnum::SkeletalRichEnum; + +public: + FIXED_CONTAINERS_RICH_ENUM_CONSTANT_GEN_HELPER(ColorVariant, PINK) + FIXED_CONTAINERS_RICH_ENUM_CONSTANT_GEN_HELPER(ColorVariant, ORANGE) +}; +template <> +class ColorVariant + : public SkeletalRichEnum, ColorVariantBlue> +{ + friend SkeletalRichEnum::ValuesFriend; + using SkeletalRichEnum::SkeletalRichEnum; + +public: + FIXED_CONTAINERS_RICH_ENUM_CONSTANT_GEN_HELPER(ColorVariant, CYAN) + FIXED_CONTAINERS_RICH_ENUM_CONSTANT_GEN_HELPER(ColorVariant, AZURE) +}; + +template <> +class ColorVariant +{ +public: + using ColorStdVariant = + std::variant, ColorVariant>; + +private: + ColorStdVariant variant_{}; + +public: + template + constexpr ColorVariant(Args&&... args) + : variant_{std::forward(args)...} + { + } + + constexpr bool operator==(const ColorVariant<>& other) const = default; + constexpr auto operator<=>(const ColorVariant<>& other) const = default; + + [[nodiscard]] const ColorStdVariant& as_std_variant() const { return variant_; } + ColorStdVariant& as_std_variant() { return variant_; } + + template + constexpr std::optional to() + { + if (const U* valid_variant = std::get_if(&as_std_variant()); valid_variant != nullptr) + { + return std::optional{*valid_variant}; + } + return std::nullopt; + } +}; + +enum class ColorBackingEnum +{ + RED_PINK, + RED_ORANGE, + BLUE_CYAN, + BLUE_AZURE, +}; + +struct ColorData +{ + ColorBase plain_color{}; + ColorVariant<> color_variant{}; +}; + +struct ColorValues +{ + using BE = ColorBackingEnum; + static constexpr auto VALUES = EnumMap::create_with_all_entries({ + Pair{BE::RED_PINK, + {ColorBase::RED(), ColorVariant::PINK()}}, + Pair{BE::RED_ORANGE, + ColorData{ColorBase::RED(), ColorVariant::ORANGE()}}, + Pair{BE::BLUE_CYAN, + ColorData{ColorBase::BLUE(), ColorVariant::CYAN()}}, + Pair{BE::BLUE_AZURE, + ColorData{ColorBase::BLUE(), ColorVariant::AZURE()}}, + }); + + static constexpr EnumMap, BE, MAXIMUM_COLOR_VARIANT_COUNT>> + REVERSE_MAP = []() + { + EnumMap, BE, MAXIMUM_COLOR_VARIANT_COUNT>> output{}; + + for (const auto& [backing_enum, v] : VALUES) + { + output[v.plain_color][v.color_variant] = backing_enum; + } + + return output; + }(); +}; + +class Color : public SkeletalRichEnum +{ +private: + friend SkeletalRichEnum::ValuesFriend; + using SkeletalRichEnum::SkeletalRichEnum; + +public: + FIXED_CONTAINERS_RICH_ENUM_CONSTANT_GEN_HELPER(Color, RED_PINK) + FIXED_CONTAINERS_RICH_ENUM_CONSTANT_GEN_HELPER(Color, RED_ORANGE) + FIXED_CONTAINERS_RICH_ENUM_CONSTANT_GEN_HELPER(Color, BLUE_CYAN) + FIXED_CONTAINERS_RICH_ENUM_CONSTANT_GEN_HELPER(Color, BLUE_AZURE) + + static constexpr std::optional from(const ColorBase& plain_color, + const ColorVariant<>& color_variant) + { + auto it1 = ColorValues::REVERSE_MAP.find(plain_color); + if (it1 == ColorValues::REVERSE_MAP.end()) + { + return std::nullopt; + } + + const FixedMap, BackingEnum, MAXIMUM_COLOR_VARIANT_COUNT>& variant_map = + it1->second; + auto it2 = variant_map.find(color_variant); + if (it2 == variant_map.end()) + { + return std::nullopt; + } + + return value_of(it2->second); + } + +public: + [[nodiscard]] constexpr ColorBase plain_color() const + { + return ColorValues::VALUES.at(backing_enum()).plain_color; + } + [[nodiscard]] constexpr ColorVariant<> color_variant() const + { + return ColorValues::VALUES.at(backing_enum()).color_variant; + } + + template + constexpr void variant_switch(Args&&... args) const + { + std::visit(Overloaded{std::forward(args)...}, color_variant().as_std_variant()); + } +}; + +static constexpr EnumSet ALL_RED_VARIANTS = []() +{ + EnumSet out{}; + for (const auto& color : Color::values()) + { + if (color.plain_color() == ColorBase::RED()) + { + out.insert(color); + } + } + return out; +}(); + +static_assert(ALL_RED_VARIANTS.size() == 2); +static_assert(ALL_RED_VARIANTS.contains(Color::RED_ORANGE())); +static_assert(ALL_RED_VARIANTS.contains(Color::RED_PINK())); + +namespace +{ +void flat_switch(const Color& color) +{ + switch (color) + { + case Color::RED_PINK(): + std::cout << "RED_PINK" << std::endl; + break; + case Color::RED_ORANGE(): + std::cout << color.to_string() << std::endl; + break; + case Color::BLUE_CYAN(): + std::cout << "BLUE_CYAN" << std::endl; + break; + case Color::BLUE_AZURE(): + std::cout << "BLUE_AZURE" << std::endl; + break; + } +} + +void automatic_hierarchical_switch(const Color& color) +{ + color.variant_switch( + [](const ColorVariant& variant) + { + switch (variant) + { + case ColorVariant::PINK(): + std::cout << "RED:PINK2" << std::endl; + break; + case ColorVariant::ORANGE(): + std::cout << "RED:ORANGE2" << std::endl; + break; + } + }, + [](const ColorVariant& variant) + { + switch (variant) + { + case ColorVariant::CYAN(): + std::cout << "BLUE:CYAN2" << std::endl; + break; + case ColorVariant::AZURE(): + std::cout << "BLUE:AZURE2" << std::endl; + break; + } + }); +} + +} // namespace + +TEST(NestedEnums, Example) +{ + const Color color = + Color::from(ColorBase::BLUE(), ColorVariant::AZURE()).value(); + flat_switch(color); + automatic_hierarchical_switch(color); +} + +} // namespace nested_enums + TEST(BuiltinEnumAdapter, Ordinal) { {