From 9d1cf196cf59cef8ac8da8ea95d896965f8bc22d Mon Sep 17 00:00:00 2001 From: "Pavel I. Kryukov" Date: Tue, 8 Mar 2022 11:38:19 +0300 Subject: [PATCH] Make enum_fuse typesafe (fixes #143) (#145) --- doc/reference.md | 11 +++++++++-- include/magic_enum.hpp | 16 ++++++++++++++-- test/test.cpp | 14 +++++++++++--- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/doc/reference.md b/doc/reference.md index 9266752..adbf075 100644 --- a/doc/reference.md +++ b/doc/reference.md @@ -330,10 +330,16 @@ constexpr string_view enum_type_name() noexcept; ```cpp template -[[nodiscard]] constexpr optional enum_fuse(Es... values); +[[nodiscard]] constexpr optional enum_fuse(Es... values); ``` -* Returns a bijective mix of several enum values. This can be used to emulate 2D switch/case statements. +* Returns a typesafe bijective mix of several enum values. This can be used to emulate 2D switch/case statements. + +* Return type is `optional` where FusedEnum is an incomplete enum. It is unique for any given combination of `Es...`. + +* Switch/case statement over an incomplete enum is a Visual Studio warning C4064 + * You have to silent (/wd4064) or ignore it. + * Alternatively, define MAGIC_ENUM_NO_TYPESAFE_ENUM_FUSE to disable type-safety (FusedEnum equals std::uintmax_t). * Examples @@ -341,6 +347,7 @@ template switch (magic_enum::enum_fuse(color, direction).value()) { case magic_enum::enum_fuse(Color::RED, Directions::Up).value(): // ... case magic_enum::enum_fuse(Color::BLUE, Directions::Down).value(): // ... + case magic_enum::enum_fuse(Directions::BLUE, Color::Down).value(): // Compilation error // ... } ``` diff --git a/include/magic_enum.hpp b/include/magic_enum.hpp index 628cfc0..ea5339f 100644 --- a/include/magic_enum.hpp +++ b/include/magic_enum.hpp @@ -917,8 +917,7 @@ template constexpr optional fuse_one_enum(optional hash, E value) noexcept { if (hash.has_value()) { if (const auto index = enum_index(value); index.has_value()) { - // Add 1 to prevent matching 2D fusions with 3D fusions etc. - return (hash.value() << detail::log2(enum_count() + 1)) | (index.value() + 1); + return (hash.value() << detail::log2(enum_count() + 1)) | index.value(); } } return {}; @@ -934,6 +933,15 @@ constexpr optional fuse_enum(E head, Es... tail) noexcept { return fuse_one_enum(fuse_enum(tail...), head); } +template +constexpr auto typesafe_fuse_enum(Es... values) noexcept { + enum class Enum : std::uintmax_t; + auto hash = fuse_enum(values...); + if (hash.has_value()) + return optional(static_cast(hash.value())); + return optional{}; +} + } // namespace magic_enum::fusion_detail // Returns a bijective mix of several enum values. This can be used to emulate 2D switch/case statements. @@ -942,7 +950,11 @@ template static_assert((std::is_enum_v> && ...), "magic_enum::enum_fuse works only with enums"); static_assert(sizeof...(Es) >= 2, "magic_enum::enum_fuse requires at least 2 enums"); static_assert((detail::log2(enum_count() + 1) + ...) <= (sizeof(std::uintmax_t) * 8), "magic_enum::enum_fuse does not work for large enums"); +#ifdef MAGIC_ENUM_NO_TYPESAFE_ENUM_FUSE const auto fuse = fusion_detail::fuse_enum(values...); +#else + const auto fuse = fusion_detail::typesafe_fuse_enum(values...); +#endif return assert(fuse.has_value()), fuse; } diff --git a/test/test.cpp b/test/test.cpp index 3ffdd12..84fea6e 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -1026,6 +1026,11 @@ TEST_CASE("constexpr_for") { }); } +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4064) +#endif + static int switch_case_2d(Color color, Directions direction) { switch (magic_enum::enum_fuse(color, direction).value()) { case magic_enum::enum_fuse(Color::RED, Directions::Up).value(): @@ -1043,21 +1048,24 @@ static int switch_case_3d(Color color, Directions direction, Index index) { switch (magic_enum::enum_fuse(color, direction, index).value()) { case magic_enum::enum_fuse(Color::RED, Directions::Up, Index::zero).value(): return 1; - // model accidental removal of last index, must not match anything - case magic_enum::enum_fuse(Color::BLUE, Directions::Up).value(): + case magic_enum::enum_fuse(Color::BLUE, Directions::Up, Index::zero).value(): return 2; default: return 0; } } +#ifdef _MSC_VER +# pragma warning(pop) +#endif + TEST_CASE("multdimensional-switch-case") { REQUIRE(switch_case_2d(Color::RED, Directions::Up) == 1); REQUIRE(switch_case_2d(Color::RED, Directions::Down) == 0); REQUIRE(switch_case_2d(Color::BLUE, Directions::Up) == 0); REQUIRE(switch_case_2d(Color::BLUE, Directions::Down) == 2); REQUIRE(switch_case_3d(Color::RED, Directions::Up, Index::zero) == 1); - REQUIRE(switch_case_3d(Color::BLUE, Directions::Up, Index::zero) == 0); + REQUIRE(switch_case_3d(Color::BLUE, Directions::Up, Index::zero) == 2); REQUIRE(switch_case_3d(Color::BLUE, Directions::Up, Index::one) == 0); REQUIRE(switch_case_3d(Color::BLUE, Directions::Up, Index::two) == 0); }