Skip to content

Commit

Permalink
checking that the enum is valid (not empty and not forward declaratio…
Browse files Browse the repository at this point in the history
…n) (#323)
  • Loading branch information
Neargye authored Dec 16, 2023
1 parent 41f674b commit e09f257
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 20 deletions.
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,6 @@ Header-only C++17 library provides static reflection for enums, work with any en
* IOstream operator for enum

```cpp
using namespace magic_enum::ostream_operators; // out-of-the-box ostream operators for enums.
Color color = Color::BLUE;
std::cout << color << std::endl; // "BLUE"

using namespace magic_enum::ostream_operators; // out-of-the-box ostream operators for enums.
Color color = Color::BLUE;
std::cout << color << std::endl; // "BLUE"
Expand Down
44 changes: 37 additions & 7 deletions include/magic_enum/magic_enum.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,9 @@ namespace detail {
template <typename T>
struct supported
#if defined(MAGIC_ENUM_SUPPORTED) && MAGIC_ENUM_SUPPORTED || defined(MAGIC_ENUM_NO_CHECK_SUPPORT)
: std::true_type {};
: std::true_type {};
#else
: std::false_type {};
: std::false_type {};
#endif

template <auto V, typename E = std::decay_t<decltype(V)>, std::enable_if_t<std::is_enum_v<E>, int> = 0>
Expand Down Expand Up @@ -881,6 +881,17 @@ constexpr U values_ors() noexcept {
return ors;
}

template <typename E, enum_subtype S>
struct is_valid_enum
#if defined(MAGIC_ENUM_NO_CHECK_VALID_ENUM_TYPE)
: std::true_type {};
#else
: std::bool_constant<std::is_enum_v<E> && (count_v<E, S> > 0)> {};
#endif

template <typename E, enum_subtype S>
inline constexpr auto is_valid_enum_v = is_valid_enum<std::decay_t<E>, S>{};

template <bool, typename R>
struct enable_if_enum {};

Expand Down Expand Up @@ -1180,6 +1191,7 @@ template <typename E, detail::enum_subtype S = detail::subtype_v<E>>
template <typename E, detail::enum_subtype S = detail::subtype_v<E>>
[[nodiscard]] constexpr auto enum_value(std::size_t index) noexcept -> detail::enable_if_t<E, std::decay_t<E>> {
using D = std::decay_t<E>;
static_assert(detail::is_valid_enum_v<D, S>, "magic_enum requires enum implementation and valid max and min.");

if constexpr (detail::is_sparse_v<D, S>) {
return MAGIC_ENUM_ASSERT(index < detail::count_v<D, S>), detail::values_v<D, S>[index];
Expand All @@ -1194,6 +1206,7 @@ template <typename E, detail::enum_subtype S = detail::subtype_v<E>>
template <typename E, std::size_t I, detail::enum_subtype S = detail::subtype_v<E>>
[[nodiscard]] constexpr auto enum_value() noexcept -> detail::enable_if_t<E, std::decay_t<E>> {
using D = std::decay_t<E>;
static_assert(detail::is_valid_enum_v<D, S>, "magic_enum requires enum implementation and valid max and min.");
static_assert(I < detail::count_v<D, S>, "magic_enum::enum_value out of range.");

return enum_value<D, S>(I);
Expand All @@ -1202,7 +1215,10 @@ template <typename E, std::size_t I, detail::enum_subtype S = detail::subtype_v<
// Returns std::array with enum values, sorted by enum value.
template <typename E, detail::enum_subtype S = detail::subtype_v<E>>
[[nodiscard]] constexpr auto enum_values() noexcept -> detail::enable_if_t<E, detail::values_t<E, S>> {
return detail::values_v<std::decay_t<E>, S>;
using D = std::decay_t<E>;
static_assert(detail::is_valid_enum_v<D, S>, "magic_enum requires enum implementation and valid max and min.");

return detail::values_v<D, S>;
}

// Returns integer value from enum value.
Expand All @@ -1223,6 +1239,7 @@ template <typename E, detail::enum_subtype S = detail::subtype_v<E>>
[[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_t<E, optional<std::size_t>> {
using D = std::decay_t<E>;
using U = underlying_type_t<D>;
static_assert(detail::is_valid_enum_v<D, S>, "magic_enum requires enum implementation and valid max and min.");

if constexpr (detail::count_v<D, S> == 0) {
static_cast<void>(value);
Expand Down Expand Up @@ -1255,14 +1272,17 @@ template <typename E, detail::enum_subtype S = detail::subtype_v<E>>
template <detail::enum_subtype S, typename E>
[[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_t<E, optional<std::size_t>> {
using D = std::decay_t<E>;
static_assert(detail::is_valid_enum_v<D, S>, "magic_enum requires enum implementation and valid max and min.");

return enum_index<D, S>(value);
}

// Obtains index in enum values from static storage enum variable.
template <auto V, detail::enum_subtype S = detail::subtype_v<std::decay_t<decltype(V)>>>
[[nodiscard]] constexpr auto enum_index() noexcept -> detail::enable_if_t<decltype(V), std::size_t> {
constexpr auto index = enum_index<std::decay_t<decltype(V)>, S>(V);
[[nodiscard]] constexpr auto enum_index() noexcept -> detail::enable_if_t<decltype(V), std::size_t> {\
using D = std::decay_t<decltype(V)>;
static_assert(detail::is_valid_enum_v<D, S>, "magic_enum requires enum implementation and valid max and min.");
constexpr auto index = enum_index<D, S>(V);
static_assert(index, "magic_enum::enum_index enum value does not have a index.");

return *index;
Expand All @@ -1283,6 +1303,7 @@ template <auto V>
template <typename E, detail::enum_subtype S = detail::subtype_v<E>>
[[nodiscard]] constexpr auto enum_name(E value) noexcept -> detail::enable_if_t<E, string_view> {
using D = std::decay_t<E>;
static_assert(detail::is_valid_enum_v<D, S>, "magic_enum requires enum implementation and valid max and min.");

if (const auto i = enum_index<D, S>(value)) {
return detail::names_v<D, S>[*i];
Expand All @@ -1295,20 +1316,27 @@ template <typename E, detail::enum_subtype S = detail::subtype_v<E>>
template <detail::enum_subtype S, typename E>
[[nodiscard]] constexpr auto enum_name(E value) -> detail::enable_if_t<E, string_view> {
using D = std::decay_t<E>;
static_assert(detail::is_valid_enum_v<D, S>, "magic_enum requires enum implementation and valid max and min.");

return enum_name<D, S>(value);
}

// Returns std::array with names, sorted by enum value.
template <typename E, detail::enum_subtype S = detail::subtype_v<E>>
[[nodiscard]] constexpr auto enum_names() noexcept -> detail::enable_if_t<E, detail::names_t<E, S>> {
return detail::names_v<std::decay_t<E>, S>;
using D = std::decay_t<E>;
static_assert(detail::is_valid_enum_v<D, S>, "magic_enum requires enum implementation and valid max and min.");

return detail::names_v<D, S>;
}

// Returns std::array with pairs (value, name), sorted by enum value.
template <typename E, detail::enum_subtype S = detail::subtype_v<E>>
[[nodiscard]] constexpr auto enum_entries() noexcept -> detail::enable_if_t<E, detail::entries_t<E, S>> {
return detail::entries_v<std::decay_t<E>, S>;
using D = std::decay_t<E>;
static_assert(detail::is_valid_enum_v<D, S>, "magic_enum requires enum implementation and valid max and min.");

return detail::entries_v<D, S>;
}

// Allows you to write magic_enum::enum_cast<foo>("bar", magic_enum::case_insensitive);
Expand All @@ -1319,6 +1347,7 @@ inline constexpr auto case_insensitive = detail::case_insensitive<>{};
template <typename E, detail::enum_subtype S = detail::subtype_v<E>>
[[nodiscard]] constexpr auto enum_cast(underlying_type_t<E> value) noexcept -> detail::enable_if_t<E, optional<std::decay_t<E>>> {
using D = std::decay_t<E>;
static_assert(detail::is_valid_enum_v<D, S>, "magic_enum requires enum implementation and valid max and min.");

if constexpr (detail::count_v<D, S> == 0) {
static_cast<void>(value);
Expand Down Expand Up @@ -1352,6 +1381,7 @@ template <typename E, detail::enum_subtype S = detail::subtype_v<E>>
template <typename E, detail::enum_subtype S = detail::subtype_v<E>, typename BinaryPredicate = std::equal_to<>>
[[nodiscard]] constexpr auto enum_cast(string_view value, [[maybe_unused]] BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable<BinaryPredicate>()) -> detail::enable_if_t<E, optional<std::decay_t<E>>, BinaryPredicate> {
using D = std::decay_t<E>;
static_assert(detail::is_valid_enum_v<D, S>, "magic_enum requires enum implementation and valid max and min.");

if constexpr (detail::count_v<D, S> == 0) {
static_cast<void>(value);
Expand Down
4 changes: 2 additions & 2 deletions include/magic_enum/magic_enum_containers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ template <typename E, typename V, typename Index = default_indexing<E>>
struct array {
static_assert(std::is_enum_v<E>);
static_assert(std::is_trivially_constructible_v<Index>);
static_assert(enum_count<E>() == 0 || Index::at(enum_values<E>().front())); // check Index is constexpr
static_assert(enum_count<E>() > 0 && Index::at(enum_values<E>().front()));

using index_type = Index;
using container_type = std::array<V, enum_count<E>()>;
Expand Down Expand Up @@ -481,7 +481,7 @@ template <typename E, typename Index = default_indexing<E>>
class bitset {
static_assert(std::is_enum_v<E>);
static_assert(std::is_trivially_constructible_v<Index>);
static_assert(enum_count<E>() == 0 || Index::at(enum_values<E>().front())); // check Index is constexpr
static_assert(enum_count<E>() > 0 && Index::at(enum_values<E>().front()));

using base_type = std::conditional_t<enum_count<E>() <= 8, std::uint_least8_t,
std::conditional_t<enum_count<E>() <= 16, std::uint_least16_t,
Expand Down
3 changes: 3 additions & 0 deletions include/magic_enum/magic_enum_flags.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ template <typename E>
using D = std::decay_t<E>;
using U = underlying_type_t<D>;
constexpr auto S = detail::enum_subtype::flags;
static_assert(detail::is_valid_enum_v<D, S>, "magic_enum requires enum implementation and valid max and min.");

string name;
auto check_value = U{0};
Expand Down Expand Up @@ -82,6 +83,7 @@ template <typename E>
using D = std::decay_t<E>;
using U = underlying_type_t<D>;
constexpr auto S = detail::enum_subtype::flags;
static_assert(detail::is_valid_enum_v<D, S>, "magic_enum requires enum implementation and valid max and min.");

if constexpr (detail::count_v<D, S> == 0) {
static_cast<void>(value);
Expand Down Expand Up @@ -117,6 +119,7 @@ template <typename E, typename BinaryPredicate = std::equal_to<>>
using D = std::decay_t<E>;
using U = underlying_type_t<D>;
constexpr auto S = detail::enum_subtype::flags;
static_assert(detail::is_valid_enum_v<D, S>, "magic_enum requires enum implementation and valid max and min.");

if constexpr (detail::count_v<D, S> == 0) {
static_cast<void>(value);
Expand Down
2 changes: 2 additions & 0 deletions include/magic_enum/magic_enum_switch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ template <typename Result = detail::default_result_type, typename E, detail::enu
constexpr decltype(auto) enum_switch(F&& f, E value) {
using D = std::decay_t<E>;
static_assert(std::is_enum_v<D>, "magic_enum::enum_switch requires enum type.");
static_assert(detail::is_valid_enum_v<D, S>, "magic_enum requires enum implementation and valid max and min.");

#if defined(MAGIC_ENUM_ENABLE_HASH) || defined(MAGIC_ENUM_ENABLE_HASH_SWITCH)
return detail::constexpr_switch<&detail::values_v<D, S>, detail::case_call_t::value>(
Expand All @@ -160,6 +161,7 @@ template <typename Result, typename E, detail::enum_subtype S = detail::subtype_
constexpr decltype(auto) enum_switch(F&& f, E value, Result&& result) {
using D = std::decay_t<E>;
static_assert(std::is_enum_v<D>, "magic_enum::enum_switch requires enum type.");
static_assert(detail::is_valid_enum_v<D, S>, "magic_enum requires enum implementation and valid max and min.");

#if defined(MAGIC_ENUM_ENABLE_HASH) || defined(MAGIC_ENUM_ENABLE_HASH_SWITCH)
return detail::constexpr_switch<&detail::values_v<D, S>, detail::case_call_t::value>(
Expand Down
1 change: 1 addition & 0 deletions include/magic_enum/magic_enum_utility.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ template <typename E, detail::enum_subtype S = detail::subtype_v<E>, typename F,
constexpr auto enum_for_each(F&& f) {
using D = std::decay_t<E>;
static_assert(std::is_enum_v<D>, "magic_enum::enum_for_each requires enum type.");
static_assert(detail::is_valid_enum_v<D, S>, "magic_enum requires enum implementation and valid max and min.");
constexpr auto sep = std::make_index_sequence<detail::count_v<D, S>>{};

if constexpr (detail::all_invocable<D, S, F>(sep)) {
Expand Down
31 changes: 29 additions & 2 deletions test/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>

#undef MAGIC_ENUM_RANGE_MIN
#define MAGIC_ENUM_NO_CHECK_VALID_ENUM_TYPE
#define MAGIC_ENUM_RANGE_MIN -120
#undef MAGIC_ENUM_RANGE_MAX
#define MAGIC_ENUM_RANGE_MAX 120
#include <magic_enum.hpp>
#include <magic_enum_fuse.hpp>
Expand Down Expand Up @@ -1247,3 +1246,31 @@ TEST_CASE("enum_prev_value_circular") {
REQUIRE(enum_prev_value_circular(Color::RED, -3) == Color::RED);
REQUIRE(enum_prev_value_circular(Color::RED, -4) == Color::GREEN);
}

TEST_CASE("valid_enum") {
//enum Forward1;
enum Forward2 : uint32_t;
enum class Forward3;
enum class Forward4 : uint32_t;
enum Empty1 {};
enum Empty2 : uint32_t {};
enum class Empty3 {};
enum class Empty4 : uint32_t {};

//REQUIRE(magic_enum::detail::is_valid_enum_v<Forward1, as_flags<true>>);
//REQUIRE(magic_enum::detail::is_valid_enum_v<Forward1, as_flags<false>>);
REQUIRE(magic_enum::detail::is_valid_enum_v<Forward2, as_flags<true>>);
REQUIRE(magic_enum::detail::is_valid_enum_v<Forward2, as_flags<false>>);
REQUIRE(magic_enum::detail::is_valid_enum_v<Forward3, as_flags<true>>);
REQUIRE(magic_enum::detail::is_valid_enum_v<Forward3, as_flags<false>>);
REQUIRE(magic_enum::detail::is_valid_enum_v<Forward4, as_flags<true>>);
REQUIRE(magic_enum::detail::is_valid_enum_v<Forward4, as_flags<false>>);
REQUIRE(magic_enum::detail::is_valid_enum_v<Empty1, as_flags<true>>);
REQUIRE(magic_enum::detail::is_valid_enum_v<Empty1, as_flags<false>>);
REQUIRE(magic_enum::detail::is_valid_enum_v<Empty2, as_flags<true>>);
REQUIRE(magic_enum::detail::is_valid_enum_v<Empty2, as_flags<false>>);
REQUIRE(magic_enum::detail::is_valid_enum_v<Empty3, as_flags<true>>);
REQUIRE(magic_enum::detail::is_valid_enum_v<Empty3, as_flags<false>>);
REQUIRE(magic_enum::detail::is_valid_enum_v<Empty4, as_flags<true>>);
REQUIRE(magic_enum::detail::is_valid_enum_v<Empty4, as_flags<false>>);
}
5 changes: 0 additions & 5 deletions test/test_containers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,6 @@ TEST_CASE("containers_array") {
REQUIRE(std::make_pair(colors[1], color_rgb_container_int[colors[1]]) == std::make_pair<Color, std::uint8_t>(Color::GREEN, 2U));
REQUIRE(std::make_pair(colors[2], color_rgb_container_int[colors[2]]) == std::make_pair<Color, std::uint8_t>(Color::BLUE, 4U));

auto empty = magic_enum::containers::array<Empty, std::nullptr_t>();
REQUIRE(empty.empty());
REQUIRE(empty.size() == 0);
REQUIRE(magic_enum::enum_count<Empty>() == empty.size());

auto color_rgb_container = magic_enum::containers::array<Color, RGB>();
REQUIRE_FALSE(color_rgb_container.empty());
REQUIRE(color_rgb_container.size() == 3);
Expand Down

0 comments on commit e09f257

Please sign in to comment.