From 8f6c9905fdf901c27093f01b16b8a756073b1ea6 Mon Sep 17 00:00:00 2001 From: Daniil Goncharov Date: Wed, 24 May 2023 19:05:20 +0400 Subject: [PATCH] Enable wchar_t as string_view value_type (#272) --- include/magic_enum.hpp | 46 +++++++--- include/magic_enum_containers.hpp | 14 +-- test/CMakeLists.txt | 4 + test/test.cpp | 1 - test/test_wchar_t.cpp | 147 ++++++++++++++++++++++++++++++ 5 files changed, 190 insertions(+), 22 deletions(-) create mode 100644 test/test_wchar_t.cpp diff --git a/include/magic_enum.hpp b/include/magic_enum.hpp index bd4ff69..6fde1f2 100644 --- a/include/magic_enum.hpp +++ b/include/magic_enum.hpp @@ -134,6 +134,23 @@ MAGIC_ENUM_USING_ALIAS_STRING using std::string; #endif +using char_type = string_view::value_type; +static_assert(std::is_same_v, "magic_enum::customize requires same string_view::value_type and string::value_type"); +static_assert([] { + if constexpr (std::is_same_v) { + constexpr const char c[] = "abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + constexpr const wchar_t wc[] = L"abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + static_assert(std::size(c) == std::size(wc), "magic_enum::customize identifier characters are multichars in wchar_t."); + + for (std::size_t i = 0; i < std::size(c); ++i) { + if (c[i] != wc[i]) { + return false; + } + } + } + return true; +} (), "magic_enum::customize wchar_t is not compatible with ASCII."); + namespace customize { // Enum value must be in range [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX]. By default MAGIC_ENUM_RANGE_MIN = -128, MAGIC_ENUM_RANGE_MAX = 128. @@ -163,7 +180,7 @@ enum class customize_tag { class customize_t : public std::pair { public: constexpr customize_t(string_view srt) : std::pair{detail::customize_tag::custom_tag, srt} {} - constexpr customize_t(const char* srt) : customize_t{string_view{srt}} {} + constexpr customize_t(const char_type* srt) : customize_t{string_view{srt}} {} constexpr customize_t(detail::customize_tag tag) : std::pair{tag, string_view{}} { assert(tag != detail::customize_tag::custom_tag); } @@ -186,8 +203,6 @@ constexpr customize_t enum_type_name() noexcept { return default_tag; } -static_assert(std::is_same_v, "magic_enum::customize requires same string_view::value_type and string::value_type"); - } // namespace magic_enum::customize namespace detail { @@ -244,7 +259,7 @@ class static_str { assert(str.size() == N); } - constexpr const char* data() const noexcept { return chars_; } + constexpr const char_type* data() const noexcept { return chars_; } constexpr std::uint16_t size() const noexcept { return N; } @@ -252,9 +267,12 @@ class static_str { private: template - constexpr static_str(const char* str, std::integer_sequence) noexcept : chars_{str[I]..., '\0'} {} + constexpr static_str(const char* str, std::integer_sequence) noexcept : chars_{static_cast(str[I])..., '\0'} {} - char chars_[static_cast(N) + 1]; + template + constexpr static_str(string_view str, std::integer_sequence) noexcept : chars_{str[I]..., '\0'} {} + + char_type chars_[static_cast(N) + 1]; }; template <> @@ -266,7 +284,7 @@ class static_str<0> { constexpr explicit static_str(string_view) noexcept {} - constexpr const char* data() const noexcept { return nullptr; } + constexpr const char_type* data() const noexcept { return nullptr; } constexpr std::uint16_t size() const noexcept { return 0; } @@ -275,18 +293,18 @@ class static_str<0> { template > class case_insensitive { - static constexpr char to_lower(char c) noexcept { - return (c >= 'A' && c <= 'Z') ? static_cast(c + ('a' - 'A')) : c; + static constexpr char_type to_lower(char_type c) noexcept { + return (c >= 'A' && c <= 'Z') ? static_cast(c + ('a' - 'A')) : c; } public: template - constexpr auto operator()(L lhs,R rhs) const noexcept -> std::enable_if_t, char> && std::is_same_v, char>, bool> { + constexpr auto operator()(L lhs,R rhs) const noexcept -> std::enable_if_t, char_type> && std::is_same_v, char_type>, bool> { return Op{}(to_lower(lhs), to_lower(rhs)); } }; -constexpr std::size_t find(string_view str, char c) noexcept { +constexpr std::size_t find(string_view str, char_type c) noexcept { #if defined(__clang__) && __clang_major__ < 9 && defined(__GLIBCXX__) || defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__) // https://stackoverflow.com/questions/56484834/constexpr-stdstring-viewfind-last-of-doesnt-work-on-clang-8-with-libstdc // https://developercommunity.visualstudio.com/content/problem/360432/vs20178-regression-c-failed-in-test.html @@ -317,7 +335,7 @@ constexpr bool is_default_predicate() noexcept { template constexpr bool is_nothrow_invocable() { return is_default_predicate() || - std::is_nothrow_invocable_r_v; + std::is_nothrow_invocable_r_v; } template @@ -785,7 +803,7 @@ struct enable_if_enum { }; template , typename D = std::decay_t> -using enable_if_t = typename enable_if_enum && std::is_invocable_r_v, R>::type; +using enable_if_t = typename enable_if_enum && std::is_invocable_r_v, R>::type; template >, int> = 0> using enum_concept = T; @@ -1225,7 +1243,7 @@ template // Returns name from enum-flags value. // If enum-flags value does not have name or value out of range, returns empty string. template -[[nodiscard]] auto enum_flags_name(E value, char sep = '|') -> detail::enable_if_t { +[[nodiscard]] auto enum_flags_name(E value, char_type sep = '|') -> detail::enable_if_t { using D = std::decay_t; using U = underlying_type_t; constexpr auto S = detail::enum_subtype::flags; diff --git a/include/magic_enum_containers.hpp b/include/magic_enum_containers.hpp index 17614e6..cbe9397 100644 --- a/include/magic_enum_containers.hpp +++ b/include/magic_enum_containers.hpp @@ -190,7 +190,7 @@ struct name_sort_impl { struct FullCmp : S {}; template - struct FullCmp && std::is_invocable_v>> { + struct FullCmp && std::is_invocable_v>> { [[nodiscard]] constexpr inline bool operator()(string_view s1, string_view s2) const noexcept { return lexicographical_compare(s1, s2); } }; @@ -546,7 +546,7 @@ class bitset { } } - constexpr explicit bitset(detail::raw_access_t, string_view sv, string_view::size_type pos = 0, string_view::size_type n = string_view::npos, char zero = '0', char one = '1') + constexpr explicit bitset(detail::raw_access_t, string_view sv, string_view::size_type pos = 0, string_view::size_type n = string_view::npos, char_type zero = '0', char_type one = '1') : a{{}} { std::size_t i{}; for (auto c : sv.substr(pos, n)) { @@ -562,8 +562,8 @@ class bitset { } } - constexpr explicit bitset(detail::raw_access_t, const char* str, std::size_t n = ~std::size_t{}, char zero = '0', char one = '1') - : bitset(std::string_view{str, (std::min)(std::char_traits::length(str), n)}, 0, n, zero, one) {} + constexpr explicit bitset(detail::raw_access_t, const char_type* str, std::size_t n = ~std::size_t{}, char_type zero = '0', char_type one = '1') + : bitset(std::string_view{str, (std::min)(std::char_traits::length(str), n)}, 0, n, zero, one) {} constexpr bitset(std::initializer_list starters) : a{{}} { if constexpr (magic_enum::detail::subtype_v == magic_enum::detail::enum_subtype::flags) { @@ -591,7 +591,7 @@ class bitset { } template > - constexpr explicit bitset(string_view sv, Cmp&& cmp = {}, char sep = '|') { + constexpr explicit bitset(string_view sv, Cmp&& cmp = {}, char_type sep = '|') { for (std::size_t to{}; (to = magic_enum::detail::find(sv, sep)) != string_view::npos; sv.remove_prefix(to + 1)) { if (auto v = enum_cast(sv.substr(0, to), cmp)) { set(v); @@ -756,7 +756,7 @@ class bitset { return res; } - [[nodiscard]] string to_string(char sep = '|') const { + [[nodiscard]] string to_string(char_type sep = '|') const { string name; for (auto& e : enum_values()) { @@ -771,7 +771,7 @@ class bitset { return name; } - [[nodiscard]] string to_string(detail::raw_access_t, char zero = '0', char one = '1') const { + [[nodiscard]] string to_string(detail::raw_access_t, char_type zero = '0', char_type one = '1') const { string name; name.reserve(size()); for (std::size_t i{}; i < size(); ++i) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index aabf550..8c20762 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -38,6 +38,7 @@ make_test(test.cpp test-cpp17 c++17) make_test(test_flags.cpp test_flags-cpp17 c++17) make_test(test_aliases.cpp test_aliases-cpp17 c++17) make_test(test_containers.cpp test_containers-cpp17 c++17) +make_test(test_wchar_t.cpp test_wchar_t-cpp17 c++17) if(MAGIC_ENUM_OPT_ENABLE_NONASCII) make_test(test_nonascii.cpp test_nonascii-cpp17 c++17) @@ -48,6 +49,7 @@ if(HAS_CPP20_FLAG) make_test(test_flags.cpp test_flags-cpp20 c++20) make_test(test_aliases.cpp test_aliases-cpp20 c++20) make_test(test_containers.cpp test_containers-cpp20 c++20) + make_test(test_wchar_t.cpp test_wchar_t-cpp20 c++20) if(MAGIC_ENUM_OPT_ENABLE_NONASCII) make_test(test_nonascii.cpp test_nonascii-cpp20 c++20) endif() @@ -58,6 +60,7 @@ if(HAS_CPP23_FLAG) make_test(test_flags.cpp test_flags-cpp23 c++23) make_test(test_aliases.cpp test_aliases-cpp23 c++23) make_test(test_containers.cpp test_containers-cpp23 c++23) + make_test(test_wchar_t.cpp test_wchar_t-cpp23 c++23) if(MAGIC_ENUM_OPT_ENABLE_NONASCII) make_test(test_nonascii.cpp test_nonascii-cpp23 c++23) endif() @@ -68,6 +71,7 @@ if(HAS_CPPLATEST_FLAG) make_test(test_flags.cpp test_flags-cpplatest c++latest) make_test(test_aliases.cpp test_aliases-cpplatest c++latest) make_test(test_containers.cpp test_containers-cpplatest c++latest) + make_test(test_wchar_t.cpp test_wchar_t-cpplatest c++latest) if(MAGIC_ENUM_OPT_ENABLE_NONASCII) make_test(test_nonascii.cpp test_nonascii-cpplatest c++latest) endif() diff --git a/test/test.cpp b/test/test.cpp index c6cb96e..c87da78 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -447,7 +447,6 @@ TEST_CASE("enum_count") { constexpr auto s6 = enum_count(); REQUIRE(s6 == 2); - } enum lt1 { s1, loooooooooooooooooooong1 }; diff --git a/test/test_wchar_t.cpp b/test/test_wchar_t.cpp new file mode 100644 index 0000000..4f13c6a --- /dev/null +++ b/test/test_wchar_t.cpp @@ -0,0 +1,147 @@ +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2023 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#define CATCH_CONFIG_MAIN +#include + +#define MAGIC_ENUM_USING_ALIAS_STRING_VIEW using string_view = std::wstring_view; +#define MAGIC_ENUM_USING_ALIAS_STRING using string = std::wstring; +#include +#include + +#include +#include +#include +#include + +enum class Color { RED = -12, GREEN = 7, BLUE = 15 }; +template <> +constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name(Color value) noexcept { + switch (value) { + case Color::RED: + return L"red"; + default: + return default_tag; + } +} + +using namespace magic_enum; + +static_assert(is_magic_enum_supported, "magic_enum: Unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility)."); + +TEST_CASE("enum_cast") { + SECTION("string") { + constexpr auto cr = enum_cast(L"red"); + REQUIRE(cr.value() == Color::RED); + REQUIRE(enum_cast(L"GREEN").value() == Color::GREEN); + REQUIRE(enum_cast(L"blue", [](wchar_t lhs, wchar_t rhs) { return std::tolower(lhs) == std::tolower(rhs); }).value() == Color::BLUE); + REQUIRE_FALSE(enum_cast(L"None").has_value()); + } + + SECTION("integer") { + Color cm[3] = {Color::RED, Color::GREEN, Color::BLUE}; + constexpr auto cr = enum_cast(-12); + REQUIRE(cr.value() == Color::RED); + REQUIRE(enum_cast(7).value() == Color::GREEN); + REQUIRE(enum_cast(static_cast(cm[2])).value() == Color::BLUE); + REQUIRE_FALSE(enum_cast(0).has_value()); + } +} + +TEST_CASE("enum_values") { + REQUIRE(std::is_same_v()), const std::array&>); + + constexpr auto& s1 = enum_values(); + REQUIRE(s1 == std::array{{Color::RED, Color::GREEN, Color::BLUE}}); +} + +TEST_CASE("enum_count") { + constexpr auto s1 = enum_count(); + REQUIRE(s1 == 3); +} + +TEST_CASE("enum_name") { + SECTION("automatic storage") { + constexpr Color cr = Color::RED; + constexpr auto cr_name = enum_name(cr); + Color cm[3] = {Color::RED, Color::GREEN, Color::BLUE}; + Color cb = Color::BLUE; + REQUIRE(cr_name == L"red"); + REQUIRE(enum_name(cb) == L"BLUE"); + REQUIRE(enum_name>(cm[1]) == L"GREEN"); + REQUIRE(enum_name>(cm[1]) == L"GREEN"); + REQUIRE(enum_name>(static_cast(0)).empty()); + } + + SECTION("static storage") { + constexpr Color cr = Color::RED; + constexpr auto cr_name = enum_name(); + constexpr Color cm[3] = {Color::RED, Color::GREEN, Color::BLUE}; + REQUIRE(cr_name == L"red"); + REQUIRE(enum_name() == L"BLUE"); + REQUIRE(enum_name() == L"GREEN"); + } +} + +TEST_CASE("enum_names") { + REQUIRE(std::is_same_v()), const std::array&>); + + constexpr auto& s1 = enum_names(); + REQUIRE(s1 == std::array{{L"red", L"GREEN", L"BLUE"}}); +} + +TEST_CASE("enum_entries") { + REQUIRE(std::is_same_v()), const std::array, 3>&>); + + constexpr auto& s1 = enum_entries(); + REQUIRE(s1 == std::array, 3>{{{Color::RED, L"red"}, {Color::GREEN, L"GREEN"}, {Color::BLUE, L"BLUE"}}}); +} + +TEST_CASE("ostream_operators") { + auto test_ostream = [](auto e, std::wstring name) { + using namespace magic_enum::ostream_operators; + std::wstringstream ss; + ss << e; + REQUIRE(ss); + REQUIRE(ss.str() == name); + }; + + test_ostream(std::make_optional(Color::RED), L"red"); + test_ostream(Color::GREEN, L"GREEN"); + test_ostream(Color::BLUE, L"BLUE"); + test_ostream(static_cast(0), L"0"); + test_ostream(std::make_optional(static_cast(0)), L"0"); +} + +TEST_CASE("istream_operators") { + auto test_istream = [](const auto e, std::wstring name) { + using namespace magic_enum::istream_operators; + std::wistringstream ss(name); + std::decay_t v; + ss >> v; + REQUIRE(ss); + REQUIRE(v == e); + }; + + test_istream(Color::GREEN, L"GREEN"); + test_istream(Color::BLUE, L"BLUE"); +}