]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
libstdc++: Implement formatters for queue, priority_queue and stack [PR109162]
authorTomasz Kamiński <tkaminsk@redhat.com>
Fri, 18 Apr 2025 12:56:39 +0000 (14:56 +0200)
committerTomasz Kamiński <tkaminsk@redhat.com>
Fri, 25 Apr 2025 11:02:04 +0000 (13:02 +0200)
This patch implements formatter specializations for standard container adaptors
(queue, priority_queue and stack) from P2286R8.

To be able to access the protected `c` member, the adaptors befriend
corresponding formatter specializations. Note that such specialization
may be disable if the container is formattable, in such case
specializations are unharmful.

As in the case of previous commits, the signatures of the user-facing parse
and format methods of the provided formatters deviate from the standard by
constraining types of parameters:
 * _CharT is constrained __formatter::__char
 * basic_format_parse_context<_CharT> for parse argument
 * basic_format_context<_Out, _CharT> for format second argument
The standard specifies all above as unconstrained types. In particular
_CharT constrain, allow us to befriend all allowed specializations.

Furthermore the standard specifies these formatters as delegating to
formatter<ranges::ref_view<const? _Container>, charT>, which in turn
delegates to range_formatter. This patch avoids one level of indirection,
and dependency of ranges::ref_view.  This is technically observable if
user specializes formatter<std::ref_view<PD>> where PD is program defined
container, but I do not think this is the case worth extra indirection.

This patch also moves the formattable and it's dependencies to the formatfwd.h,
so it can be used in adapters formatters, without including format header.
The definition of _Iter_for is changed from alias to denoting
back_insert_iterator<basic_string<_CharT>>, to struct with type nested typedef
that points to same type, that is forward declared.

PR libstdc++/109162

libstdc++-v3/ChangeLog:

* include/bits/formatfwd.h (__format::__parsable_with)
(__format::__formattable_with, __format::__formattable_impl)
(__format::__has_debug_format, __format::__const_formattable_range)
(__format::__maybe_const_range, __format::__maybe_const)
(std::formattable): Moved from std/format.
(__format::Iter_for, std::range_formatter): Forward declare.
* include/bits/stl_queue.h (std::formatter): Forward declare.
(std::queue, std::priority_queue): Befriend formatter specializations.
* include/bits/stl_stack.h (std::formatter): Forward declare.
(std::stack): Befriend formatter specializations.
* include/std/format (__format::_Iter_for): Define as struct with
(__format::__parsable_with, __format::__formattable_with)
(__format::__formattable_impl, __format::__has_debug_format)
(_format::__const_formattable_range, __format::__maybe_const_range)
(__format::__maybe_const, std::formattable): Moved to bits/formatfwd.h.
(std::range_formatter): Remove default argument specified in declaration
in bits/formatfwd.h.
* include/std/queue: Include bits/version.h before bits/stl_queue.h.
(formatter<queue<_Tp, _Container, _Compare>, _CharT>)
(formatter<priority_queue<_Tp, _Container, _Compare>, _CharT>): Define.
* include/std/stack: Include bits/version.h before bits/stl_stack.h
(formatter<stack<_Tp, _Container, _Compare>, _CharT>): Define.
* testsuite/std/format/ranges/adaptors.cc: New test.

Reviewed-by: Jonathan Wakely <jwakely@redhat.com>
Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
libstdc++-v3/include/bits/formatfwd.h
libstdc++-v3/include/bits/stl_queue.h
libstdc++-v3/include/bits/stl_stack.h
libstdc++-v3/include/std/format
libstdc++-v3/include/std/queue
libstdc++-v3/include/std/stack
libstdc++-v3/testsuite/std/format/ranges/adaptors.cc [new file with mode: 0644]

index a6b5ac8c8ce1cc0aa22bdf467ad13247a40facd8..9ba658b078a50dafc5cd78283f5cec141f578497 100644 (file)
 // <bits/version.h> must have been included before this header:
 #ifdef __glibcxx_format // C++ >= 20 && HOSTED
 
