On Tue, 22 Apr 2025 at 12:19, Tomasz Kamiński <tkami...@redhat.com> wrote: > > 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 <jwak...@redhat.com> > Signed-off-by: Tomasz Kamiński <tkami...@redhat.com> > --- > Applied changes to feature test macros. Also in queue/stack files > included bits/version.h before bits/stl_queue.h. > > OK for trunk?
Looks good, but as it moves things around between headers let's wait until after 15.1 is released. That way the contents of <bits/formatfwd.h> won't diverge too much between trunk and gcc-15. After 15.1 is released, this is OK for trunk and for gcc-15 (so that 15.2 will have full range formatting support). > libstdc++-v3/include/bits/formatfwd.h | 78 +++++++++ > libstdc++-v3/include/bits/stl_queue.h | 14 ++ > libstdc++-v3/include/bits/stl_stack.h | 9 + > libstdc++-v3/include/std/format | 76 +-------- > libstdc++-v3/include/std/queue | 80 ++++++++- > libstdc++-v3/include/std/stack | 48 +++++- > .../testsuite/std/format/ranges/adaptors.cc | 156 ++++++++++++++++++ > 7 files changed, 387 insertions(+), 74 deletions(-) > create mode 100644 libstdc++-v3/testsuite/std/format/ranges/adaptors.cc > > diff --git a/libstdc++-v3/include/bits/formatfwd.h > b/libstdc++-v3/include/bits/formatfwd.h > index a6b5ac8c8ce..9ba658b078a 100644 > --- a/libstdc++-v3/include/bits/formatfwd.h > +++ b/libstdc++-v3/include/bits/formatfwd.h > @@ -37,6 +37,12 @@ > // <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 > diff --git a/libstdc++-v3/include/bits/stl_queue.h > b/libstdc++-v3/include/bits/stl_queue.h > index 554e076aae9..a3a8bc1f0ad 100644 > --- a/libstdc++-v3/include/bits/stl_queue.h > +++ b/libstdc++-v3/include/bits/stl_queue.h > @@ -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 > diff --git a/libstdc++-v3/include/bits/stl_stack.h > b/libstdc++-v3/include/bits/stl_stack.h > index 7b324642b32..27c79d6ce58 100644 > --- a/libstdc++-v3/include/bits/stl_stack.h > +++ b/libstdc++-v3/include/bits/stl_stack.h > @@ -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 > diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format > index e557e104d74..7d3067098be 100644 > --- a/libstdc++-v3/include/std/format > +++ b/libstdc++-v3/include/std/format > @@ -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 > { > diff --git a/libstdc++-v3/include/std/queue b/libstdc++-v3/include/std/queue > index 74b6c07b49f..90525897da7 100644 > --- a/libstdc++-v3/include/std/queue > +++ b/libstdc++-v3/include/std/queue > @@ -61,14 +61,88 @@ > > #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 */ > diff --git a/libstdc++-v3/include/std/stack b/libstdc++-v3/include/std/stack > index 5cea4762a19..a57a5a08bc3 100644 > --- a/libstdc++-v3/include/std/stack > +++ b/libstdc++-v3/include/std/stack > @@ -61,11 +61,53 @@ > > #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 > index 00000000000..854c7eef5bd > --- /dev/null > +++ b/libstdc++-v3/testsuite/std/format/ranges/adaptors.cc > @@ -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>(); > +} > -- > 2.49.0 >