Add formatters for container adapters
Close #3215. 

https://eel.is/c++draft/container.adaptors.format specifies formatting for container adapters (`std::stack`, `std::queue`, and `std::priority_queue` in particular).

Not sure if the implementation should go into `ranges.h` or `std.h`. I'm putting it in `ranges.h` for now because I don't want to make `std.h` depend on `ranges.h`.

Container adapters have a protected member `Container c`, which can be exposed by defining a derived class. 
{"label": "fmtlib:master", "ref": "master", "sha": "f89cd276f7dead38f11cebc73d1e91a1b1b38124"}
`std::priority_queue`, `std::queue` and `std::stack` should be formattable out of the box, they are part of the standard.
Add formatters for STL container adapters
diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h
index 0b6ec90030c9..fae425f9e5f2 100644
--- a/include/fmt/ranges.h
+++ b/include/fmt/ranges.h
@@ -660,6 +660,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
 
 /**

diff --git a/test/ranges-test.cc b/test/ranges-test.cc
index fa46fc41eb8b..e1d384d7af0d 100644
--- a/test/ranges-test.cc
+++ b/test/ranges-test.cc
@@ -14,6 +14,8 @@
 #include <map>
 #include <string>
 #include <vector>
+#include <stack>
+#include <queue>
 
 #include "gtest/gtest.h"
 
@@ -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]");
+  }
+}

{"compile-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "ostream-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "ranges-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "std-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "core-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "scan-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "color-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "chrono-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "format-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "gtest-extra-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "args-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "compile-fp-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "unicode-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "assert-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "posix-mock-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "printf-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "enforce-checks-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "format-impl-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "os-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "xchar-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}}
{"compile-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "ostream-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "ranges-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "std-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "core-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "scan-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "color-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "chrono-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "format-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "gtest-extra-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "args-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "compile-fp-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "unicode-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "assert-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "posix-mock-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "printf-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "enforce-checks-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "format-impl-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "os-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "xchar-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}}
{"passed_count": 20, "failed_count": 0, "skipped_count": 0, "passed_tests": ["compile-test", "core-test", "chrono-test", "gtest-extra-test", "compile-fp-test", "unicode-test", "assert-test", "enforce-checks-test", "format-impl-test", "os-test", "xchar-test", "ostream-test", "ranges-test", "std-test", "scan-test", "color-test", "format-test", "args-test", "posix-mock-test", "printf-test"], "failed_tests": [], "skipped_tests": []}
{"passed_count": 0, "failed_count": 0, "skipped_count": 0, "passed_tests": [], "failed_tests": [], "skipped_tests": []}
{"passed_count": 20, "failed_count": 0, "skipped_count": 0, "passed_tests": ["compile-test", "core-test", "chrono-test", "gtest-extra-test", "compile-fp-test", "unicode-test", "assert-test", "enforce-checks-test", "format-impl-test", "os-test", "xchar-test", "ostream-test", "ranges-test", "std-test", "scan-test", "color-test", "format-test", "args-test", "posix-mock-test", "printf-test"], "failed_tests": [], "skipped_tests": []}