+#include <concepts>
+#include <type_traits>
+#if __glibcxx_format_ranges // C++ >= 23 && HOSTED
+#  include <bits/ranges_base.h>  // input_range, range_reference_t
+#endif
+
 namespace std _GLIBCXX_VISIBILITY(default)
 {
 _GLIBCXX_BEGIN_NAMESPACE_VERSION
@@ -50,6 +56,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   // [format.formatter], formatter
   template<typename _Tp, typename _CharT = char> struct formatter;
 
+/// @cond undocumented
 namespace __format
 {
 #ifdef _GLIBCXX_USE_WCHAR_T
@@ -60,9 +67,80 @@ namespace __format
     concept __char = same_as<_CharT, char>;
 #endif
 
+  template<typename _Tp, typename _Context,
+          typename _Formatter
+            = typename _Context::template formatter_type<remove_const_t<_Tp>>,
+          typename _ParseContext
+            = basic_format_parse_context<typename _Context::char_type>>
+    concept __parsable_with
+      = semiregular<_Formatter>
+         && requires (_Formatter __f, _ParseContext __pc)
+    {
+      { __f.parse(__pc) } -> same_as<typename _ParseContext::iterator>;
+    };
+
+  template<typename _Tp, typename _Context,
+          typename _Formatter
+            = typename _Context::template formatter_type<remove_const_t<_Tp>>,
+          typename _ParseContext
+            = basic_format_parse_context<typename _Context::char_type>>
+    concept __formattable_with
+      = semiregular<_Formatter>
+         && requires (const _Formatter __cf, _Tp&& __t, _Context __fc)
+    {
+      { __cf.format(__t, __fc) } -> same_as<typename _Context::iterator>;
+    };
+
+  // An unspecified output iterator type used in the `formattable` concept.
+  template<typename _CharT>
+    struct _Iter_for;
+  template<typename _CharT>
+    using _Iter_for_t = typename _Iter_for<_CharT>::type;
+
+  template<typename _Tp, typename _CharT,
+          typename _Context = basic_format_context<_Iter_for_t<_CharT>, _CharT>>
+    concept __formattable_impl
+      = __parsable_with<_Tp, _Context> && __formattable_with<_Tp, _Context>;
+
+  template<typename _Formatter>
+    concept __has_debug_format = requires(_Formatter __f)
+    {
+      __f.set_debug_format();
+    };
+
   template<__char _CharT>
     struct __formatter_int;
+} // namespace __format
+/// @endcond
+
+#if __glibcxx_format_ranges // C++ >= 23 && HOSTED
+  // [format.formattable], concept formattable
+  template<typename _Tp, typename _CharT>
+    concept formattable
+      = __format::__formattable_impl<remove_reference_t<_Tp>, _CharT>;
+
+   template<typename _Tp, __format::__char _CharT = char>
+     requires same_as<remove_cvref_t<_Tp>, _Tp> && formattable<_Tp, _CharT>
+     class range_formatter;
+
+/// @cond undocumented
+namespace __format
+{
+  template<typename _Rg, typename _CharT>
+    concept __const_formattable_range
+      = ranges::input_range<const _Rg>
+         && formattable<ranges::range_reference_t<const _Rg>, _CharT>;
+
+  template<typename _Rg, typename _CharT>
+    using __maybe_const_range
+      = __conditional_t<__const_formattable_range<_Rg, _CharT>, const _Rg, _Rg>;
+
+  template<typename _Tp, typename _CharT>
+    using __maybe_const
+      = __conditional_t<formattable<const _Tp, _CharT>, const _Tp, _Tp>;
 }
+#endif // format_ranges
+
 
 _GLIBCXX_END_NAMESPACE_VERSION
 } // namespace std
