diff --git a/include/fmt/core.h b/include/fmt/core.h index 9a00668d..9a2c92d8 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1348,10 +1348,16 @@ inline auto format_as(std::byte b) -> unsigned char { } #endif +template struct convertible_to { operator const T&() const; }; + template struct has_format_as { template ::value)> static auto check(U*) -> std::true_type; + // Use convertible_to to avoid implicit conversions. + template ())), + FMT_ENABLE_IF(std::is_class::value)> + static auto check(U*) -> std::true_type; static auto check(...) -> std::false_type; enum { value = decltype(check(static_cast(nullptr)))::value }; diff --git a/test/core-test.cc b/test/core-test.cc index 75168dcd..15d69bcc 100644 --- a/test/core-test.cc +++ b/test/core-test.cc @@ -680,6 +680,9 @@ auto format_as(scoped_enum_as_int) -> int { return 42; } enum class scoped_enum_as_string {}; auto format_as(scoped_enum_as_string) -> fmt::string_view { return "foo"; } +struct struct_as_int {}; +auto format_as(struct_as_int) -> int { return 42; } + struct convertible_to_enum { operator scoped_enum_as_int() const { return {}; } }; @@ -724,7 +727,7 @@ TEST(core_test, is_formattable) { static_assert(!fmt::is_formattable::value, ""); static_assert(!fmt::is_formattable::value, ""); static_assert(fmt::is_formattable::value, ""); - static_assert(!fmt::is_formattable::value, ""); + static_assert(!fmt::is_formattable::value, ""); } TEST(core_test, format) { EXPECT_EQ(fmt::format("{}", 42), "42"); } @@ -738,6 +741,7 @@ TEST(core_test, format_to) { TEST(core_test, format_as) { EXPECT_EQ(fmt::format("{}", test::scoped_enum_as_int()), "42"); EXPECT_EQ(fmt::format("{}", test::scoped_enum_as_string()), "foo"); + EXPECT_EQ(fmt::format("{}", test::struct_as_int()), "42"); } #ifdef __cpp_lib_byte