On Wed, 16 Apr 2025 at 10:38, Tomasz Kaminski <tkami...@redhat.com> wrote: > > > > On Wed, Apr 16, 2025 at 11:17 AM Jonathan Wakely <jwak...@redhat.com> 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 >> > >> > >>