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.99k stars 445 forks source link

Including fmt/ranges.h and magic_enum/magic_enum_format.hpp fails to compile #379

Open edelmanjm opened 2 months ago

edelmanjm commented 2 months ago

(Ported from a comment on #298)

Including both headers appears to result in a compiler error.

edelmanjm commented 2 months ago

Possibly related to https://github.com/fmtlib/fmt/issues/4058? Unfortunately my templating isn't strong enough to figure this out definitively; I tried adding the following but it didn't work:

#include <fmt/ranges.h>

...

template <typename E>
struct fmt::is_range<E, std::enable_if_t<std::is_enum_v<std::decay_t<E>> && magic_enum::customize::enum_format_enabled<E>(), char>> {
  static constexpr const bool value = false;
};

As for the local compiler error I'm seeing, it's as follows (trimmed for brevity):

ranges.h:674:47: error: ambiguous template instantiation for ‘struct fmt::v11::formatter<std::byte, char, void>’
  674 |   formatter<remove_cvref_t<value_type>, Char> value_formatter_;
      |                                               ^~~~~~~~~~~~~~~~
format.h:3975:8: note: candidates are: ‘template<class T, class Char> struct fmt::v11::formatter<T, Char, typename std::enable_if<fmt::v11::detail::has_format_as<T>::value, void>::type> [with T = std::byte; Char = char]’
 3975 | struct formatter<T, Char, enable_if_t<detail::has_format_as<T>::value>>
      |        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
magic_enum_format.hpp:86:13: note:                 ‘template<class E> struct fmt::v11::formatter<E, typename std::enable_if<(is_enum_v<typename std::decay<_Tp>::type> && enum_format_enabled<E>()), char>::type> [with E = std::byte]’
   86 | struct fmt::formatter<E, std::enable_if_t<std::is_enum_v<std::decay_t<E>> && magic_enum::customize::enum_format_enabled<E>(), char>> : fmt::formatter<std::string_view> {
      |             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
edelmanjm commented 2 months ago

So after a discussion with the author of fmt, I've worked out the following which appears to work:

template <typename E, std::enable_if_t<std::is_enum_v<std::decay_t<E>>, int> = 0>
auto format_as(E e) {
 static_assert(std::is_same_v<char, fmt::string_view::value_type>, "formatter requires string_view::value_type type same as char.");
 using D = std::decay_t<E>;

 if constexpr (magic_enum::detail::supported<D>::value) {
  if constexpr (magic_enum::detail::subtype_v<D> == magic_enum::detail::enum_subtype::flags) {
   if (const auto name = magic_enum::enum_flags_name<D>(e); !name.empty()) {
    return name;
   }
  } else {
   if (const auto name = magic_enum::enum_name<D>(e); !name.empty()) {
    return name;
   }
  }
 }
 return std::string_view(std::to_string(magic_enum::enum_integer<D>(e)));
}

I'm not confident this is necessarily the most optimal implementation, but I'm happy to upstream it if so desired.

The author of fmt has also made it clear that such weakly constrained specializations are considered poor practice. However, given the need to format many enums across namespaces, I'm not necessarily against continuing to include this in magic_enum.

Neargye commented 1 month ago

Hi, please test https://github.com/Neargye/magic_enum/pull/382

edelmanjm commented 1 month ago

Thanks, starting testing now.

edelmanjm commented 1 month ago

Unfortunately this is not working for me; the formatter is not always detected.

cmake-build-debug-local/_deps/fmt-src/include/fmt/base.h:1641:63: error: ‘fmt::v11::detail::type_is_unformattable_for<Shared::Tests::CATCH2_INTERNAL_TEST_0()::MyEnum, char> _’ has incomplete type
 1641 |     type_is_unformattable_for<T, typename Context::char_type> _;
      |                                                               ^
cmake-build-debug-local/_deps/fmt-src/include/fmt/base.h:1644:7: error: static assertion failed: Cannot format an argument. To make type T formattable provide a formatter<T> specialization: https://fmt.dev/latest/api.html#udt
 1644 |       formattable,
      |       ^~~~~~~~~~~
cmake-build-debug-local/_deps/fmt-src/include/fmt/base.h:1644:7: note: ‘formattable’ evaluates to false

I can see about providing a more minimal example if necessary.

Neargye commented 1 week ago

@edelmanjm for me locally work, so I'll ask you to test it again (I update https://github.com/Neargye/magic_enum/pull/382)

edelmanjm commented 2 hours ago

I believe that's working! I'll do a bit more in-depth testing to confirm.