On Wed, 16 Apr 2025 at 10:38, Tomasz Kaminski <[email protected]> wrote:
>
>
>
> On Wed, Apr 16, 2025 at 11:17 AM Jonathan Wakely <[email protected]> wrote:
>>
>> On 16/04/25 09:13 +0200, Tomasz Kamiński wrote:
>> >This patch implements formatter specializations for pair and tuple form
>> >P2286R8. In addition using 'm` and range_format::map (from P2585R1) for
>> >ranges are now supported.
>> >
>> >The formatters for pairs and tuples whose corresponding elements are the
>> >same
>> >(after applying remove_cvref_t) derive from the same __tuple_formatter
>> >class.
>> >This reduce the code duplication, as most of the parsing and formatting is
>> >the
>> >same in such cases. We use a custom reduced implementation of the tuple
>> >(__formatters_storage) to store the elements formatters.
>>
>> This is a nice solution, the code for __formatters_storage and
>> __indexed_formatter_storage is clear and concise.
>>
>> >Handling of the padding (width and fill) options, is extracted to
>> >__format::__format_padded function, that is used both by __tuple_formatter
>> >and
>> >range_formatter. To reduce number of instantations range_formatter::format
>> >triggers, we cast incoming range to __format::__maybe_const_range<_Rg,
>> >_CharT>&,
>> >before formatting it.
>> >
>> >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 last three of above as unconstrained types.
>> >
>> >Finally, test for tuple-like std::array and std::ranges::subrange,
>> >that illustrate that they remain formatted as ranges.
>> >
>> > PR libstdc++/PR109162
>> >
>> >libstdc++-v3/ChangeLog:
>> >
>> > * include/std/format (__formatter_int::_M_format_character_escaped)
>> > (__formatter_str::format): Use __sink.out() to produce _Sink_iter.
>> > (__format::__format_padded, __format::maybe_const)
>> > (__format::__indexed_formatter_storage, __format::__tuple_formatter)
>> > (std::formatter<pair<_Fp, _Sp>, _CharT>>)
>> > (std::formatter<tuple<_Tps...>, _CharT): Define.
>> > (std::formatter<_Rg, _CharT>::format): Cast incoming range to
>> > __format::__maybe_const_range<_Rg, _CharT>&.
>> > (std::formatter<_Rg, _CharT>::_M_format): Extracted from format,
>> > and use __format_padded.
>> > (std::formatter<_Rg, _CharT>::_M_format_no_padding): Rename...
>> > (std::formatter<_Rg, _CharT>::_M_format_elems): ...to this.
>> > (std::formatter<_Rg, _CharT>::_M_format_with_padding): Extracted as
>> > __format_padded.
>> > * testsuite/util/testsuite_iterators.h (test_input_range_nocopy):
>> > Define.
>> > * testsuite/std/format/ranges/formatter.cc: Tests for `m` specifier.
>> > * testsuite/std/format/ranges/sequence.cc: Tests for array and
>> > subrange.
>> > * testsuite/std/format/ranges/map.cc: New test.
>> > * testsuite/std/format/tuple.cc: New test.
>> >---
>> >Testing on x86_64-linux, tests matched by `*format*` passes.
>> >OK for trunk? Should I wait for 16?
>>
>> A few minor comments below ...
>>
>> > libstdc++-v3/include/std/format | 357 +++++++++++++++---
>> > .../testsuite/std/format/ranges/formatter.cc | 6 +-
>> > .../testsuite/std/format/ranges/map.cc | 209 ++++++++++
>> > .../testsuite/std/format/ranges/sequence.cc | 52 ++-
>> > libstdc++-v3/testsuite/std/format/tuple.cc | 259 +++++++++++++
>> > .../testsuite/util/testsuite_iterators.h | 3 +
>> > 6 files changed, 806 insertions(+), 80 deletions(-)
>> > create mode 100644 libstdc++-v3/testsuite/std/format/ranges/map.cc
>> > create mode 100644 libstdc++-v3/testsuite/std/format/tuple.cc
>> >
>> >diff --git a/libstdc++-v3/include/std/format
>> >b/libstdc++-v3/include/std/format
>> >index 096dda4f989..5b93eb8bc2d 100644
>> >--- a/libstdc++-v3/include/std/format
>> >+++ b/libstdc++-v3/include/std/format
>> >@@ -1350,8 +1350,7 @@ namespace __format
>> > __fc, _M_spec);
>> >
>> > __format::_Str_sink<_CharT> __sink;
>> >- __format::_Sink_iter<_CharT> __out(__sink);
>> >- __format::__write_escaped(__out, __s, __term);
>> >+ __format::__write_escaped(__sink.out(), __s, __term);
>> > basic_string_view<_CharT> __escaped(__sink.view().data(),
>> > __sink.view().size());
>> > const size_t __escaped_width = _S_trunc(__escaped, __prec);
>> >@@ -1387,13 +1386,13 @@ namespace __format
>> > {
>> > ranges::iterator_t<_Rg> __first = ranges::begin(__rg);
>> > ranges::subrange __sub(__first, __first + __n);
>> >- return format(_String(from_range, __sub), __fc);
>> >+ return format(_String(from_range, __sub), __fc);
>> > }
>> > else
>> > {
>> > // N.B. preserve the computed size
>> > ranges::subrange __sub(__rg, __n);
>> >- return format(_String(from_range, __sub), __fc);
>> >+ return format(_String(from_range, __sub), __fc);
>> > }
>> > }
>> > else
>> >@@ -1698,7 +1697,7 @@ namespace __format
>> > template<typename _Out>
>> > typename basic_format_context<_Out, _CharT>::iterator
>> > _M_format_character_escaped(_CharT __c,
>> >- basic_format_context<_Out, _CharT>& __fc)
>> >const
>> >+ basic_format_context<_Out, _CharT>&
>> >__fc) const
>> > {
>> > using _Esc = _Escapes<_CharT>;
>> > constexpr auto __term = __format::_Term_char::_Tc_apos;
>> >@@ -1708,8 +1707,7 @@ namespace __format
>> >
>> > _CharT __buf[12];
>> > __format::_Fixedbuf_sink<_CharT> __sink(__buf);
>> >- __format::_Sink_iter<_CharT> __out(__sink);
>> >- __format::__write_escaped(__out, __in, __term);
>> >+ __format::__write_escaped(__sink.out(), __in, __term);
>> >
>> > const basic_string_view<_CharT> __escaped = __sink.view();
>> > size_t __estimated_width;
>> >@@ -5209,6 +5207,237 @@ namespace __format
>> > /// @cond undocumented
>> > namespace __format
>> > {
>> >+ template<typename _CharT, typename _Out, typename _Callback>
>> >+ typename basic_format_context<_Out, _CharT>::iterator
>> >+ __format_padded(basic_format_context<_Out, _CharT>& __fc,
>> >+ const _Spec<_CharT>& __spec,
>> >+ _Callback&& __call)
>> >+ {
>> >+ // This is required to implement formatting with padding,
>> >+ // as we need to format to temporary buffer, using the same iterator.
>> >+ static_assert(is_same_v<_Out, __format::_Sink_iter<_CharT>>);
>> >+
>> >+ if (__spec._M_get_width(__fc) == 0)
>> >+ return __call(__fc);
>> >+
>> >+ struct _Restore_out
>> >+ {
>> >+ _Restore_out(basic_format_context<_Sink_iter<_CharT>, _CharT>& __fc)
>> >+ : _M_ctx(addressof(__fc)), _M_out(__fc.out())
>> >+ { }
>> >+
>> >+ void trigger()
>> >+ {
>> >+ if (_M_ctx)
>> >+ _M_ctx->advance_to(_M_out);
>> >+ _M_ctx = nullptr;
>> >+ }
>> >+
>> >+ ~_Restore_out()
>> >+ { trigger(); }
>> >+
>> >+ private:
>> >+ basic_format_context<_Sink_iter<_CharT>, _CharT>* _M_ctx;
>> >+ _Sink_iter<_CharT> _M_out;
>> >+ };
>> >+
>> >+ _Restore_out __restore(__fc);
>> >+ // TODO Consider double sinking, first buffer of width
>> >+ // size and then original sink, if first buffer is overun
>> >+ // we do not need to align
>> >+ _Str_sink<_CharT> __buf;
>> >+ __fc.advance_to(__buf.out());
>> >+ __call(__fc);
>> >+ __restore.trigger();
>> >+
>> >+ basic_string_view<_CharT> __str(__buf.view());
>> >+ size_t __width;
>> >+ if constexpr (__unicode::__literal_encoding_is_unicode<_CharT>())
>> >+ __width = __unicode::__field_width(__str);
>> >+ else
>> >+ __width = __str.size();
>> >+
>> >+ return __format::__write_padded_as_spec(__str, __width, __fc,
>> >__spec);
>> >+ }
>> >+
>> >+ template<typename _Tp, typename _CharT>
>> >+ using __maybe_const
>> >+ = conditional_t<formattable<const _Tp, _CharT>, const _Tp, _Tp>;
>>
>> __conditional_t is cheaper to instantiate than std::conditional_t.
>
> I wil apply this also to __maybe_const_range.
Thanks.
>>
>>
>> >+
>> >+ template<size_t _Pos, typename _Tp, typename _CharT>
>> >+ struct __indexed_formatter_storage : formatter<_Tp, _CharT>
>> >+ {
>> >+ constexpr void
>> >+ _M_parse()
>> >+ {
>> >+ basic_format_parse_context<_CharT> __pc({});
>> >+ if (_M_formatter.parse(__pc) != __pc.end())
>> >+ __format::__failed_to_parse_format_spec();
>> >+ }
>> >+
>> >+ template<typename _Out>
>> >+ void
>> >+ _M_format(__maybe_const<_Tp, _CharT>& __elem,
>> >+ basic_format_context<_Out, _CharT>& __fc,
>> >+ basic_string_view<_CharT> __sep) const
>> >+ {
>> >+ if constexpr (_Pos != 0)
>> >+ __fc.advance_to(__format::__write(__fc.out(), __sep));
>> >+ __fc.advance_to(_M_formatter.format(__elem, __fc));
>> >+ }
>> >+
>> >+ [[__gnu__::__always_inline__]]
>> >+ constexpr void
>> >+ set_debug_format()
>> >+ {
>> >+ if constexpr (__has_debug_format<formatter<_Tp, _CharT>>)
>> >+ _M_formatter.set_debug_format();
>> >+ }
>> >+
>> >+ private:
>> >+ formatter<_Tp, _CharT> _M_formatter;
>> >+ };
>> >+
>> >+ template<typename _CharT, typename... _Tps>
>> >+ class __tuple_formatter
>> >+ {
>> >+ using _String_view = basic_string_view<_CharT>;
>> >+ using _Seps = __format::_Separators<_CharT>;
>> >+
>> >+ public:
>> >+ constexpr void
>> >+ set_separator(basic_string_view<_CharT> __sep) noexcept
>> >+ { _M_sep = __sep; }
>> >+
>> >+ constexpr void
>> >+ set_brackets(basic_string_view<_CharT> __open,
>> >+ basic_string_view<_CharT> __close) noexcept
>> >+ {
>> >+ _M_open = __open;
>> >+ _M_close = __close;
>> >+ }
>> >+
>> >+ // We deviate from standard, that declares this as template accepting
>> >+ // unconstrained ParseContext type, which seems unimplementable.
>> >+ constexpr typename basic_format_parse_context<_CharT>::iterator
>> >+ parse(basic_format_parse_context<_CharT>& __pc)
>> >+ {
>> >+ auto __first = __pc.begin();
>> >+ const auto __last = __pc.end();
>> >+ __format::_Spec<_CharT> __spec{};
>> >+
>> >+ auto __finished = [&]
>> >+ {
>> >+ if (__first != __last && *__first != '}')
>> >+ return false;
>> >+
>> >+ _M_spec = __spec;
>> >+ _M_felems._M_parse();
>> >+ _M_felems.set_debug_format();
>> >+ return true;
>> >+ };
>> >+
>> >+ if (__finished())
>> >+ return __first;
>> >+
>> >+ __first = __spec._M_parse_fill_and_align(__first, __last, "{:");
>> >+ if (__finished())
>> >+ return __first;
>> >+
>> >+ __first = __spec._M_parse_width(__first, __last, __pc);
>> >+ if (__finished())
>> >+ return __first;
>> >+
>> >+ if (*__first == 'n')
>> >+ {
>> >+ ++__first;
>> >+ _M_open = _M_close = _String_view();
>> >+ }
>> >+ else if (*__first == 'm')
>> >+ {
>> >+ ++__first;
>> >+ if constexpr (sizeof...(_Tps) == 2)
>> >+ {
>> >+ _M_sep = _Seps::_S_colon();
>> >+ _M_open = _M_close = _String_view();
>> >+ }
>> >+ else
>> >+ __throw_format_error("format error: 'm' specifier requires
>> >range"
>> >+ " of pair of tuple of two elements");
>>
>> s/pair of tuple/pair or tuple/
>>
>> >+ }
>> >+
>> >+ if (__finished())
>> >+ return __first;
>> >+
>> >+ __format::__failed_to_parse_format_spec();
>> >+ }
>> >+
>> >+ protected:
>> >+ template<typename _Tuple, typename _Out, size_t... _Ids>
>> >+ typename basic_format_context<_Out, _CharT>::iterator
>> >+ _M_format(_Tuple& __tuple, index_sequence<_Ids...>,
>> >+ basic_format_context<_Out, _CharT>& __fc) const
>> >+ { return _M_format_elems(std::get<_Ids>(__tuple)..., __fc); }
>> >+
>> >+ template<typename _Out>
>> >+ typename basic_format_context<_Out, _CharT>::iterator
>> >+ _M_format_elems(__maybe_const<_Tps, _CharT>&... __elems,
>> >+ basic_format_context<_Out, _CharT>& __fc) const
>> >+ {
>> >+ return __format::__format_padded(
>> >+ __fc, _M_spec,
>> >+ [this, &__elems...](basic_format_context<_Out, _CharT>&
>> >__nfc)
>> >+ {
>> >+ __nfc.advance_to(__format::__write(__nfc.out(),
>> >_M_open));
>> >+ _M_felems._M_format(__elems..., __nfc, _M_sep);
>> >+ return __format::__write(__nfc.out(), _M_close);
>> >+ });
>> >+ }
>> >+
>> >+ private:
>> >+ template<size_t... _Ids>
>> >+ struct __formatters_storage
>> >+ : __indexed_formatter_storage<_Ids, _Tps, _CharT>...
>> >+ {
>> >+ template<size_t _Id, typename _Up>
>> >+ using _Base = __indexed_formatter_storage<_Id, _Up, _CharT>;
>> >+
>> >+ constexpr void
>> >+ _M_parse()
>> >+ {
>> >+ (_Base<_Ids, _Tps>::_M_parse(), ...);
>> >+ }
>> >+
>> >+ template<typename _Out>
>> >+ void
>> >+ _M_format(__maybe_const<_Tps, _CharT>&... __elems,
>> >+ basic_format_context<_Out, _CharT>& __fc,
>> >+ _String_view __sep) const
>> >+ {
>> >+ (_Base<_Ids, _Tps>::_M_format(__elems, __fc, __sep), ...);
>> >+ }
>> >+
>> >+ constexpr void
>> >+ set_debug_format()
>> >+ {
>> >+ (_Base<_Ids, _Tps>::set_debug_format(), ...);
>> >+ }
>> >+ };
>> >+
>> >+ template<size_t... _Ids>
>> >+ static auto
>> >+ _S_create_storage(index_sequence<_Ids...>)
>> >+ -> __formatters_storage<_Ids...>;
>> >+ using _Formatters
>> >+ = decltype(_S_create_storage(index_sequence_for<_Tps...>()));
>> >+
>> >+ _Spec<_CharT> _M_spec{};
>> >+ _String_view _M_open = _Seps::_S_parens().substr(0, 1);
>> >+ _String_view _M_close = _Seps::_S_parens().substr(1, 1);
>> >+ _String_view _M_sep = _Seps::_S_comma();
>> >+ _Formatters _M_felems;
>> >+ };
>> >+
>> > template<typename _Tp>
>> > concept __is_map_formattable
>> > = __is_pair<_Tp> || (__is_tuple_v<_Tp> && tuple_size_v<_Tp> == 2);
>> >@@ -5216,6 +5445,48 @@ namespace __format
>> > } // namespace __format
>> > /// @endcond
>> >
>> >+ // [format.tuple] Tuple formatter
>> >+ template<__format::__char _CharT, formattable<_CharT> _Fp,
>> >+ formattable<_CharT> _Sp>
>> >+ struct formatter<pair<_Fp, _Sp>, _CharT>
>> >+ : __format::__tuple_formatter<_CharT, remove_cvref_t<_Fp>,
>> >+ remove_cvref_t<_Sp>>
>> >+ {
>> >+ private:
>> >+ using __maybe_const_pair
>> >+ = conditional_t<formattable<const _Fp, _CharT>
>>
>> This can use __conditional_t too.
>>
>> >+ && formattable<const _Sp, _CharT>,
>> >+ const pair<_Fp, _Sp>, pair<_Fp, _Sp>>;
>> >+ public:
>> >+ // We deviate from standard, that declares this as template accepting
>> >+ // unconstrained FormatContext type, which seems unimplementable.
>> >+ template<typename _Out>
>> >+ typename basic_format_context<_Out, _CharT>::iterator
>> >+ format(__maybe_const_pair& __p,
>> >+ basic_format_context<_Out, _CharT>& __fc) const
>> >+ { return this->_M_format_elems(__p.first, __p.second, __fc); }
>> >+ };
>> >+
>> >+ template<__format::__char _CharT, formattable<_CharT>... _Tps>
>> >+ struct formatter<tuple<_Tps...>, _CharT>
>> >+ : __format::__tuple_formatter<_CharT, remove_cvref_t<_Tps>...>
>> >+ {
>> >+ private:
>> >+ using __maybe_const_tuple
>> >+ = conditional_t<(formattable<const _Tps, _CharT> && ...),
>>
>> And here.
>>
>> >+ const tuple<_Tps...>, tuple<_Tps...>>;
>> >+ public:
>> >+ // We deviate from standard, that declares this as template accepting
>> >+ // unconstrained FormatContext type, which seems unimplementable.
>> >+ template<typename _Out>
>> >+ typename basic_format_context<_Out, _CharT>::iterator
>> >+ format(__maybe_const_tuple& __t,
>> >+ basic_format_context<_Out, _CharT>& __fc) const
>> >+ {
>> >+ return this->_M_format(__t, index_sequence_for<_Tps...>(), __fc);
>> >+ }
>> >+ };
>> >+
>> > // [format.range.formatter], class template range_formatter
>> > template<typename _Tp, __format::__char _CharT = char>
>> > requires same_as<remove_cvref_t<_Tp>, _Tp> && formattable<_Tp, _CharT>
>> >@@ -5369,9 +5640,16 @@ namespace __format
>> > typename basic_format_context<_Out, _CharT>::iterator
>> > format(_Rg&& __rg, basic_format_context<_Out, _CharT>& __fc) const
>> > {
>> >- // This is required to implement formatting with padding,
>> >- // as we need to format to temporary buffer, using the same
>> >iterator.
>> >- static_assert(is_same_v<_Out, __format::_Sink_iter<_CharT>>);
>> >+ using __maybe_const_range
>> >+ = __format::__maybe_const_range<_Rg, _CharT>;
>> >+ return _M_format(static_cast<__maybe_const_range&>(__rg), __fc);
>>
>> Should this be const_cast not static_cast?
>> It's either casting an _Rg lvalue to const _Rg& or _Rg&, right?
>> If we don't need any type conversion, just potential cv-qualification,
>> then I think const_cast is safer/clearer.
>
> I connect const_cast with removing constness, which is not happening here.
> So I always think of it as "less" safe.
OK, that makes sense. The static_cast is fine then.
>>
>>
>> >+ }
>> >+
>> >+ private:
>> >+ template<ranges::input_range _Rg, typename _Out>
>> >+ typename basic_format_context<_Out, _CharT>::iterator
>> >+ _M_format(_Rg& __rg, basic_format_context<_Out, _CharT>& __fc) const
>> >+ {
>> > if constexpr (same_as<_Tp, _CharT>)
>> > if (_M_spec._M_type == __format::_Pres_str
>> > || _M_spec._M_type == __format::_Pres_esc)
>> >@@ -5379,16 +5657,17 @@ namespace __format
>> > __format::__formatter_str __fstr(_M_spec);
>> > return __fstr._M_format_range(__rg, __fc);
>> > }
>> >- if (_M_spec._M_get_width(__fc) > 0)
>> >- return _M_format_with_padding(__rg, __fc);
>> >- return _M_format_no_padding(__rg, __fc);
>> >+ return __format::__format_padded(
>> >+ __fc, _M_spec,
>> >+ [this, &__rg](basic_format_context<_Out, _CharT>& __nfc)
>> >+ { return _M_format_elems(__rg, __nfc); });
>> > }
>> >
>> >- private:
>> >+
>> > template<ranges::input_range _Rg, typename _Out>
>> > typename basic_format_context<_Out, _CharT>::iterator
>> >- _M_format_no_padding(_Rg& __rg,
>> >- basic_format_context<_Out, _CharT>& __fc) const
>> >+ _M_format_elems(_Rg& __rg,
>> >+ basic_format_context<_Out, _CharT>& __fc) const
>> > {
>> > auto __out = __format::__write(__fc.out(), _M_open);
>> >
>> >@@ -5409,50 +5688,6 @@ namespace __format
>> > return __format::__write(__out, _M_close);
>> > }
>> >
>> >- template<ranges::input_range _Rg, typename _Out>
>> >- typename basic_format_context<_Out, _CharT>::iterator
>> >- _M_format_with_padding(_Rg& __rg,
>> >- basic_format_context<_Out, _CharT>& __fc)
>> >const
>> >- {
>> >- struct _Restore_out
>> >- {
>> >- _Restore_out(basic_format_context<_Out, _CharT>& __fc)
>> >- : _M_ctx(addressof(__fc)), _M_out(__fc.out())
>> >- { }
>> >-
>> >- void trigger()
>> >- {
>> >- if (_M_ctx)
>> >- _M_ctx->advance_to(_M_out);
>> >- _M_ctx = nullptr;
>> >- }
>> >-
>> >- ~_Restore_out()
>> >- { trigger(); }
>> >-
>> >- private:
>> >- basic_format_context<_Out, _CharT>* _M_ctx;
>> >- __format::_Sink_iter<_CharT> _M_out;
>> >- };
>> >-
>> >- _Restore_out __restore{__fc};
>> >- // TODO Consider double sinking, first buffer of width
>> >- // size and then original sink, if first buffer is overrun
>> >- // we do not need to align
>> >- __format::_Str_sink<_CharT> __buf;
>> >- __fc.advance_to(__format::_Sink_iter<_CharT>(__buf));
>> >- _M_format_no_padding(__rg, __fc);
>> >- __restore.trigger();
>> >-
>> >- _String_view __s(__buf.view());
>> >- size_t __width;
>> >- if constexpr (__unicode::__literal_encoding_is_unicode<_CharT>())
>> >- __width = __unicode::__field_width(__s);
>> >- else
>> >- __width = __s.size();
>> >- return __format::__write_padded_as_spec(__s, __width, __fc,
>> >_M_spec);
>> >- }
>> >-
>> > __format::_Spec<_CharT> _M_spec{};
>> > _String_view _M_open = _Seps::_S_squares().substr(0, 1);
>> > _String_view _M_close = _Seps::_S_squares().substr(1, 1);
>> >@@ -5537,7 +5772,7 @@ namespace __format
>> > return _M_under._M_format_range(__rg, __fc);
>> > else
>> > return _M_under.format(__rg, __fc);
>> >- }
>> >+ }
>> >
>> > private:
>> > using _Formatter_under
>> >diff --git a/libstdc++-v3/testsuite/std/format/ranges/formatter.cc
>> >b/libstdc++-v3/testsuite/std/format/ranges/formatter.cc
>> >index 2045b51547a..a4f5d9210dd 100644
>> >--- a/libstdc++-v3/testsuite/std/format/ranges/formatter.cc
>> >+++ b/libstdc++-v3/testsuite/std/format/ranges/formatter.cc
>> >@@ -97,6 +97,7 @@ void
>> > test_override()
>> > {
>> > MyVector<_CharT, Formatter> vc{'a', 'b', 'c', 'd'};
>> >+ MyVector<std::pair<int, int>, Formatter> vp{{1, 11}, {2, 21}};
>> > std::basic_string<_CharT> res;
>> >
>> > res = std::format(WIDEN("{:s}"), vc);
>> >@@ -106,7 +107,10 @@ test_override()
>> > res = std::format(WIDEN("{:+^6s}"), vc);
>> > VERIFY( res == WIDEN("+abcd+") );
>> >
>> >- // TODO test map
>> >+ res = std::format(WIDEN("{:m}"), vp);
>> >+ VERIFY( res == WIDEN("{1: 11, 2: 21}") );
>> >+ res = std::format(WIDEN("{:=^20m}"), vp);
>> >+ VERIFY( res == WIDEN("==={1: 11, 2: 21}===") );
>> > }
>> >
>> > template<template<typename, typename> class Formatter>
>> >diff --git a/libstdc++-v3/testsuite/std/format/ranges/map.cc
>> >b/libstdc++-v3/testsuite/std/format/ranges/map.cc
>> >new file mode 100644
>> >index 00000000000..34c5ed554b8
>> >--- /dev/null
>> >+++ b/libstdc++-v3/testsuite/std/format/ranges/map.cc
>> >@@ -0,0 +1,209 @@
>> >+// { dg-do run { target c++23 } }
>> >+
>> >+#include <flat_map>
>> >+#include <format>
>> >+#include <list>
>> >+#include <map>
>> >+#include <span>
>> >+#include <testsuite_hooks.h>
>> >+#include <testsuite_iterators.h>
>> >+#include <vector>
>> >+
>> >+struct NotFormattable
>> >+{
>> >+ friend auto operator<=>(NotFormattable, NotFormattable) = default;
>> >+};
>> >+
>> >+static_assert( !std::formattable<std::map<int, NotFormattable>, char> );
>> >+static_assert( !std::formattable<std::map<NotFormattable, int>, wchar_t> );
>> >+
>> >+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;
>> >+ }
>> >+}
>> >+
>> >+template<typename... Args>
>> >+bool
>> >+is_format_string_for(const wchar_t* str, Args&&... args)
>> >+{
>> >+ try {
>> >+ (void) std::vformat(str, std::make_wformat_args(args...));
>> >+ return true;
>> >+ } catch (const std::format_error&) {
>> >+ return false;
>> >+ }
>> >+}
>> >+
>> >+template<typename Rg, typename CharT>
>> >+bool is_range_formatter_spec_for(CharT const* spec, Rg&& rg)
>> >+{
>> >+ using V = std::remove_cvref_t<std::ranges::range_reference_t<Rg>>;
>> >+ std::range_formatter<V, CharT> fmt;
>> >+ std::basic_format_parse_context<CharT> pc(spec);
>> >+ try {
>> >+ (void)fmt.parse(pc);
>> >+ 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)
>> >+
>> >+void
>> >+test_format_string()
>> >+{
>> >+ // only pair<T, U> amd tuple<T, U> value types are supported
>> >+ VERIFY( !is_range_formatter_spec_for("m", std::vector<int>()) );
>> >+ VERIFY( !is_format_string_for("{:m}", std::vector<int>()) );
>> >+ VERIFY( !is_range_formatter_spec_for("m", std::vector<std::tuple<int,
>> >int, int>>()) );
>> >+ VERIFY( !is_format_string_for("{:m}", std::vector<std::tuple<int, int,
>> >int>>()) );
>> >+
>> >+ // invalid format stringss
>> >+ VERIFY( !is_range_formatter_spec_for("?m", std::vector<std::pair<int,
>> >int>>()) );
>> >+ VERIFY( !is_format_string_for("{:?m}", std::vector<std::pair<int,
>> >int>>()) );
>> >+ VERIFY( !is_range_formatter_spec_for("m:", std::vector<std::pair<int,
>> >int>>()) );
>> >+ VERIFY( !is_format_string_for("{:m:}", std::vector<std::pair<int,
>> >int>>()) );
>> >+
>> >+ // precision is not supported
>> >+ VERIFY( !is_range_formatter_spec_for(".10m", std::vector<std::pair<int,
>> >int>>()) );
>> >+ VERIFY( !is_format_string_for("{:.10m}", std::vector<std::pair<int,
>> >int>>()) );
>> >+ VERIFY( !is_format_string_for("{:.{}m}", std::vector<std::pair<int,
>> >int>>(), 10) );
>> >+
>> >+ // width needs to be integer type
>> >+ VERIFY( !is_format_string_for("{:{}m}", std::vector<std::pair<int,
>> >int>>(), 1.0f) );
>> >+}
>> >+
>> >+template<typename _CharT, typename Range>
>> >+void test_output(bool mapIsDefault)
>> >+{
>> >+ using Sv = std::basic_string_view<_CharT>;
>> >+ using Pt = std::ranges::range_value_t<Range>;
>> >+ using Ft = std::remove_cvref_t<std::tuple_element_t<0, Pt>>;
>> >+ using St = std::remove_cvref_t<std::tuple_element_t<1, Pt>>;
>> >+ auto makeRange = [](std::span<Pt> s) {
>> >+ return Range(s.data(), s.data() + s.size());
>> >+ };
>> >+
>> >+ std::basic_string<_CharT> res;
>> >+ size_t size = 0;
>> >+
>> >+ Ft f1[]{1, 2, 3};
>> >+ St s1[]{11, 22, 33};
>> >+ Pt v1[]{{f1[0], s1[0]}, {f1[1], s1[1]}, {f1[2], s1[2]}};
>> >+
>> >+ res = std::format(WIDEN("{}"), makeRange(v1));
>> >+ if (mapIsDefault)
>> >+ VERIFY( res == WIDEN("{1: 11, 2: 22, 3: 33}") );
>> >+ else
>> >+ VERIFY( res == WIDEN("[(1, 11), (2, 22), (3, 33)]") );
>> >+
>> >+ res = std::format(WIDEN("{:m}"), makeRange(v1));
>> >+ VERIFY( res == WIDEN("{1: 11, 2: 22, 3: 33}") );
>> >+ res = std::format(WIDEN("{:nm}"), makeRange(v1));
>> >+ VERIFY( res == WIDEN("1: 11, 2: 22, 3: 33") );
>> >+
>> >+ res = std::format(WIDEN("{:3m}"), makeRange(v1));
>> >+ VERIFY( res == WIDEN("{1: 11, 2: 22, 3: 33}") );
>> >+
>> >+ res = std::format(WIDEN("{:25m}"), makeRange(v1));
>> >+ VERIFY( res == WIDEN("{1: 11, 2: 22, 3: 33} ") );
>> >+
>> >+ res = std::format(WIDEN("{:{}m}"), makeRange(v1), 25);
>> >+ VERIFY( res == WIDEN("{1: 11, 2: 22, 3: 33} ") );
>> >+
>> >+ res = std::format(WIDEN("{1:{0}m}"), 25, makeRange(v1));
>> >+ VERIFY( res == WIDEN("{1: 11, 2: 22, 3: 33} ") );
>> >+
>> >+ res = std::format(WIDEN("{:25nm}"), makeRange(v1));
>> >+ VERIFY( res == WIDEN("1: 11, 2: 22, 3: 33 ") );
>> >+
>> >+ res = std::format(WIDEN("{:*<23m}"), makeRange(v1));
>> >+ VERIFY( res == WIDEN("{1: 11, 2: 22, 3: 33}**") );
>> >+
>> >+ res = std::format(WIDEN("{:->24m}"), makeRange(v1));
>> >+ VERIFY( res == WIDEN("---{1: 11, 2: 22, 3: 33}") );
>> >+
>> >+ res = std::format(WIDEN("{:=^25m}"), makeRange(v1));
>> >+ VERIFY( res == WIDEN("=={1: 11, 2: 22, 3: 33}==") );
>> >+
>> >+ res = std::format(WIDEN("{:=^25nm}"), makeRange(v1));
>> >+ VERIFY( res == WIDEN("===1: 11, 2: 22, 3: 33===") );
>> >+
>> >+ size = std::formatted_size(WIDEN("{:m}"), makeRange(v1));
>> >+ VERIFY( size == Sv(WIDEN("{1: 11, 2: 22, 3: 33}")).size() );
>> >+
>> >+ size = std::formatted_size(WIDEN("{:3m}"), makeRange(v1));
>> >+ VERIFY( size == Sv(WIDEN("{1: 11, 2: 22, 3: 33}")).size() );
>> >+
>> >+ size = std::formatted_size(WIDEN("{:25m}"), makeRange(v1));
>> >+ VERIFY( size == 25 );
>> >+}
>> >+
>> >+template<class Range>
>> >+void test_output_c(bool mapIsDefault = false)
>> >+{
>> >+ test_output<char, Range>(mapIsDefault);
>> >+ test_output<wchar_t, Range>(mapIsDefault);
>> >+}
>> >+
>> >+template<template<typename> class RangeT>
>> >+void test_output_pc()
>> >+{
>> >+ test_output_c<RangeT<std::pair<int, int>>>();
>> >+ test_output_c<RangeT<std::pair<const int, int>>>();
>> >+ test_output_c<RangeT<std::tuple<const int&, int&>>>();
>> >+}
>> >+
>> >+void
>> >+test_outputs()
>> >+{
>> >+ using namespace __gnu_test;
>> >+ test_output_c<std::map<int, int>>(true);
>> >+ test_output_c<std::flat_map<int, int>>(true);
>> >+
>> >+ test_output_pc<std::vector>();
>> >+ test_output_pc<std::list>();
>> >+ test_output_pc<std::span>();
>> >+
>> >+ test_output_pc<test_forward_range>();
>> >+ test_output_pc<test_input_range>();
>> >+ test_output_pc<test_input_range_nocopy>();
>> >+}
>> >+
>> >+void
>> >+test_nested()
>> >+{
>> >+ std::vector<std::map<int, std::string>> vm{
>> >+ {{1, "one"}, {2, "two"}},
>> >+ {{1, "jeden"}, {2, "dwa"}},
>> >+ };
>> >+ std::string res;
>> >+
>> >+ res = std::format("{}", vm);
>> >+ VERIFY( res == R"([{1: "one", 2: "two"}, {1: "jeden", 2: "dwa"}])" );
>> >+ res = std::format("{:n:n}", vm);
>> >+ VERIFY( res == R"(1: "one", 2: "two", 1: "jeden", 2: "dwa")" );
>> >+
>> >+ std::map<std::string, std::vector<std::string>> mv{
>> >+ {"english", {"zero", "one", "two"}},
>> >+ {"polish", {"zero", "jeden", "dwa"}},
>> >+ };
>> >+ res = std::format("{}", mv);
>> >+ VERIFY( res == R"({"english": ["zero", "one", "two"], "polish": ["zero",
>> >"jeden", "dwa"]})" );
>> >+}
>> >+
>> >+int main()
>> >+{
>> >+ test_format_string();
>> >+ test_outputs();
>> >+ test_nested();
>> >+}
>> >diff --git a/libstdc++-v3/testsuite/std/format/ranges/sequence.cc
>> >b/libstdc++-v3/testsuite/std/format/ranges/sequence.cc
>> >index 06574379ed5..61fc68ea252 100644
>> >--- a/libstdc++-v3/testsuite/std/format/ranges/sequence.cc
>> >+++ b/libstdc++-v3/testsuite/std/format/ranges/sequence.cc
>> >@@ -1,7 +1,9 @@
>> > // { dg-do run { target c++23 } }
>> >
>> >+#include <array>
>> > #include <format>
>> > #include <list>
>> >+#include <ranges>
>> > #include <span>
>> > #include <testsuite_hooks.h>
>> > #include <testsuite_iterators.h>
>> >@@ -73,19 +75,23 @@ test_format_string()
>> > #define WIDEN_(C, S) ::std::__format::_Widen<C>(S, L##S)
>> > #define WIDEN(S) WIDEN_(_CharT, S)
>> >
>> >-template<typename _CharT, typename Range>
>> >+template<typename _CharT, typename Range, typename Storage>
>> > void test_output()
>> > {
>> > using Sv = std::basic_string_view<_CharT>;
>> > using T = std::ranges::range_value_t<Range>;
>> >- auto makeRange = [](std::span<T> s) {
>> >- return Range(s.data(), s.data() + s.size());
>> >+ auto makeRange = [](Storage& s) -> Range {
>> >+ if constexpr (std::is_same_v<std::remove_cvref_t<Range>, Storage>)
>> >+ return s;
>> >+ else
>> >+ return Range(std::ranges::data(s),
>> >+ std::ranges::data(s) + std::ranges::size(s));
>> > };
>> >
>> > std::basic_string<_CharT> res;
>> > size_t size = 0;
>> >
>> >- T v1[]{1, 2, 3};
>> >+ Storage v1{1, 2, 3};
>> > res = std::format(WIDEN("{}"), makeRange(v1));
>> > VERIFY( res == WIDEN("[1, 2, 3]") );
>> > res = std::format(WIDEN("{:}"), makeRange(v1));
>> >@@ -143,27 +149,37 @@ void test_output()
>> > VERIFY( size == 25 );
>> > }
>> >
>> >-template<typename Range>
>> >-void test_output_c()
>> >+template<typename Cont>
>> >+void test_output_cont()
>> > {
>> >- test_output<char, Range>();
>> >- test_output<wchar_t, Range>();
>> >+ test_output<char, Cont&, Cont>();
>> >+ test_output<wchar_t, Cont const&, Cont>();
>> >+}
>> >+
>> >+template<typename View>
>> >+void test_output_view()
>> >+{
>> >+ test_output<char, View, int[3]>();
>> >+ test_output<wchar_t, View, int[3]>();
>> > }
>> >
>> > void
>> > test_outputs()
>> > {
>> > using namespace __gnu_test;
>> >- test_output_c<std::vector<int>>();
>> >- test_output_c<std::list<int>>();
>> >- test_output_c<std::span<int>>();
>> >-
>> >- test_output_c<test_forward_range<int>>();
>> >- test_output_c<test_input_range<int>>();
>> >- test_output_c<test_range_nocopy<int, input_iterator_wrapper_nocopy>>();
>> >-
>> >- test_output_c<std::span<const int>>();
>> >- test_output_c<test_forward_range<const int>>();
>> >+ test_output_cont<std::vector<int>>();
>> >+ test_output_cont<std::list<int>>();
>> >+ test_output_cont<std::array<int, 3>>();
>> >+
>> >+ test_output_view<std::span<int>>();
>> >+ test_output_view<std::ranges::subrange<int*>>();
>> >+ test_output_view<test_forward_range<int>>();
>> >+ test_output_view<test_input_range<int>>();
>> >+ test_output_view<test_input_range_nocopy<int>>();
>> >+
>> >+ test_output_view<std::span<const int>>();
>> >+ test_output_view<std::ranges::subrange<const int*>>();
>> >+ test_output_view<test_forward_range<const int>>();
>> > }
>> >
>> > void
>> >diff --git a/libstdc++-v3/testsuite/std/format/tuple.cc
>> >b/libstdc++-v3/testsuite/std/format/tuple.cc
>> >new file mode 100644
>> >index 00000000000..62f9d293aab
>> >--- /dev/null
>> >+++ b/libstdc++-v3/testsuite/std/format/tuple.cc
>> >@@ -0,0 +1,259 @@
>> >+// { dg-do run { target c++23 } }
>> >+
>> >+#include <format>
>> >+#include <string>
>> >+#include <testsuite_hooks.h>
>> >+#include <tuple>
>> >+#include <utility>
>> >+
>> >+struct NotFormattable
>> >+{};
>> >+
>> >+static_assert( !std::formattable<std::pair<int, NotFormattable>, char> );
>> >+static_assert( !std::formattable<std::tuple<int, NotFormattable, int>,
>> >wchar_t> );
>> >+
>> >+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;
>> >+ }
>> >+}
>> >+
>> >+template<typename... Args>
>> >+bool
>> >+is_format_string_for(const wchar_t* str, Args&&... args)
>> >+{
>> >+ try {
>> >+ (void) std::vformat(str, std::make_wformat_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)
>> >+
>> >+void
>> >+test_format_string()
>> >+{
>> >+ // invalid format stringss
>> >+ VERIFY( !is_format_string_for("{:p}", std::tuple<>()) );
>> >+ VERIFY( !is_format_string_for("{:nm}", std::tuple<>()) );
>> >+
>> >+ // 'm' is only valid for 2 elemenst
>> >+ VERIFY( !is_format_string_for("{:m}", std::tuple<>()) );
>> >+ VERIFY( !is_format_string_for("{:m}", std::tuple<int, int, int>()) );
>> >+
>> >+ // element specifier is not supported
>> >+ VERIFY( !is_format_string_for("{::}", std::tuple<>()) );
>> >+
>> >+ // precision is not supported
>> >+ VERIFY( !is_format_string_for("{:.10}", std::tuple<>()) );
>> >+
>> >+ // width needs to be integer type
>> >+ VERIFY( !is_format_string_for("{:{}}", std::tuple<>(), 1.0f) );
>> >+}
>> >+
>> >+template<typename _CharT>
>> >+void test_multi()
>> >+{
>> >+ using Sv = std::basic_string_view<_CharT>;
>> >+ using Str = std::basic_string<_CharT>;
>> >+
>> >+ std::basic_string<_CharT> res;
>> >+ std::size_t size = 0;
>> >+ std::tuple<int, Str, float> t1(1, WIDEN("test"), 2.1);
>> >+
>> >+ res = std::format(WIDEN("{}"), t1);
>> >+ VERIFY( res == WIDEN(R"((1, "test", 2.1))") );
>> >+ res = std::format(WIDEN("{:}"), t1);
>> >+ VERIFY( res == WIDEN(R"((1, "test", 2.1))") );
>> >+ res = std::format(WIDEN("{:n}"), t1);
>> >+ VERIFY( res == WIDEN(R"(1, "test", 2.1)") );
>> >+
>> >+ res = std::format(WIDEN("{:3}"), t1);
>> >+ VERIFY( res == WIDEN(R"((1, "test", 2.1))") );
>> >+
>> >+ res = std::format(WIDEN("{:20}"), t1);
>> >+ VERIFY( res == WIDEN(R"((1, "test", 2.1) )") );
>> >+
>> >+ res = std::format(WIDEN("{:{}}"), t1, 20);
>> >+ VERIFY( res == WIDEN(R"((1, "test", 2.1) )") );
>> >+
>> >+ res = std::format(WIDEN("{1:{0}}"), 20, t1);
>> >+ VERIFY( res == WIDEN(R"((1, "test", 2.1) )") );
>> >+
>> >+ res = std::format(WIDEN("{:^>17}"), t1);
>> >+ VERIFY( res == WIDEN(R"(^(1, "test", 2.1))") );
>> >+
>> >+ res = std::format(WIDEN("{:$<18}"), t1);
>> >+ VERIFY( res == WIDEN(R"((1, "test", 2.1)$$)") );
>> >+
>> >+ res = std::format(WIDEN("{:+^19}"), t1);
>> >+ VERIFY( res == WIDEN(R"(+(1, "test", 2.1)++)") );
>> >+
>> >+ res = std::format(WIDEN("{:|^19n}"), t1);
>> >+ VERIFY( res == WIDEN(R"(||1, "test", 2.1|||)") );
>> >+
>> >+ size = std::formatted_size(WIDEN("{}"), t1);
>> >+ VERIFY( size == Sv(WIDEN(R"((1, "test", 2.1))")).size() );
>> >+
>> >+ size = std::formatted_size(WIDEN("{:3}"), t1);
>> >+ VERIFY( size == Sv(WIDEN(R"((1, "test", 2.1))")).size() );
>> >+
>> >+ size = std::formatted_size(WIDEN("{:20}"), t1);
>> >+ VERIFY( size == 20 );
>> >+
>> >+ std::tuple<int&, Str&, float&> t2 = t1;
>> >+ res = std::format(WIDEN("{}"), t2);
>> >+ VERIFY( res == WIDEN(R"((1, "test", 2.1))") );
>> >+
>> >+ std::tuple<int, int, int, int> t3(1, 2, 3, 4);
>> >+ res = std::format(WIDEN("{}"), t3);
>> >+ VERIFY( res == WIDEN(R"((1, 2, 3, 4))") );
>> >+
>> >+}
>> >+
>> >+template<typename _CharT, typename Tuple>
>> >+void test_empty()
>> >+{
>> >+ std::basic_string<_CharT> res;
>> >+
>> >+ Tuple e1;
>> >+ res = std::format(WIDEN("{}"), e1);
>> >+ VERIFY( res == WIDEN(R"(())") );
>> >+
>> >+ res = std::format(WIDEN("{:}"), e1);
>> >+ VERIFY( res == WIDEN(R"(())") );
>> >+
>> >+ res = std::format(WIDEN("{:n}"), e1);
>> >+ VERIFY( res == WIDEN(R"()") );
>> >+
>> >+ res = std::format(WIDEN("{:^>6}"), e1);
>> >+ VERIFY( res == WIDEN(R"(^^^^())") );
>> >+}
>> >+
>> >+template<typename _CharT, typename Pair>
>> >+void test_pair()
>> >+{
>> >+ using Ft = std::remove_cvref_t<std::tuple_element_t<0, Pair>>;
>> >+ using St = std::remove_cvref_t<std::tuple_element_t<1, Pair>>;
>> >+
>> >+ std::basic_string<_CharT> res;
>> >+
>> >+ Ft f1 = 1;
>> >+ St s1 = WIDEN("abc");
>> >+ Pair p1(f1, s1);
>> >+
>> >+ res = std::format(WIDEN("{}"), p1);
>> >+ VERIFY( res == WIDEN(R"((1, "abc"))") );
>> >+
>> >+ res = std::format(WIDEN("{:}"), p1);
>> >+ VERIFY( res == WIDEN(R"((1, "abc"))") );
>> >+
>> >+ res = std::format(WIDEN("{:m}"), p1);
>> >+ VERIFY( res == WIDEN(R"(1: "abc")") );
>> >+
>> >+ res = std::format(WIDEN("{:|^12m}"), p1);
>> >+ VERIFY( res == WIDEN(R"(||1: "abc"||)") );
>> >+}
>> >+
>> >+template<typename CharT, template<typename, typename> class PairT>
>> >+void test_pair_e()
>> >+{
>> >+ test_pair<CharT, PairT<int, std::basic_string<CharT>>>();
>> >+ test_pair<CharT, PairT<int, const CharT*>>();
>> >+ test_pair<CharT, PairT<const int, std::basic_string<CharT>>>();
>> >+ test_pair<CharT, PairT<int&, std::basic_string<CharT>&>>();
>> >+ test_pair<CharT, PairT<const int&, const std::basic_string<CharT>&>>();
>> >+}
>> >+
>> >+template<typename Pair>
>> >+struct MyPair : Pair
>> >+{
>> >+ using Pair::Pair;
>> >+};
>> >+
>> >+template<typename Pair, typename CharT>
>> >+struct std::formatter<MyPair<Pair>, CharT>
>> >+{
>> >+ constexpr formatter() noexcept
>> >+ {
>> >+ using _CharT = CharT;
>> >+ _formatter.set_brackets(WIDEN("<"), WIDEN(">"));
>> >+ _formatter.set_separator(WIDEN("; "));
>> >+ }
>> >+
>> >+ constexpr std::basic_format_parse_context<CharT>::iterator
>> >+ parse(std::basic_format_parse_context<CharT>& pc)
>> >+ { return _formatter.parse(pc); }
>> >+
>> >+ template<typename Out>
>> >+ typename std::basic_format_context<Out, CharT>::iterator
>> >+ format(const MyPair<Pair>& mp,
>> >+ std::basic_format_context<Out, CharT>& fc) const
>> >+ { return _formatter.format(mp, fc); }
>> >+
>> >+private:
>> >+ std::formatter<Pair, CharT> _formatter;
>> >+};
>> >+
>> >+template<typename _CharT, template<typename, typename> class PairT>
>> >+void test_custom()
>> >+{
>> >+ std::basic_string<_CharT> res;
>> >+ MyPair<PairT<int, const _CharT*>> c1(1, WIDEN("abc"));
>> >+
>> >+ res = std::format(WIDEN("{}"), c1);
>> >+ VERIFY( res == WIDEN(R"(<1; "abc">)") );
>> >+
>> >+ res = std::format(WIDEN("{:}"), c1);
>> >+ VERIFY( res == WIDEN(R"(<1; "abc">)") );
>> >+
>> >+ res = std::format(WIDEN("{:n}"), c1);
>> >+ VERIFY( res == WIDEN(R"(1; "abc")") );
>> >+
>> >+ res = std::format(WIDEN("{:m}"), c1);
>> >+ VERIFY( res == WIDEN(R"(1: "abc")") );
>> >+
>> >+ res = std::format(WIDEN("{:|^14}"), c1);
>> >+ VERIFY( res == WIDEN(R"(||<1; "abc">||)") );
>> >+}
>> >+
>> >+template<typename CharT>
>> >+void test_outputs()
>> >+{
>> >+ test_multi<CharT>();
>> >+ test_empty<CharT, std::tuple<>>();
>> >+ test_pair_e<CharT, std::pair>();
>> >+ test_pair_e<CharT, std::tuple>();
>> >+ test_custom<CharT, std::pair>();
>> >+ test_custom<CharT, std::tuple>();
>> >+}
>> >+
>> >+void test_nested()
>> >+{
>> >+ std::string res;
>> >+ std::tuple<std::tuple<>, std::pair<int, std::string>> tt{{}, {1, "abc"}};
>> >+
>> >+ res = std::format("{}", tt);
>> >+ VERIFY( res == R"(((), (1, "abc")))" );
>> >+ res = std::format("{:n}", tt);
>> >+ VERIFY( res == R"((), (1, "abc"))" );
>> >+ res = std::format("{:m}", tt);
>> >+ VERIFY( res == R"((): (1, "abc"))" );
>> >+}
>> >+
>> >+int main()
>> >+{
>> >+ test_format_string();
>> >+ test_outputs<char>();
>> >+ test_outputs<wchar_t>();
>> >+ test_nested();
>> >+}
>> >diff --git a/libstdc++-v3/testsuite/util/testsuite_iterators.h
>> >b/libstdc++-v3/testsuite/util/testsuite_iterators.h
>> >index 20539ecaca6..74a87395cd7 100644
>> >--- a/libstdc++-v3/testsuite/util/testsuite_iterators.h
>> >+++ b/libstdc++-v3/testsuite/util/testsuite_iterators.h
>> >@@ -891,6 +891,9 @@ namespace __gnu_test
>> > template<typename T>
>> > using test_input_range
>> > = test_range<T, input_iterator_wrapper>;
>> >+ template<typename T>
>> >+ using test_input_range_nocopy
>> >+ = test_range_nocopy<T, input_iterator_wrapper_nocopy>;
>> > template<typename T>
>> > using test_output_range
>> > = test_range<T, output_iterator_wrapper>;
>> >--
>> >2.49.0
>> >
>> >
>>