index 554e076aae903eaa4472f797884c33dfe022747c..a3a8bc1f0ada354d520125d78835f29e21de193c 100644 (file)
@@ -70,6 +70,10 @@ namespace std _GLIBCXX_VISIBILITY(default)
 {
 _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
+#if __glibcxx_format_ranges
+  template<typename, typename> class formatter;
+#endif
+
   /**
    *  @brief  A standard container giving FIFO behavior.
    *
@@ -369,6 +373,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        swap(c, __q.c);
       }
 #endif // __cplusplus >= 201103L
+
+#if __glibcxx_format_ranges
+      friend class formatter<queue<_Tp, _Sequence>, char>;
+      friend class formatter<queue<_Tp, _Sequence>, wchar_t>;
+#endif
     };
 
 #if __cpp_deduction_guides >= 201606
@@ -898,6 +907,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        swap(comp, __pq.comp);
       }
 #endif // __cplusplus >= 201103L
+
+#if __glibcxx_format_ranges
+      friend class formatter<priority_queue<_Tp, _Sequence, _Compare>, char>;
+      friend class formatter<priority_queue<_Tp, _Sequence, _Compare>, wchar_t>;
+#endif
     };
 
 #if __cpp_deduction_guides >= 201606
index 7b324642b322546c102bb4217b09b58167b6b286..27c79d6ce58863288bdb9b8c537fa223be4d4328 100644 (file)
@@ -70,6 +70,10 @@ namespace std _GLIBCXX_VISIBILITY(default)
 {
 _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
+#if __glibcxx_format_ranges
+  template<typename, typename> class formatter;
+#endif
+
   /**
    *  @brief  A standard container giving FILO behavior.
    *
@@ -343,6 +347,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        swap(c, __s.c);
       }
 #endif // __cplusplus >= 201103L
+
+#if __glibcxx_format_ranges
+      friend class formatter<stack<_Tp, _Sequence>, char>;
+      friend class formatter<stack<_Tp, _Sequence>, wchar_t>;
+#endif
     };
 
 #if __cpp_deduction_guides >= 201606
index e557e104d74d083dcc38c4ec4533b6930a1a61c1..7d3067098befea0c9dbd0da95c9995972c0500ea 100644 (file)
@@ -117,6 +117,11 @@ namespace __format
   template<typename _CharT>
     class _Sink_iter;
 
+  // An unspecified output iterator type used in the `formattable` concept.
+  template<typename _CharT>
+    struct _Iter_for
+    { using type = back_insert_iterator<basic_string<_CharT>>; };
+
   template<typename _CharT>
     using __format_context = basic_format_context<_Sink_iter<_CharT>, _CharT>;
 
@@ -135,6 +140,7 @@ namespace __format
 
       template<typename, typename...> friend struct std::basic_format_string;
     };
+
 } // namespace __format
 /// @endcond
 
@@ -3024,59 +3030,6 @@ namespace __format
     : private formatter<__format::__disabled, wchar_t> { };
 #endif
 
-/// @cond undocumented
-namespace __format
-{
-  template<typename _Tp, typename _Context,
-          typename _Formatter
-            = typename _Context::template formatter_type<remove_const_t<_Tp>>,
-          typename _ParseContext
-            = basic_format_parse_context<typename _Context::char_type>>
-    concept __parsable_with
-      = semiregular<_Formatter>
-         && requires (_Formatter __f, _ParseContext __pc)
-    {
-      { __f.parse(__pc) } -> same_as<typename _ParseContext::iterator>;
-    };
-
-  template<typename _Tp, typename _Context,
-          typename _Formatter
-            = typename _Context::template formatter_type<remove_const_t<_Tp>>,
-          typename _ParseContext
-            = basic_format_parse_context<typename _Context::char_type>>
-    concept __formattable_with
-      = semiregular<_Formatter>
-         && requires (const _Formatter __cf, _Tp&& __t, _Context __fc)
-    {
-      { __cf.format(__t, __fc) } -> same_as<typename _Context::iterator>;
-    };
-
-  // An unspecified output iterator type used in the `formattable` concept.
-  template<typename _CharT>
-    using _Iter_for = back_insert_iterator<basic_string<_CharT>>;
-
-  template<typename _Tp, typename _CharT,
-          typename _Context = basic_format_context<_Iter_for<_CharT>, _CharT>>
-    concept __formattable_impl
-      = __parsable_with<_Tp, _Context> && __formattable_with<_Tp, _Context>;
-
-  template<typename _Formatter>
-    concept __has_debug_format = requires(_Formatter __f)
-    {
-      __f.set_debug_format();
-    };
-
-} // namespace __format
-/// @endcond
-
-#if __glibcxx_format_ranges // C++ >= 23 && HOSTED
-  // [format.formattable], concept formattable
-  template<typename _Tp, typename _CharT>
-    concept formattable
-      = __format::__formattable_impl<remove_reference_t<_Tp>, _CharT>;
-
-#endif // format_ranges
-
   /// An iterator after the last character written, and the number of
   /// characters that would have been written.
   template<typename _Out>
@@ -5250,26 +5203,13 @@ namespace __format
       return __format::__write_padded_as_spec(__str, __width, __fc, __spec);
     }
 
-  template<typename _Rg, typename _CharT>
-    concept __const_formattable_range
-      = ranges::input_range<const _Rg>
-         && formattable<ranges::range_reference_t<const _Rg>, _CharT>;
-
   // _Rg& and const _Rg& are both formattable and use same formatter
   // specialization for their references.
   template<typename _Rg, typename _CharT>
     concept __simply_formattable_range
       = __const_formattable_range<_Rg, _CharT>
          && same_as<remove_cvref_t<ranges::range_reference_t<_Rg>>,
-                    remove_cvref_t<ranges::range_reference_t<const _Rg>>>;
-
-  template<typename _Rg, typename _CharT>
-    using __maybe_const_range
-      = __conditional_t<__const_formattable_range<_Rg, _CharT>, const _Rg, _Rg>;
-
-  template<typename _Tp, typename _CharT>
-    using __maybe_const
-      = __conditional_t<formattable<const _Tp, _CharT>, const _Tp, _Tp>;
+                    remove_cvref_t<ranges::range_reference_t<const _Rg>>>;
 
   template<size_t _Pos, typename _Tp, typename _CharT>
     struct __indexed_formatter_storage
@@ -5493,7 +5433,7 @@ namespace __format
     };
 
   // [format.range.formatter], class template range_formatter
-  template<typename _Tp, __format::__char _CharT = char>
+  template<typename _Tp, __format::__char _CharT>
     requires same_as<remove_cvref_t<_Tp>, _Tp> && formattable<_Tp, _CharT>
     class range_formatter
     {
index 74b6c07b49f16ea92ddd53090d5b8d2898f302f5..90525897da7548c5b45c076b61aaf1345e72dbe3 100644 (file)
 
 #include <bits/requires_hosted.h> // containers
 
+#define __glibcxx_want_adaptor_iterator_pair_constructor
+#define __glibcxx_want_containers_ranges
+#include <bits/version.h>
+
 #include <deque>
 #include <vector>
 #include <bits/stl_heap.h>
 #include <bits/stl_function.h>
 #include <bits/stl_queue.h>
 
-#define __glibcxx_want_adaptor_iterator_pair_constructor
-#define __glibcxx_want_containers_ranges
-#include <bits/version.h>
+#ifdef __glibcxx_format_ranges // C++ >= 23 && HOSTED
+#include <bits/formatfwd.h>
+
+namespace std _GLIBCXX_VISIBILITY(default)
+{
+_GLIBCXX_BEGIN_NAMESPACE_VERSION
+  // Standard does not constrain accepted _CharT, we do so we can
+  // befriend specializations.
+  template<__format::__char _CharT, typename _Tp,
+          formattable<_CharT> _Container>
+    struct formatter<queue<_Tp, _Container>, _CharT>
+    {
+    private:
+      using __maybe_const_adaptor
+       = __conditional_t<
+           __format::__const_formattable_range<_Container, _CharT>,
+           const queue<_Tp, _Container>, queue<_Tp, _Container>>;
+
+    public:
+      // Standard declares this as template accepting unconstrained
+      // ParseContext type.
+      constexpr typename basic_format_parse_context<_CharT>::iterator
+      parse(basic_format_parse_context<_CharT>& __pc)
+      { return _M_f.parse(__pc); }
+
+      // Standard declares this as template accepting unconstrained
+      // FormatContext type.
+      template<typename _Out>
+       typename basic_format_context<_Out, _CharT>::iterator
+       format(__maybe_const_adaptor& __a,
+              basic_format_context<_Out, _CharT>& __fc) const
+       { return _M_f.format(__a.c, __fc); }
+
+    private:
+      // Standard uses formatter<ref_view<_Container>, _CharT>.
+      range_formatter<_Tp, _CharT> _M_f;
+    };
+
+  template<__format::__char _CharT, typename _Tp,
+          formattable<_CharT> _Container, typename _Compare>
+    struct formatter<priority_queue<_Tp, _Container, _Compare>, _CharT>
+    {
+    private:
+      using __maybe_const_adaptor
+       = __conditional_t<
+           __format::__const_formattable_range<_Container, _CharT>,
+           const priority_queue<_Tp, _Container, _Compare>,
+           priority_queue<_Tp, _Container, _Compare>>;
+
+    public:
+      // Standard declares this as template accepting unconstrained
+      // ParseContext type.
+      constexpr typename basic_format_parse_context<_CharT>::iterator
+      parse(basic_format_parse_context<_CharT>& __pc)
+      { return _M_f.parse(__pc); }
+
+      // Standard declares this as template accepting unconstrained
+      // FormatContext type.
+      template<typename _Out>
+       typename basic_format_context<_Out, _CharT>::iterator
+       format(__maybe_const_adaptor& __a,
+              basic_format_context<_Out, _CharT>& __fc) const
+       { return _M_f.format(__a.c, __fc); }
+
+    private:
+      // Standard uses formatter<ref_view<_Container>, _CharT>.
+      range_formatter<_Tp, _CharT> _M_f;
+    };
+
+_GLIBCXX_END_NAMESPACE_VERSION
+} // namespace std
+#endif // __glibcxx_format_ranges
+
 
 #endif /* _GLIBCXX_QUEUE */
index 5cea4762a1923d9ccd35609eceef9b47de943973..a57a5a08bc3c3ccdc20ab13f92572e5962f79348 100644 (file)
 
 #include <bits/requires_hosted.h> // containers
 
-#include <deque>
-#include <bits/stl_stack.h>
-
 #define __glibcxx_want_adaptor_iterator_pair_constructor
 #define __glibcxx_want_containers_ranges
 #include <bits/version.h>
 
+#include <deque>
+#include <bits/stl_stack.h>
+
+#ifdef __glibcxx_format_ranges // C++ >= 23 && HOSTED
+#include <bits/formatfwd.h>
+
+namespace std _GLIBCXX_VISIBILITY(default)
+{
+_GLIBCXX_BEGIN_NAMESPACE_VERSION
+  // Standard does not constrain accepted _CharT, we do so we can
+  // befriend specializations.
+  template<__format::__char _CharT, typename _Tp,
+          formattable<_CharT> _Container>
+    struct formatter<stack<_Tp, _Container>, _CharT>
+    {
+    private:
+      using __maybe_const_adaptor
+       = __conditional_t<
+           __format::__const_formattable_range<_Container, _CharT>,
+           const stack<_Tp, _Container>, stack<_Tp, _Container>>;
+
+    public:
+      // Standard declares this as template accepting unconstrained
+      // ParseContext type.
+      constexpr typename basic_format_parse_context<_CharT>::iterator
+      parse(basic_format_parse_context<_CharT>& __pc)
+      { return _M_f.parse(__pc); }
+
+      // Standard declares this as template accepting unconstrained
+      // FormatContext type.
+      template<typename _Out>
+       typename basic_format_context<_Out, _CharT>::iterator
+       format(__maybe_const_adaptor& __a,
+              basic_format_context<_Out, _CharT>& __fc) const
+       { return _M_f.format(__a.c, __fc); }
+
+    private:
+      // Standard uses formatter<ref_view<_Container>, _CharT>.
+      range_formatter<_Tp, _CharT> _M_f;
+    };
+_GLIBCXX_END_NAMESPACE_VERSION
+} // namespace std
+#endif // __glibcxx_format_ranges
+
+
 #endif /* _GLIBCXX_STACK */
diff --git a/libstdc++-v3/testsuite/std/format/ranges/adaptors.cc b/libstdc++-v3/testsuite/std/format/ranges/adaptors.cc
new file mode 100644 (file)
index 0000000..854c7ee
--- /dev/null
@@ -0,0 +1,156 @@
+// { dg-do run { target c++23 } }
+// { dg-timeout-factor 2 }
+
+#include <format>
+#include <queue>
+#include <stack>
+#include <testsuite_hooks.h>
+
+template<typename... Args>
+bool
+is_format_string_for(const char* str, Args&&... args)
+{
+  try {
+    (void) std::vformat(str, std::make_format_args(args...));
+    return true;
+  } catch (const std::format_error&) {
+    return false;
+  }
+}
+
+#define WIDEN_(C, S) ::std::__format::_Widen<C>(S, L##S)
+#define WIDEN(S) WIDEN_(_CharT, S)
+
+template<template<typename Tp> class Adaptor>
+void
+test_format_string()
+{
+  Adaptor<int> q;
+  VERIFY( !is_format_string_for("{:?}", q) );
+  VERIFY( !is_format_string_for("{:P}", q) );
+
+  // width needs to be integer type
+  VERIFY( !is_format_string_for("{:{}}", q, 1.0f) );
+}
+
+struct NoFormat
+{
+  friend auto operator<=>(NoFormat, NoFormat) = default;
+};
+
+struct MutFormat
+{
+  MutFormat() = default;
+  MutFormat(int p) : x(p) {}
+
+  int x;
+  friend auto operator<=>(MutFormat, MutFormat) = default;
+};
+
+template<typename CharT>
+struct std::formatter<MutFormat, CharT>
+  : std::formatter<int, CharT>
+{
+   template<typename Out>
+   Out format(MutFormat& mf, basic_format_context<Out, CharT>& ctx) const
+   { return std::formatter<int, CharT>::format(mf.x, ctx); }
+};
+
+template<typename T>
+struct NotFormattableCont : std::vector<T>
+{
+  using std::vector<T>::vector;
+};
+
+template<typename T>
+constexpr auto std::format_kind<NotFormattableCont<T>>
+  = std::range_format::disabled;
+
+template<typename _CharT,
+        template<typename Tp, typename Cont = std::vector<Tp>> class Adaptor>
+void
+test_output()
+{
+  const std::vector<int> v{3, 2, 1};
+  std::basic_string<_CharT> res;
+  Adaptor<int, std::vector<int>> q(std::from_range, v);
+
+  res = std::format(WIDEN("{}"), q);
+  VERIFY( res == WIDEN("[3, 2, 1]") );
+
+  res = std::format(WIDEN("{}"), std::as_const(q));
+  VERIFY( res == WIDEN("[3, 2, 1]") );
+
+  res = std::format(WIDEN("{:n:#x}"), q);
+  VERIFY( res == WIDEN("0x3, 0x2, 0x1") );
+
+  res = std::format(WIDEN("{:=^23:#04x}"), q);
+  VERIFY( res == WIDEN("==[0x03, 0x02, 0x01]===") );
+
+  // Sequence output is always used
+  std::queue<_CharT, std::basic_string<_CharT>> qs(
+    std::from_range,
+    std::basic_string_view<_CharT>(WIDEN("321")));
+
+  res = std::format(WIDEN("{}"), qs);
+  VERIFY( res == WIDEN("['3', '2', '1']") );
+
+  res = std::format(WIDEN("{::}"), std::as_const(qs));
+  VERIFY( res == WIDEN("[3, 2, 1]") );
+
+  res = std::format(WIDEN("{:?s}"), qs);
+  VERIFY( res == WIDEN(R"("321")") );
+
+  Adaptor<int, std::deque<int>> qd(std::from_range, v);
+
+  res = std::format(WIDEN("{}"), qd);
+  VERIFY( res == WIDEN("[3, 2, 1]") );
+
+  res = std::format(WIDEN("{}"), std::as_const(qd));
+  VERIFY( res == WIDEN("[3, 2, 1]") );
+
+  Adaptor<MutFormat> mq(std::from_range, v);
+
+  res = std::format(WIDEN("{}"), mq);
+  VERIFY( res == WIDEN("[3, 2, 1]") );
+
+  static_assert(!std::formattable<const Adaptor<MutFormat>, _CharT>);
+
+  static_assert(!std::formattable<Adaptor<NoFormat>, _CharT>);
+  static_assert(!std::formattable<const Adaptor<NoFormat>, _CharT>);
+
+  // Formatter check if container is formattable, not container elements.
+  static_assert(!std::formattable<Adaptor<int, NotFormattableCont<int>>, _CharT>);
+}
+
+template<template<typename Tp, typename Cont = std::vector<Tp>> class Adaptor>
+void
+test_adaptor()
+{
+  test_format_string<Adaptor>();
+  test_output<char, Adaptor>();
+  test_output<wchar_t, Adaptor>();
+
+  static_assert(!std::formattable<Adaptor<int>, int>);
+  static_assert(!std::formattable<Adaptor<int>, char32_t>);
+}
+
+template<typename _CharT>
+void
+test_compare()
+{
+  const std::vector<int> v{3, 2, 1};
+  std::basic_string<_CharT> res;
+  std::priority_queue<int, std::vector<int>, std::greater<>> q(
+     std::from_range, v);
+
+  res = std::format(WIDEN("{}"), q);
+  VERIFY( res == WIDEN("[1, 2, 3]") );
+}
+
+int main()
+{
+  test_adaptor<std::queue>();
+  test_adaptor<std::priority_queue>();
+  test_compare<char>();
+}