Skip to content

Commit

Permalink
Add formatters for container adapters (#3279)
Browse files Browse the repository at this point in the history
  • Loading branch information
ShawnZhong committed Feb 9, 2023
1 parent 7718eee commit 581c629
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 0 deletions.
28 changes: 28 additions & 0 deletions include/fmt/ranges.h
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,34 @@ struct formatter<tuple_join_view<Char, T...>, Char> {
}
};

namespace detail {
// Check if T has an interface like container adapter (e.g. std::stack,
// std::queue, std::priority_queue).
template <typename T> class is_container_adaptor_like {
template <typename U> static auto check(U* p) -> typename U::container_type;
template <typename> static void check(...);

public:
static constexpr const bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
};
} // namespace detail

template <typename T, typename Char>
struct formatter<T, Char,
enable_if_t<detail::is_container_adaptor_like<T>::value>>
: formatter<typename T::container_type, Char> {
template <typename FormatContext>
auto format(const T& t, FormatContext& ctx) const -> decltype(ctx.out()) {
struct getter : T {
static auto get(const T& t) -> const typename T::container_type& {
return t.*(&getter::c); // Access c through the derived class.
}
};
return formatter<typename T::container_type>::format(getter::get(t), ctx);
}
};

FMT_MODULE_EXPORT_BEGIN

/**
Expand Down
63 changes: 63 additions & 0 deletions test/ranges-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include <map>
#include <string>
#include <vector>
#include <stack>
#include <queue>

#include "gtest/gtest.h"

Expand Down Expand Up @@ -406,3 +408,64 @@ TEST(ranges_test, range_of_range_of_mixed_const) {
TEST(ranges_test, vector_char) {
EXPECT_EQ(fmt::format("{}", std::vector<char>{'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<std::stack<T>>::value, "");
static_assert(is_container_adaptor_like<std::queue<T>>::value, "");
static_assert(is_container_adaptor_like<std::priority_queue<T>>::value, "");
static_assert(!is_container_adaptor_like<std::vector<T>>::value, "");
}

{
std::stack<int> s;
s.push(1);
s.push(2);
EXPECT_EQ(fmt::format("{}", s), "[1, 2]");
EXPECT_EQ(fmt::format("{}", const_cast<const decltype(s)&>(s)), "[1, 2]");
}

{
std::queue<int> q;
q.push(1);
q.push(2);
EXPECT_EQ(fmt::format("{}", q), "[1, 2]");
}

{
std::priority_queue<int> q;
q.push(3);
q.push(1);
q.push(2);
q.push(4);
EXPECT_EQ(fmt::format("{}", q), "[4, 3, 2, 1]");
}

{
std::stack<char, std::string> s;
s.push('a');
s.push('b');
// Note: The output is formatted as a string because the underlying
// container is a string. This behavior is conforming to the standard
// [container.adaptors.format].
EXPECT_EQ(fmt::format("{}", s), "ab");
}

{
struct my_container_adaptor {
using value_type = int;
using container_type = std::vector<value_type>;
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]");
}
}

0 comments on commit 581c629

Please sign in to comment.