Just to clarify, we still will be missing the formatter for adaptors (stack, queue, piority_queue).
On Wed, Apr 16, 2025 at 10:04 AM Jonathan Wakely <jwak...@redhat.com> wrote: > On Wed, 16 Apr 2025 at 08:15, Tomasz Kamiński <tkami...@redhat.com> 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. > > > > 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? > > I'd like to get this in trunk before we branch for gcc-15, if possible. > > > > > 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()) > > std::addressof > > > + { } > > + > > + void trigger() > > This is not a reserved name. > > I'll continue the review ASAP ... > > > + { > > + 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>; > > + > > + 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"); > > + } > > + > > + 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> > > + && 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> && ...), > > + 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); > > + } > > + > > + 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 > > > >