On Tue, 22 Apr 2025 at 12:19, Tomasz Kamiński <[email protected]> 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 <[email protected]>
> Signed-off-by: Tomasz Kamiński <[email protected]>
> ---
> 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
>