Neargye / magic_enum

Static reflection for enums (to string, from string, iteration) for modern C++, work with any enum type without any macro or boilerplate code
MIT License
4.76k stars 422 forks source link

Using multiple string types #340

Closed Nerixyz closed 5 months ago

Nerixyz commented 5 months ago

Hi, thanks for the great library!

We're currently using magic_enum without any custom string type. Since we're using Qt, a lot of our strings are Utf-16, so we'd want to change the string type in these cases with MAGIC_ENUM_USING_ALIAS_STRING{,_VIEW} to a u16string, however, other parts are using Utf-8. With templated code defined in headers, it's not really easy to ensure only one type is included (utf-8 or utf-16) or some files might want to use both.

Would it be possible to allow users to modify the namespace of magic_enum? I suppose it's not that easy with the include-guards, but it would be nice to be able to have both magic_enum8 and magic_enum16 for example.

Neargye commented 5 months ago

I would rather suggest you make your own namespaces with the necessary overloads

This is a quick example

namespace magic_enum8 {
  template <typename E>
  std::u8string enum_name(E v) {
    std::u8string str;
    auto n = magic_enum::enum_name<E>(v);
    str.reserve(n.size());
    for (auto c : n)
      str.append(1, c);
    return str;
  }
}

namespace magic_enum16 {
  template <typename E>
  std::u16string enum_name(E v) {
    std::u16string str;
    auto n = magic_enum::enum_name<E>(v);
    str.reserve(n.size());
    for (auto c : n)
      str.append(1, c);
    return str;
  }
}
Nerixyz commented 5 months ago

Thank you, I made a small adaption for Qt's QString and QStringView. It only supports Latin1 characters, but that shouldn't really be a limitation in practice. It's mostly constexpr except for getting QStrings. Should work on both Qt 6 and 5 (only tested on 6 right now).

Adaptation We have different naming conventions, but that shouldn't be an issue. It's using `is_nothrow_invocable`, `enum_subtype`, `subtype_v`, and `enable_if_t` from `magic_enum::detail`. ```cpp #include #include #include namespace qmagicenum { namespace detail { template consteval QStringView fromArray(const std::array &arr) { return QStringView{arr.data(), static_cast(N - 1)}; } // Only the latin1 subset may be used right now, since it's easily convertible template consteval bool isLatin1(std::string_view maybe) { for (std::size_t i = 0; i < N; i++) { if (maybe[i] < 0x20 || maybe[i] > 0x7e) { return false; } } return true; } template inline constexpr bool eq( QStringView a, QStringView b, [[maybe_unused]] BinaryPredicate &&p) noexcept(magic_enum::detail:: is_nothrow_invocable()) { // Note: operator== isn't constexpr if (a.size() != b.size()) { return false; } for (QStringView::size_type i = 0; i < a.size(); i++) { if (!p(a[i], b[i])) { return false; } } return true; } template consteval auto enumNameStorage() { constexpr auto utf8 = magic_enum::enum_name(); static_assert(isLatin1(utf8), "Can't convert non-latin1 UTF8 to UTF16"); std::array storage; for (std::size_t i = 0; i < utf8.size(); i++) { storage[i] = static_cast(utf8[i]); } storage[utf8.size()] = 0; return storage; } template inline constexpr auto ENUM_NAME_STORAGE = enumNameStorage(); template consteval auto namesStorage(std::index_sequence /*unused*/) { return std::array{{detail::fromArray( ENUM_NAME_STORAGE()[I]>)...}}; } template > inline constexpr auto NAMES_STORAGE = namesStorage( std::make_index_sequence()>{}); } // namespace detail /// @brief Get the name of an enum value /// /// This version is much lighter on the compile times and is not restricted to the enum_range limitation. /// /// @tparam V The enum value /// @returns The name as a string view template [[nodiscard]] consteval magic_enum::detail::enable_if_t enumName() noexcept { return QStringView{ detail::fromArray(detail::ENUM_NAME_STORAGE)}; } /// @brief Get the name of an enum value /// /// @param value The enum value /// @returns The name as a string view. If @a value does not have name or the /// value is out of range an empty string is returned. template > [[nodiscard]] constexpr magic_enum::detail::enable_if_t enumName(E value) noexcept { using D = std::decay_t; if (const auto i = magic_enum::enum_index(value)) { return detail::NAMES_STORAGE[*i]; } return {}; } /// @brief Get the name of an enum value /// /// This version is much lighter on the compile times and is not restricted to /// the enum_range limitation. /// /// @tparam V The enum value /// @returns The name as a string. The returned string is static. template [[nodiscard]] inline magic_enum::detail::enable_if_t enumNameString() noexcept { constexpr auto view = enumName(); return QString(QStringPrivate(nullptr, const_cast(view.utf16()), view.size())); } /// @brief Get the name of an enum value /// /// This version is much lighter on the compile times and is not restricted to /// the enum_range limitation. /// /// @tparam V The enum value /// @returns The name as a string. If @a value does not have name or the /// value is out of range an empty string is returned. /// The returned string is static. template > [[nodiscard]] inline magic_enum::detail::enable_if_t enumNameString( E value) noexcept { using D = std::decay_t; auto view = enumName(value); return QString(QStringPrivate(nullptr, const_cast(view.utf16()), view.size())); } /// @brief Gets the enum value from a name /// /// @tparam E The enum type to parse the @a name as /// @param name The name of the enum value to parse /// @param p A predicate to compare characters of a string /// (defaults to std::equal_to) /// @returns A `std::optional` of the parsed value. If no value was parsed, /// `std::nullopt` is returned. template , typename BinaryPredicate = std::equal_to<>> [[nodiscard]] constexpr magic_enum::detail::enable_if_t< E, std::optional>, BinaryPredicate> enumCast(QStringView name, [[maybe_unused]] BinaryPredicate p = {}) noexcept(magic_enum::detail:: is_nothrow_invocable()) { using D = std::decay_t; if constexpr (magic_enum::enum_count() == 0) { static_cast(name); return std::nullopt; // Empty enum. } for (std::size_t i = 0; i < magic_enum::enum_count(); i++) { if (detail::eq(name, detail::NAMES_STORAGE[i], p)) { return magic_enum::enum_value(i); } } return std::nullopt; // Invalid value or out of range. } /// @brief Constructs a name from the @a flags /// /// @param flags The combined flags to construct the name from /// @param sep A separator between each flag (defaults to u'|') /// @returns A string containing all names separated by @a sep. If any flag in /// @a flags is out of rage or does not have a name, an empty string /// is returned. template [[nodiscard]] inline magic_enum::detail::enable_if_t enumFlagsName( E flags, char16_t sep = u'|') { using D = std::decay_t; using U = std::underlying_type_t; constexpr auto S = magic_enum::detail::enum_subtype::flags; // NOLINT QString name; auto checkValue = U{0}; for (std::size_t i = 0; i < magic_enum::enum_count(); ++i) { const auto v = static_cast(magic_enum::enum_value(i)); if ((static_cast(flags) & v) != 0) { const auto n = detail::NAMES_STORAGE[i]; if (!n.empty()) { checkValue |= v; if (!name.isEmpty()) { name.append(sep); } name.append(n); } else { return {}; // Value out of range. } } } if (checkValue != 0 && checkValue == static_cast(flags)) { return name; } return {}; // Invalid value or out of range. } } // namespace qmagicenum ```