From 4999416e5cba39569b95331e99be02d486ee09a2 Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sat, 10 May 2025 13:15:45 -0500 Subject: [PATCH] Fix reference_wrapper ambiguity with format_as (#4434) --- include/fmt/std.h | 21 ++++++++++++++++++++- test/std-test.cc | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/include/fmt/std.h b/include/fmt/std.h index 5fea3fd7..3af4d61c 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -678,9 +678,28 @@ template struct formatter, Char> { } }; +namespace detail { +template +struct has_format_as : std::false_type {}; +template +struct has_format_as()))>> + : std::true_type {}; + +template +struct has_format_as_member : std::false_type {}; +template +struct has_format_as_member< + T, void_t::format_as(std::declval()))>> + : std::true_type {}; +} // namespace detail + +// Guard against format_as because reference_wrappers are implicitly convertible +// to T& template struct formatter, Char, - enable_if_t, Char>::value>> + enable_if_t, Char>::value && + !detail::has_format_as::value && + !detail::has_format_as_member::value>> : formatter, Char> { template auto format(std::reference_wrapper ref, FormatContext& ctx) const diff --git a/test/std-test.cc b/test/std-test.cc index 8e5fa06c..2f7f7d7c 100644 --- a/test/std-test.cc +++ b/test/std-test.cc @@ -414,4 +414,36 @@ TEST(std_test, format_shared_ptr) { TEST(std_test, format_reference_wrapper) { int num = 35; EXPECT_EQ("35", fmt::to_string(std::cref(num))); + EXPECT_EQ("35", fmt::to_string(std::ref(num))); + EXPECT_EQ("35", fmt::format("{}", std::cref(num))); + EXPECT_EQ("35", fmt::format("{}", std::ref(num))); +} + +// Regression test for https://github.com/fmtlib/fmt/issues/4424 +struct type_with_format_as { + int x; +}; + +int format_as(const type_with_format_as& t) { return t.x; } + +TEST(std_test, format_reference_wrapper_with_format_as) { + type_with_format_as t{20}; + EXPECT_EQ("20", fmt::to_string(std::cref(t))); + EXPECT_EQ("20", fmt::to_string(std::ref(t))); + EXPECT_EQ("20", fmt::format("{}", std::cref(t))); + EXPECT_EQ("20", fmt::format("{}", std::ref(t))); +} + +struct type_with_format_as_string { + std::string str; +}; + +std::string format_as(const type_with_format_as_string& t) { return t.str; } + +TEST(std_test, format_reference_wrapper_with_format_as_string) { + type_with_format_as_string t{"foo"}; + EXPECT_EQ("foo", fmt::to_string(std::cref(t))); + EXPECT_EQ("foo", fmt::to_string(std::ref(t))); + EXPECT_EQ("foo", fmt::format("{}", std::cref(t))); + EXPECT_EQ("foo", fmt::format("{}", std::ref(t))); }