Skip to content

Commit 8312153

Browse files
committed
fix string lookup for bool-backed enums
1 parent e71d33d commit 8312153

9 files changed

Lines changed: 173 additions & 10 deletions

File tree

‎include/magic_enum/magic_enum.hpp‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,9 @@ constexpr std::size_t find(string_view str, char_type c) noexcept {
475475
}
476476
}
477477

478+
template <typename T>
479+
inline constexpr bool is_name_input_v = std::is_convertible_v<T, string_view>;
480+
478481
template <typename BinaryPredicate>
479482
inline constexpr bool is_default_predicate_v = std::is_same_v<std::decay_t<BinaryPredicate>, std::equal_to<string_view::value_type>> || std::is_same_v<std::decay_t<BinaryPredicate>, std::equal_to<>>;
480483

@@ -1908,8 +1911,9 @@ template <typename E, detail::enum_subtype S = detail::subtype_v<E>>
19081911

19091912
// Returns enum value from name.
19101913
// Returns optional with enum value.
1911-
template <typename E, detail::enum_subtype S = detail::subtype_v<E>, typename BinaryPredicate = std::equal_to<>>
1912-
[[nodiscard]] constexpr auto enum_cast(string_view value, [[maybe_unused]] BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable_v<BinaryPredicate>) -> detail::enable_if_t<E, optional<std::decay_t<E>>, BinaryPredicate> {
1914+
template <typename E, detail::enum_subtype S = detail::subtype_v<E>, typename BinaryPredicate = std::equal_to<>, typename String = string_view, std::enable_if_t<detail::is_name_input_v<String>, int> = 0>
1915+
[[nodiscard]] constexpr auto enum_cast(String&& name, [[maybe_unused]] BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable_v<BinaryPredicate> && std::is_nothrow_constructible_v<string_view, String>) -> detail::enable_if_t<E, optional<std::decay_t<E>>, BinaryPredicate> {
1916+
string_view value(std::forward<String>(name));
19131917
using D = std::decay_t<E>;
19141918
static_assert(detail::is_reflected_v<D, S>, "magic_enum requires enum implementation and valid max and min.");
19151919

@@ -1958,11 +1962,11 @@ template <typename E, detail::enum_subtype S = detail::subtype_v<E>>
19581962
}
19591963

19601964
// Returns true if enum contains enumerator with specified name.
1961-
template <typename E, detail::enum_subtype S = detail::subtype_v<E>, typename BinaryPredicate = std::equal_to<>>
1962-
[[nodiscard]] constexpr auto enum_contains(string_view value, BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable_v<BinaryPredicate>) -> detail::enable_if_t<E, bool, BinaryPredicate> {
1965+
template <typename E, detail::enum_subtype S = detail::subtype_v<E>, typename BinaryPredicate = std::equal_to<>, typename String = string_view, std::enable_if_t<detail::is_name_input_v<String>, int> = 0>
1966+
[[nodiscard]] constexpr auto enum_contains(String&& value, BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable_v<BinaryPredicate> && std::is_nothrow_constructible_v<string_view, String>) -> detail::enable_if_t<E, bool, BinaryPredicate> {
19631967
using D = std::decay_t<E>;
19641968

1965-
return static_cast<bool>(enum_cast<D, S, BinaryPredicate&>(value, p));
1969+
return static_cast<bool>(enum_cast<D, S, BinaryPredicate&>(std::forward<String>(value), p));
19661970
}
19671971

19681972
// Returns true if enum integer value can be reflected.

‎include/magic_enum/magic_enum_flags.hpp‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,9 @@ template <typename E>
108108

109109
// Returns flag enum value from name.
110110
// Returns optional containing flag enum value.
111-
template <typename E, typename BinaryPredicate = std::equal_to<>>
112-
[[nodiscard]] constexpr auto enum_flags_cast(string_view value, [[maybe_unused]] char_type sep = char_type{'|'}, [[maybe_unused]] BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable_v<BinaryPredicate>) -> detail::enable_if_t<E, optional<std::decay_t<E>>, BinaryPredicate> {
111+
template <typename E, typename BinaryPredicate = std::equal_to<>, typename String = string_view, std::enable_if_t<detail::is_name_input_v<String>, int> = 0>
112+
[[nodiscard]] constexpr auto enum_flags_cast(String&& name, [[maybe_unused]] char_type sep = char_type{'|'}, [[maybe_unused]] BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable_v<BinaryPredicate> && std::is_nothrow_constructible_v<string_view, String>) -> detail::enable_if_t<E, optional<std::decay_t<E>>, BinaryPredicate> {
113+
string_view value(std::forward<String>(name));
113114
using D = std::decay_t<E>;
114115
using U = detail::make_unsigned_t<underlying_type_t<D>>;
115116
constexpr auto S = detail::enum_subtype::flags;
@@ -166,11 +167,11 @@ template <typename E>
166167
}
167168

168169
// Returns true if flag enum contains enumerator with specified name.
169-
template <typename E, typename BinaryPredicate = std::equal_to<>>
170-
[[nodiscard]] constexpr auto enum_flags_contains(string_view value, char_type sep = char_type{'|'}, BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable_v<BinaryPredicate>) -> detail::enable_if_t<E, bool, BinaryPredicate> {
170+
template <typename E, typename BinaryPredicate = std::equal_to<>, typename String = string_view, std::enable_if_t<detail::is_name_input_v<String>, int> = 0>
171+
[[nodiscard]] constexpr auto enum_flags_contains(String&& value, char_type sep = char_type{'|'}, BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable_v<BinaryPredicate> && std::is_nothrow_constructible_v<string_view, String>) -> detail::enable_if_t<E, bool, BinaryPredicate> {
171172
using D = std::decay_t<E>;
172173

173-
return static_cast<bool>(enum_flags_cast<D, BinaryPredicate&>(value, sep, p));
174+
return static_cast<bool>(enum_flags_cast<D, BinaryPredicate&>(std::forward<String>(value), sep, p));
174175
}
175176

176177
// Returns true if `flags` contains `flag`.

‎test/aliases.hpp‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ struct MyString {
4040
std::string str;
4141
};
4242

43+
struct DirectNameInput;
44+
4345
struct MyStringView {
4446
using value_type = char; // required
4547
static constexpr auto npos = std::string_view::npos; // required
@@ -60,6 +62,7 @@ struct MyStringView {
6062
friend constexpr bool operator==(MyStringView lhs, MyStringView rhs); // required
6163

6264
constexpr MyStringView(const char* cstr) : str{ cstr } {}
65+
explicit constexpr MyStringView(const DirectNameInput&) noexcept : str{"RED"} {}
6366
constexpr int compare(const char* s) const { return str.compare(s); }
6467

6568
private:

‎test/installed_module/main.cpp‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@ enum class Color { RED, GREEN, BLUE };
2424
enum class Directions { LEFT = 1, RIGHT = 2 };
2525
enum class ReflectionRange { Low = -1000, High = 1000 };
2626

27+
enum class BoolValue : bool { FALSE_VALUE = false, TRUE_VALUE = true };
28+
static_assert(magic_enum::enum_cast<BoolValue>("FALSE_VALUE").value() == BoolValue::FALSE_VALUE);
29+
static_assert(magic_enum::enum_cast<BoolValue>("TRUE_VALUE").value() == BoolValue::TRUE_VALUE);
30+
static_assert(!magic_enum::enum_cast<BoolValue>("INVALID").has_value());
31+
static_assert(!magic_enum::enum_cast<BoolValue>("").has_value());
32+
static_assert(!magic_enum::enum_contains<BoolValue>("INVALID"));
33+
static_assert(magic_enum::enum_cast<BoolValue>(false).value() == BoolValue::FALSE_VALUE);
34+
static_assert(magic_enum::enum_cast<BoolValue>(true).value() == BoolValue::TRUE_VALUE);
35+
2736
template <>
2837
struct magic_enum::customize::enum_range<Directions> {
2938
static constexpr bool is_flags = true;

‎test/test.cpp‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,75 @@ struct RefQualifiedPredicate {
253253
constexpr bool operator()(char lhs, char rhs) && noexcept { return lhs == rhs; }
254254
};
255255

256+
TEST_CASE("bool enum text lookup does not convert pointers to true") {
257+
static_assert(enum_cast<BoolTest>("Yay") == BoolTest::Yay);
258+
static_assert(enum_cast<const BoolTest&>("Nay") == BoolTest::Nay);
259+
static_assert(!enum_cast<BoolTest>("INVALID").has_value());
260+
static_assert(!enum_cast<BoolTest>("").has_value());
261+
static_assert(enum_contains<BoolTest>("Yay"));
262+
static_assert(!enum_contains<BoolTest>("INVALID"));
263+
static_assert(!enum_contains<BoolTest>(""));
264+
static_assert(enum_cast<BoolTest, as_common<>>("Yay") == BoolTest::Yay);
265+
static_assert(enum_cast<BoolTest>("yay", case_insensitive) == BoolTest::Yay);
266+
static_assert(enum_cast<BoolTest>("Yay", ConstReferencePredicate{}) == BoolTest::Yay);
267+
static_assert(enum_contains<BoolTest>("Yay", ConstReferencePredicate{}));
268+
static_assert(noexcept(enum_cast<BoolTest>("Yay")));
269+
static_assert(noexcept(enum_contains<BoolTest>("Yay")));
270+
static_assert(!noexcept(enum_cast<BoolTest>("Yay", ThrowingReferencePredicate{})));
271+
272+
constexpr const char* false_name = "Yay";
273+
constexpr const char* invalid_name = "INVALID";
274+
static_assert(enum_cast<BoolTest>(false_name) == BoolTest::Yay);
275+
static_assert(!enum_cast<BoolTest>(invalid_name).has_value());
276+
static_assert(enum_cast<BoolTest>(string_view{"Yay-extra", 3}) == BoolTest::Yay);
277+
static_assert(enum_cast<BoolTest>({"Yay-extra", 3}) == BoolTest::Yay);
278+
279+
for (const char* name : {"Yay", "Nay", "INVALID", "", "yay", "Yay-extra"}) {
280+
const auto expected = enum_cast<BoolTest>(string_view{name});
281+
CHECK(enum_cast<BoolTest>(name) == expected);
282+
CHECK(enum_contains<BoolTest>(name) == expected.has_value());
283+
const std::string owned{name};
284+
CHECK(enum_cast<BoolTest>(owned) == expected);
285+
CHECK(enum_cast<BoolTest>(std::string{name}) == expected);
286+
CHECK(enum_contains<BoolTest>(owned) == expected.has_value());
287+
}
288+
char mutable_name[] = "Yay";
289+
char* mutable_pointer = mutable_name;
290+
CHECK(enum_cast<BoolTest>(mutable_name) == BoolTest::Yay);
291+
CHECK(enum_cast<BoolTest>(mutable_pointer) == BoolTest::Yay);
292+
CHECK(enum_contains<BoolTest>(mutable_pointer));
293+
CHECK(enum_cast<BoolTest>(false) == BoolTest::Yay);
294+
CHECK(enum_cast<BoolTest>(true) == BoolTest::Nay);
295+
CHECK(enum_cast<BoolTest>(0) == BoolTest::Yay);
296+
CHECK(enum_cast<BoolTest>(1) == BoolTest::Nay);
297+
REQUIRE_THROWS_AS(static_cast<void>(enum_cast<BoolTest>("Yay", ThrowingReferencePredicate{})), int);
298+
299+
struct BorrowedName {
300+
BorrowedName() = default;
301+
BorrowedName(const BorrowedName&) = delete;
302+
operator string_view() & noexcept { return "Yay"; }
303+
operator bool() const noexcept { return true; }
304+
};
305+
BorrowedName borrowed;
306+
CHECK(enum_cast<BoolTest>(borrowed) == BoolTest::Yay);
307+
CHECK(enum_contains<BoolTest>(borrowed));
308+
static_assert(noexcept(enum_cast<BoolTest>(borrowed)));
309+
310+
struct TemporaryName {
311+
operator string_view() && noexcept { return "Yay"; }
312+
};
313+
CHECK(enum_cast<BoolTest>(TemporaryName{}) == BoolTest::Yay);
314+
CHECK(enum_contains<BoolTest>(TemporaryName{}));
315+
316+
struct ThrowingName {
317+
operator string_view() const { throw 42; }
318+
};
319+
static_assert(!noexcept(enum_cast<BoolTest>(ThrowingName{})));
320+
static_assert(!noexcept(enum_contains<BoolTest>(ThrowingName{})));
321+
CHECK_THROWS_AS(static_cast<void>(enum_cast<BoolTest>(ThrowingName{})), int);
322+
CHECK_THROWS_AS(static_cast<void>(enum_contains<BoolTest>(ThrowingName{})), int);
323+
}
324+
256325
TEST_CASE("enum_cast") {
257326
SUBCASE("string") {
258327
constexpr auto cr = enum_cast<Color>("red");

‎test/test_aliases.cpp‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,41 @@ struct magic_enum::customize::enum_range<Color> {
2424
static constexpr bool is_flags = true;
2525
};
2626

27+
TEST_CASE("bool enum lookup with custom string_view and optional") {
28+
enum class Binary : bool { FALSE_VALUE = false, TRUE_VALUE = true };
29+
static_assert(enum_cast<Binary>("FALSE_VALUE").value() == Binary::FALSE_VALUE);
30+
static_assert(enum_cast<Binary>("TRUE_VALUE").value() == Binary::TRUE_VALUE);
31+
static_assert(!enum_cast<Binary>("INVALID").has_value());
32+
static_assert(!enum_cast<Binary>("").has_value());
33+
static_assert(!enum_contains<Binary>("INVALID"));
34+
const char* name = "FALSE_VALUE";
35+
CHECK(enum_cast<Binary>(name).value() == Binary::FALSE_VALUE);
36+
CHECK(enum_cast<Binary>(MyStringView{name}).value() == Binary::FALSE_VALUE);
37+
CHECK(enum_contains<Binary>(name));
38+
}
39+
40+
struct DirectNameInput {
41+
operator MyStringView() const { throw 42; }
42+
};
43+
44+
TEST_CASE("name lookup construction matches noexcept with custom string_view") {
45+
DirectNameInput name;
46+
static_assert(detail::is_name_input_v<DirectNameInput&>);
47+
static_assert(std::is_nothrow_constructible_v<string_view, DirectNameInput&>);
48+
static_assert(noexcept(enum_cast<Color>(name)));
49+
static_assert(noexcept(enum_contains<Color>(name)));
50+
static_assert(noexcept(enum_flags_cast<Color>(name)));
51+
static_assert(noexcept(enum_flags_contains<Color>(name)));
52+
53+
CHECK(enum_cast<Color>(name).value() == Color::RED);
54+
CHECK(enum_contains<Color>(name));
55+
CHECK(enum_flags_cast<Color>(name).value() == Color::RED);
56+
CHECK(enum_flags_contains<Color>(name));
57+
58+
const auto copy_initialize = [](string_view) noexcept {};
59+
CHECK_THROWS_AS(copy_initialize(name), int);
60+
}
61+
2762
TEST_CASE("optional") {
2863
constexpr auto cr = enum_cast<Color>("RED");
2964
REQUIRE(cr.has_value());

‎test/test_flags.cpp‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,26 @@ TEST_CASE("enum_reflected") {
147147
REQUIRE_FALSE(enum_contains<Directions>(std::uint64_t{1} << 62));
148148
}
149149

150+
TEST_CASE("bool flag text lookup does not convert pointers to true") {
151+
static_assert(enum_cast<BoolFlags>("Enabled") == BoolFlags::Enabled);
152+
static_assert(!enum_cast<BoolFlags>("Disabled").has_value());
153+
static_assert(!enum_cast<BoolFlags>("INVALID").has_value());
154+
static_assert(!enum_cast<BoolFlags>("").has_value());
155+
static_assert(enum_cast<BoolFlags, as_common<>>("Disabled") == BoolFlags::Disabled);
156+
static_assert(!enum_contains<BoolFlags>("Disabled"));
157+
static_assert(!enum_contains<BoolFlags>("INVALID"));
158+
static_assert(enum_flags_cast<BoolFlags>("Enabled") == BoolFlags::Enabled);
159+
static_assert(!enum_flags_cast<BoolFlags>("Disabled").has_value());
160+
static_assert(!enum_flags_cast<BoolFlags>("INVALID").has_value());
161+
static_assert(!enum_flags_cast<BoolFlags>("").has_value());
162+
static_assert(!enum_flags_contains<BoolFlags>("INVALID"));
163+
static_assert(!enum_flags_contains<BoolFlags>(""));
164+
const char* invalid = "INVALID";
165+
CHECK_FALSE(enum_flags_cast<BoolFlags>(invalid).has_value());
166+
CHECK_FALSE(enum_flags_contains<BoolFlags>(invalid));
167+
CHECK(enum_flags_cast<BoolFlags>("enabled", '|', case_insensitive) == BoolFlags::Enabled);
168+
}
169+
150170
TEST_CASE("bool flags use the sole non-zero bit") {
151171
constexpr auto enabled_name = string_view{"Enabled"};
152172
constexpr auto common_values = enum_values<BoolFlags, as_common<>>();

‎test/test_module.cpp‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ enum class Color { RED, GREEN, BLUE };
2525
enum class Directions { LEFT = 1, RIGHT = 2 };
2626
enum class ReflectionRange { Low = -1000, High = 1000 };
2727

28+
enum class BoolValue : bool { FALSE_VALUE = false, TRUE_VALUE = true };
29+
static_assert(magic_enum::enum_cast<BoolValue>("FALSE_VALUE").value() == BoolValue::FALSE_VALUE);
30+
static_assert(magic_enum::enum_cast<BoolValue>("TRUE_VALUE").value() == BoolValue::TRUE_VALUE);
31+
static_assert(!magic_enum::enum_cast<BoolValue>("INVALID").has_value());
32+
static_assert(!magic_enum::enum_cast<BoolValue>("").has_value());
33+
static_assert(!magic_enum::enum_contains<BoolValue>("INVALID"));
34+
static_assert(magic_enum::enum_cast<BoolValue>(false).value() == BoolValue::FALSE_VALUE);
35+
static_assert(magic_enum::enum_cast<BoolValue>(true).value() == BoolValue::TRUE_VALUE);
36+
2837
enum class ReverseOrder { Low, High };
2938

3039
constexpr bool operator<(ReverseOrder lhs, ReverseOrder rhs) noexcept {

‎test/test_wchar_t.cpp‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,19 @@ using namespace magic_enum_tests;
3535

3636
static_assert(is_magic_enum_supported, "magic_enum: Unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility).");
3737

38+
TEST_CASE("bool enum wide string lookup") {
39+
enum class Binary : bool { FALSE_VALUE = false, TRUE_VALUE = true };
40+
static_assert(enum_cast<Binary>(L"FALSE_VALUE") == Binary::FALSE_VALUE);
41+
static_assert(enum_cast<Binary>(L"TRUE_VALUE") == Binary::TRUE_VALUE);
42+
static_assert(!enum_cast<Binary>(L"INVALID").has_value());
43+
static_assert(!enum_cast<Binary>(L"").has_value());
44+
static_assert(!enum_contains<Binary>(L"INVALID"));
45+
static_assert(enum_cast<Binary>(L"false_value", case_insensitive) == Binary::FALSE_VALUE);
46+
const wchar_t* name = L"FALSE_VALUE";
47+
CHECK(enum_cast<Binary>(name) == Binary::FALSE_VALUE);
48+
CHECK(enum_cast<Binary>(std::wstring{name}) == Binary::FALSE_VALUE);
49+
}
50+
3851
TEST_CASE("enum_cast") {
3952
SUBCASE("string") {
4053
constexpr auto cr = enum_cast<Color>(L"red");

0 commit comments

Comments
 (0)