1
0
Fork 0
mirror of https://github.com/Neargye/magic_enum.git synced 2026-01-09 23:34:23 +00:00

Make enum_fuse typesafe (fixes #143) (#145)

This commit is contained in:
Pavel I. Kryukov 2022-03-08 11:38:19 +03:00 committed by GitHub
parent 785b3f253d
commit 9d1cf196cf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 34 additions and 7 deletions

View file

@ -330,10 +330,16 @@ constexpr string_view enum_type_name() noexcept;
```cpp
template <typename... Es>
[[nodiscard]] constexpr optional<std::uintmax_t> enum_fuse(Es... values);
[[nodiscard]] constexpr optional<FusedEnum> 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<FusedEnum>` 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 <typename... Es>
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
// ...
}
```

View file

@ -917,8 +917,7 @@ template <typename E>
constexpr optional<std::uintmax_t> fuse_one_enum(optional<std::uintmax_t> 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<E>() + 1)) | (index.value() + 1);
return (hash.value() << detail::log2(enum_count<E>() + 1)) | index.value();
}
}
return {};
@ -934,6 +933,15 @@ constexpr optional<std::uintmax_t> fuse_enum(E head, Es... tail) noexcept {
return fuse_one_enum(fuse_enum(tail...), head);
}
template <typename... Es>
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<Enum>(hash.value()));
return optional<Enum>{};
}
} // 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 <typename... Es>
static_assert((std::is_enum_v<std::decay_t<Es>> && ...), "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<Es>() + 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;
}

View file

@ -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);
}