diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index 0b6ec90030c9..fcd83e3d3ef6 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -660,6 +660,50 @@ struct formatter, Char> { } }; +namespace detail { +// Check if T has an interface like container adapter (e.g. std::stack, +// std::queue, std::priority_queue). +template class is_container_adaptor_like { + template static auto check(U* p) -> typename U::container_type; + template static void check(...); + + public: + static constexpr const bool value = + !std::is_void(nullptr))>::value; +}; + +template +auto get_container(T& t) -> + typename std::add_const::type& { + struct getter : T { + static auto get(const T& t) -> + typename std::add_const::type& { + return t.*(&getter::c); // Access c through the derived class. + } + }; + return getter::get(t); +} + +} // namespace detail + +template +struct formatter::value>> { + struct formatter())), Char> + container_formatter; + + template + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + return container_formatter.parse(ctx); + } + + template + auto format(const T& value, FormatContext& ctx) const -> + typename FormatContext::iterator { + return container_formatter.format(detail::get_container(value), ctx); + } +}; + FMT_MODULE_EXPORT_BEGIN /** diff --git a/test/ranges-test.cc b/test/ranges-test.cc index fa46fc41eb8b..ff29171d57c0 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include "gtest/gtest.h" @@ -406,3 +408,60 @@ TEST(ranges_test, range_of_range_of_mixed_const) { TEST(ranges_test, vector_char) { EXPECT_EQ(fmt::format("{}", std::vector{'a', 'b'}), "['a', 'b']"); } + +TEST(ranges_test, container_adaptor) { + { + using fmt::detail::is_container_adaptor_like; + using T = std::nullptr_t; + static_assert(is_container_adaptor_like>::value, ""); + static_assert(is_container_adaptor_like>::value, ""); + static_assert(is_container_adaptor_like>::value, ""); + static_assert(!is_container_adaptor_like>::value, ""); + } + + { + std::stack s; + s.push(1); + s.push(2); + EXPECT_EQ(fmt::format("{}", s), "[1, 2]"); + } + + { + std::stack> s; + s.push(1); + s.push(2); + EXPECT_EQ(fmt::format("{}", s), "[1, 2]"); + } + + { + std::queue q; + q.push(1); + q.push(2); + EXPECT_EQ(fmt::format("{}", q), "[1, 2]"); + } + + { + std::priority_queue q; + q.push(3); + q.push(1); + q.push(2); + q.push(4); + EXPECT_EQ(fmt::format("{}", q), "[4, 3, 2, 1]"); + } + + { + struct my_container_adaptor { + using value_type = int; + using container_type = std::vector; + void push(const value_type& v) { c.push_back(v); } + + protected: + container_type c; + }; + + my_container_adaptor m; + m.push(1); + m.push(2); + EXPECT_EQ(fmt::format("{}", m), "[1, 2]"); + } +}