On Fri, Apr 25, 2025 at 11:49 AM Jonathan Wakely <jwak...@redhat.com> wrote:
> On 23/04/25 13:56 +0200, Tomasz Kamiński wrote: > >When width parameter is specified for formatting range, tuple or escaped > >presentation of string, we used to format characters to temporary string, > >and write produce sequence padded according to the spec. However, once the > >estimated width of formatted representation of input is larger than the > value > >of spec width, it can be written directly to the output. This limits size > of > >required allocation, especially for large ranges. > > > >Similarly, if precision (maximum) width is provided for string > presentation, > >on a prefix of sequence with estimated width not greater than precision, > needs > >to be buffered. > > > >To realize above, this commit implements a new _Padding_sink > specialization. > >This sink holds an output iterator, a value of padding width, (optionally) > >maximum width and a string buffer inherited from _Str_sink. > >Then any incoming characters are treated in one of following ways, > depending of > >estimated width W of written sequence: > >* written to string if W is smaller than padding width and maximum width > (if present) > >* ignored, if W is greater than maximum width > >* written to output iterator, if W is greater than padding width > > > >The padding sink is used instead of _Str_sink in > __format::__format_padded, > >__formatter_str::_M_format_escaped functions. > > > >Furthermore __formatter_str::_M_format implementation was reworked, to: > >* reduce number of instantiations by delegating to _Rg& and const _Rg& > overloads, > >* non-debug presentation is written to _Out directly or via _Padding_sink > >* if maximum width is specified for debug format with non-unicode > encoding, > > string size is limited to that number. > > > > PR libstdc++/109162 > > > >libstdc++-v3/ChangeLog: > > > > * include/bits/formatfwd.h (__simply_formattable_range): Moved from > > std/format. > > * include/std/format (__formatter_str::_format): Extracted escaped > > string handling to separate method... > > (__formatter_str::_M_format_escaped): Use __Padding_sink. > > (__formatter_str::_M_format): Adjusted implementation. > > (__formatter_str::_S_trunc): Extracted as namespace function... > > (__format::_truncate): Extracted from __formatter_str::_S_trunc. > > (__format::_Seq_sink): Removed forward declarations, made members > > protected and non-final. > > (_Seq_sink::_M_trim): Define. > > (_Seq_sink::_M_span): Renamed from view. > > (_Seq_sink::view): Returns string_view instead of span. > > (__format::_Str_sink): Moved after _Seq_sink. > > (__format::__format_padded): Use _Padding_sink. > > * testsuite/std/format/debug.cc: Add timeout and new tests. > > * testsuite/std/format/ranges/sequence.cc: Specify unicode as > > encoding and new tests. > > * testsuite/std/format/ranges/string.cc: Likewise. > > * testsuite/std/format/tuple.cc: Likewise. > >--- > >This is for sure 16 material, and nothing to backport. > >This addressed the TODO I created in __format_padded. > >OK for trunk after 15.1? > > > This is a nice improvement. > > OK with the spelling and minor tweaks mentioned below ... > > > > libstdc++-v3/include/bits/formatfwd.h | 8 + > > libstdc++-v3/include/std/format | 396 +++++++++++++----- > > libstdc++-v3/testsuite/std/format/debug.cc | 386 ++++++++++++++++- > > .../testsuite/std/format/ranges/sequence.cc | 116 +++++ > > .../testsuite/std/format/ranges/string.cc | 63 +++ > > libstdc++-v3/testsuite/std/format/tuple.cc | 93 ++++ > > 6 files changed, 957 insertions(+), 105 deletions(-) > > > >diff --git a/libstdc++-v3/include/bits/formatfwd.h > b/libstdc++-v3/include/bits/formatfwd.h > >index 9ba658b078a..2d54ee5d30b 100644 > >--- a/libstdc++-v3/include/bits/formatfwd.h > >+++ b/libstdc++-v3/include/bits/formatfwd.h > >@@ -131,6 +131,14 @@ namespace __format > > = ranges::input_range<const _Rg> > > && formattable<ranges::range_reference_t<const _Rg>, _CharT>; > > > >+ // _Rg& and const _Rg& are both formattable and use same formatter > >+ // specialization for their references. > >+ template<typename _Rg, typename _CharT> > >+ concept __simply_formattable_range > >+ = __const_formattable_range<_Rg, _CharT> > >+ && same_as<remove_cvref_t<ranges::range_reference_t<_Rg>>, > >+ remove_cvref_t<ranges::range_reference_t<const _Rg>>>; > >+ > > template<typename _Rg, typename _CharT> > > using __maybe_const_range > > = __conditional_t<__const_formattable_range<_Rg, _CharT>, const > _Rg, _Rg>; > >diff --git a/libstdc++-v3/include/std/format > b/libstdc++-v3/include/std/format > >index 7d3067098be..355db5f2a60 100644 > >--- a/libstdc++-v3/include/std/format > >+++ b/libstdc++-v3/include/std/format > >@@ -56,7 +56,7 @@ > > #include <bits/ranges_base.h> // input_range, range_reference_t > > #include <bits/ranges_util.h> // subrange > > #include <bits/ranges_algobase.h> // ranges::copy > >-#include <bits/stl_iterator.h> // back_insert_iterator > >+#include <bits/stl_iterator.h> // back_insert_iterator, counted_iterator > > #include <bits/stl_pair.h> // __is_pair > > #include <bits/unicode.h> // __is_scalar_value, _Utf_view, etc. > > #include <bits/utility.h> // tuple_size_v > >@@ -99,19 +99,12 @@ namespace __format > > > > // Size for stack located buffer > > template<typename _CharT> > >- constexpr size_t __stackbuf_size = 32 * sizeof(void*) / sizeof(_CharT); > >+ constexpr size_t __stackbuf_size = 32 * sizeof(void*) / > sizeof(_CharT); > > > > // Type-erased character sinks. > > template<typename _CharT> class _Sink; > > template<typename _CharT> class _Fixedbuf_sink; > >- template<typename _Seq> class _Seq_sink; > >- > >- template<typename _CharT, typename _Alloc = allocator<_CharT>> > >- using _Str_sink > >- = _Seq_sink<basic_string<_CharT, char_traits<_CharT>, _Alloc>>; > >- > >- // template<typename _CharT, typename _Alloc = allocator<_CharT>> > >- // using _Vec_sink = _Seq_sink<vector<_CharT, _Alloc>>; > >+ template<typename _Out, typename _CharT> class _Padding_sink; > > > > // Output iterator that writes to a type-erase character sink. > > template<typename _CharT> > >@@ -892,6 +885,25 @@ namespace __format > > __spec._M_fill); > > } > > > >+ template<typename _CharT> > >+ size_t > >+ __truncate(basic_string_view<_CharT>& __s, size_t __prec) > >+ { > >+ if constexpr (__unicode::__literal_encoding_is_unicode<_CharT>()) > >+ { > >+ if (__prec != (size_t)-1) > >+ return __unicode::__truncate(__s, __prec); > >+ else > >+ return __unicode::__field_width(__s); > >+ } > >+ else > >+ { > >+ __s = __s.substr(0, __prec); > >+ return __s.size(); > >+ } > >+ } > >+ > >+ > > // Values are indices into _Escapes::all. > > enum class _Term_char : unsigned char { > > _Tc_quote = 12, > >@@ -1327,82 +1339,110 @@ namespace __format > > format(basic_string_view<_CharT> __s, > > basic_format_context<_Out, _CharT>& __fc) const > > { > >- constexpr auto __term = __format::_Term_char::_Tc_quote; > >- const auto __write_direct = [&] > >- { > >- if (_M_spec._M_type == _Pres_esc) > >- return __format::__write_escaped(__fc.out(), __s, __term); > >- else > >- return __format::__write(__fc.out(), __s); > >- }; > >+ if (_M_spec._M_type == _Pres_esc) > >+ return _M_format_escaped(__s, __fc); > > > > if (_M_spec._M_width_kind == _WP_none > > && _M_spec._M_prec_kind == _WP_none) > >- return __write_direct(); > >+ return __format::__write(__fc.out(), __s); > > > >- const size_t __prec = > >- _M_spec._M_prec_kind != _WP_none > >- ? _M_spec._M_get_precision(__fc) > >- : basic_string_view<_CharT>::npos; > >+ const size_t __maxwidth = _M_spec._M_get_precision(__fc); > >+ const size_t __width = __format::__truncate(__s, __maxwidth); > >+ return __format::__write_padded_as_spec(__s, __width, __fc, > _M_spec); > >+ } > > > >- const size_t __estimated_width = _S_trunc(__s, __prec); > >- // N.B. Escaping only increases width > >- if (_M_spec._M_get_width(__fc) <= __estimated_width > >- && _M_spec._M_prec_kind == _WP_none) > >- return __write_direct(); > >+ template<typename _Out> > >+ _Out > >+ _M_format_escaped(basic_string_view<_CharT> __s, > >+ basic_format_context<_Out, _CharT>& __fc) const > >+ { > >+ constexpr auto __term = __format::_Term_char::_Tc_quote; > >+ const size_t __padwidth = _M_spec._M_get_width(__fc); > >+ if (__padwidth == 0 && _M_spec._M_prec_kind == _WP_none) > >+ return __format::__write_escaped(__fc.out(), __s, __term); > > > >- if (_M_spec._M_type != _Pres_esc) > >- return __format::__write_padded_as_spec(__s, __estimated_width, > >- __fc, _M_spec); > >+ const size_t __maxwidth = _M_spec._M_get_precision(__fc); > >+ const size_t __width = __truncate(__s, __maxwidth); > >+ // N.B. Escaping only increases width > >+ if (__padwidth <= __width && _M_spec._M_prec_kind == _WP_none) > >+ return __format::__write_escaped(__fc.out(), __s, __term); > > > >- __format::_Str_sink<_CharT> __sink; > >- __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); > > // N.B. [tab:format.type.string] defines '?' as > > // Copies the escaped string ([format.string.escaped]) to the > output, > > // so precision seem to appy to escaped string. > >- return __format::__write_padded_as_spec(__escaped, > __escaped_width, > >- __fc, _M_spec); > >+ _Padding_sink<_Out, _CharT> __sink(__fc.out(), __padwidth, > __maxwidth); > >+ __format::__write_escaped(__sink.out(), __s, __term); > >+ return __sink._M_finish(_M_spec._M_align, _M_spec._M_fill); > > } > > > > #if __glibcxx_format_ranges // C++ >= 23 && HOSTED > > template<ranges::input_range _Rg, typename _Out> > > requires same_as<remove_cvref_t<ranges::range_reference_t<_Rg>>, > _CharT> > >- typename basic_format_context<_Out, _CharT>::iterator > >+ _Out > > _M_format_range(_Rg&& __rg, basic_format_context<_Out, _CharT>& > __fc) const > > { > >+ using _Range = remove_reference_t<_Rg>; > > using _String = basic_string<_CharT>; > > using _String_view = basic_string_view<_CharT>; > >- if constexpr (ranges::forward_range<_Rg> || > ranges::sized_range<_Rg>) > >+ if constexpr (!is_lvalue_reference_v<_Rg>) > >+ return _M_format_range<_Range&>(__rg, __fc); > >+ else if constexpr (!is_const_v<_Range> > >+ && __simply_formattable_range<_Range, > _CharT>) > >+ return _M_format_range<const _Range&>(__rg, __fc); > >+ else if constexpr (ranges::contiguous_range<_Rg>) > >+ { > >+ _String_view __str(ranges::data(__rg), > >+ size_t(ranges::distance(__rg))); > >+ return format(__str, __fc); > >+ } > >+ else if (_M_spec._M_type != _Pres_esc) > >+ { > >+ const size_t __padwidth = _M_spec._M_get_width(__fc); > >+ if (__padwidth == 0 && _M_spec._M_prec_kind == _WP_none) > >+ return ranges::copy(__rg, __fc.out()).out; > >+ > >+ _Padding_sink<_Out, _CharT> __sink(__fc.out(), __padwidth, > >+ > _M_spec._M_get_precision(__fc)); > >+ ranges::copy(__rg, __sink.out()); > >+ return __sink._M_finish(_M_spec._M_align, _M_spec._M_fill); > >+ } > >+ else if constexpr (ranges::forward_range<_Rg> || > ranges::sized_range<_Rg>) > > { > > const size_t __n(ranges::distance(__rg)); > >- if constexpr (ranges::contiguous_range<_Rg>) > >- return format(_String_view(ranges::data(__rg), __n), __fc); > >- else if (__n <= __format::__stackbuf_size<_CharT>) > >+ size_t __w = __n; > >+ if constexpr > (!__unicode::__literal_encoding_is_unicode<_CharT>()) > >+ if (size_t __max = _M_spec._M_get_precision(__fc); __n > > __max) > >+ __w == __max; > >+ > >+ if (__w <= __format::__stackbuf_size<_CharT>) > > { > > _CharT __buf[__format::__stackbuf_size<_CharT>]; > >- ranges::copy(__rg, __buf); > >- return format(_String_view(__buf, __n), __fc); > >+ ranges::copy_n(ranges::begin(__rg), __w, __buf); > >+ return _M_format_escaped(_String_view(__buf, __n), __fc); > > } > >- else if constexpr (ranges::sized_range<_Rg>) > >- return format(_String(from_range, __rg), __fc); > > else if constexpr (ranges::random_access_range<_Rg>) > > { > > ranges::iterator_t<_Rg> __first = ranges::begin(__rg); > >- ranges::subrange __sub(__first, __first + __n); > >- return format(_String(from_range, __sub), __fc); > >+ ranges::subrange __sub(__first, __first + __w); > >+ return _M_format_escaped(_String(from_range, __sub), > __fc); > > } > >+ else if (__w <= __n) > >+ { > >+ ranges::subrange > __sub(counted_iterator(ranges::begin(__rg)), > >+ default_sentinel); > >+ return _M_format_escaped(_String(from_range, __sub), > __fc); > >+ } > >+ else if constexpr (ranges::sized_range<_Rg>) > >+ return _M_format_escaped(_String(from_range, __rg), __fc); > > else > > { > > // N.B. preserve the computed size > > ranges::subrange __sub(__rg, __n); > >- return format(_String(from_range, __sub), __fc); > >+ return _M_format_escaped(_String(from_range, __sub), > __fc); > > } > > } > > else > >- return format(_String(from_range, __rg), __fc); > >+ return _M_format_escaped(_String(from_range, __rg), __fc); > > } > > > > constexpr void > >@@ -1411,23 +1451,6 @@ namespace __format > > #endif > > > > private: > >- static size_t > >- _S_trunc(basic_string_view<_CharT>& __s, size_t __prec) > >- { > >- if constexpr (__unicode::__literal_encoding_is_unicode<_CharT>()) > >- { > >- if (__prec != basic_string_view<_CharT>::npos) > >- return __unicode::__truncate(__s, __prec); > >- else > >- return __unicode::__field_width(__s); > >- } > >- else > >- { > >- __s = __s.substr(0, __prec); > >- return __s.size(); > >- } > >- } > >- > > _Spec<_CharT> _M_spec{}; > > }; > > > >@@ -3271,12 +3294,12 @@ namespace __format > > // A sink that fills a sequence (e.g. std::string, std::vector, > std::deque). > > // Writes to a buffer then appends that to the sequence when it fills > up. > > template<typename _Seq> > >- class _Seq_sink final : public _Buf_sink<typename _Seq::value_type> > >+ class _Seq_sink : public _Buf_sink<typename _Seq::value_type> > > { > > using _CharT = typename _Seq::value_type; > > > > _Seq _M_seq; > >- > >+ protected: > > // Transfer buffer contents to the sequence, so buffer can be > refilled. > > void > > _M_overflow() override > >@@ -3348,6 +3371,17 @@ namespace __format > > } > > } > > > >+ void _M_trim(span<const _CharT> __s) > >+ requires __is_specialization_of<_Seq, basic_string> > >+ { > >+ _GLIBCXX_DEBUG_ASSERT(__s.data() == this->_M_buf > >+ || __s.data() == _M_seq.data()); > >+ if (__s.data() == _M_seq.data()) > >+ _M_seq.resize(__s.size()); > >+ else > >+ this->_M_reset(this->_M_buf, __s.size()); > >+ } > >+ > > public: > > // TODO: for SSO string, use SSO buffer as initial span, then > switch > > // to _M_buf if it overflows? Or even do that for all unused > capacity? > >@@ -3373,7 +3407,7 @@ namespace __format > > // A writable span that views everything written to the sink. > > // Will be either a view over _M_seq or the used part of _M_buf. > > span<_CharT> > >- view() > >+ _M_span() > > { > > auto __s = this->_M_used(); > > if (_M_seq.size()) > >@@ -3384,9 +3418,21 @@ namespace __format > > } > > return __s; > > } > >+ > >+ basic_string_view<_CharT> > >+ view() > >+ { > >+ auto __span = _M_span(); > >+ return basic_string_view<_CharT>(__span.data(), __span.size()); > >+ } > > }; > > > >- // A sink that writes to an output iterator. > >+ template<typename _CharT, typename _Alloc = allocator<_CharT>> > >+ using _Str_sink > >+ = _Seq_sink<basic_string<_CharT, char_traits<_CharT>, _Alloc>>; > >+ > >+ // template<typename _CharT, typename _Alloc = allocator<_CharT>> > >+ // using _Vec_sink = _Seq_sink<vector<_CharTthis-> sink that writes to > an output iterator. > > // Writes to a fixed-size buffer and then flushes to the output > iterator > > // when the buffer fills up. > > template<typename _CharT, typename _OutIter> > >@@ -3554,6 +3600,171 @@ namespace __format > > } > > }; > > > >+ // A sink for handling the padded outputs (_M_padwidth) or truncated > >+ // (_M_maxwidth). The handling is done by writting to buffer > (_Str_strink) > > s/writting/writing/ > > >+ // until sufficient number of characters is written. After that if > sequence > >+ // is longer than _M_padwidth it's written to _M_out, and further > writes are > >+ // either: > >+ // * buffered and forwarded to _M_out, if below _M_maxwidth, > >+ // * ignored otherwise > >+ // If field width of written sequence is no greater than _M_padwidth, > the > >+ // sequence is written during _M_finish call. > >+ template<typename _Out, typename _CharT> > >+ class _Padding_sink : public _Str_sink<_CharT> > >+ { > >+ const size_t _M_padwidth; > >+ const size_t _M_maxwidth; > >+ _Out _M_out; > >+ size_t _M_printwidth; > >+ > >+ [[__gnu__::__always_inline__]] > >+ bool > >+ _M_ignoring() const > >+ { > >+ return _M_printwidth >= _M_maxwidth; > >+ } > >+ > >+ [[__gnu__::__always_inline__]] > >+ bool > >+ _M_buffering() const > >+ { > >+ if (_M_printwidth < _M_padwidth) > >+ return true; > >+ if (_M_maxwidth != (size_t)-1) > >+ return _M_printwidth < _M_maxwidth; > >+ return false; > >+ } > >+ > >+ void > >+ _M_flush() > >+ { > >+ span<_CharT> __new = this->_M_used(); > >+ basic_string_view<_CharT> __str(__new.data(), __new.size()); > >+ _M_out = __format::__write(std::move(_M_out), __str); > >+ this->_M_rewind(); > >+ } > >+ > >+ bool > >+ _M_force_update() > >+ { > >+ auto __str = this->view(); > >+ // Compute actual field width, possibly truncated. > >+ _M_printwidth = __format::__truncate(__str, _M_maxwidth); > >+ if (_M_ignoring()) > >+ this->_M_trim(__str); > >+ if (_M_buffering()) > >+ return true; > >+ > >+ // We have more characters than padidng, no padding is needed, > > s/padidng/padding/ > > >+ // write direclty to _M_out. > > s/direclty/directly/ > > >+ if (_M_printwidth >= _M_padwidth) > >+ _M_out = __format::__write(std::move(_M_out), __str); > >+ // We reached _M_maxwidth that is smaller than _M_padwidth. > >+ // Store the prefix sequence in _M_seq, and free _M_buf. > >+ else > >+ _Str_sink<_CharT>::_M_overflow(); > >+ > >+ // Use internal buffer for writes to _M_out. > >+ this->_M_reset(this->_M_buf); > >+ return false; > >+ } > >+ > >+ bool > >+ _M_update(size_t __new) > >+ { > >+ _M_printwidth += __new; > >+ if (_M_buffering()) > >+ return true; > >+ return _M_force_update(); > >+ } > >+ > >+ void > >+ _M_overflow() override > >+ { > >+ // Ignore characters in buffer, and override it. > >+ if (_M_ignoring()) > >+ this->_M_rewind(); > >+ // Write buffer to _M_out, and override it. > >+ else if (!_M_buffering()) > >+ _M_flush(); > >+ // Update written count, and if input still should be buffered, > >+ // flush the to _M_seq. > > Should this be "flush to _M_seq" without "the"? > I think so, but not right person to ask. > > >+ else if (_M_update(this->_M_used().size())) > >+ _Str_sink<_CharT>::_M_overflow(); > >+ } > >+ > >+ typename _Sink<_CharT>::_Reservation > >+ _M_reserve(size_t __n) override > >+ { > >+ // Ignore characters in buffer, if any. > >+ if (_M_ignoring()) > >+ this->_M_rewind(); > >+ else if constexpr (is_same_v<_Out, _Sink_iter<_CharT>>) > >+ if (!_M_buffering()) > >+ { > >+ // Write pending characters if any > >+ if (!this->_M_used().empty()) > >+ _M_flush(); > >+ // Try to reserve from _M_out sink. > >+ if (auto __reserved = _M_out._M_reserve(__n)) > >+ return __reserved; > >+ } > >+ return _Sink<_CharT>::_M_reserve(__n); > >+ } > >+ > >+ void > >+ _M_bump(size_t __n) override > >+ { > >+ // Ignore the written characters. > >+ if (_M_ignoring()) > >+ return; > >+ // If reservation was made directy sink associated _M_out, > >+ // _M_bump will be called on that sink. > > I found this comment a bit hard to follow. Maybe something like: > > "If a reservation was made by _M_out._M_reserve then we won't get > here, because _M_out._M_bump will be called instead." > Thanks this much better captures what I meant here. > > >+ _Sink<_CharT>::_M_bump(__n); > >+ if (_M_buffering()) > >+ _M_update(__n); > >+ } > >+ > >+ public: > >+ [[__gnu__::__always_inline__]] > >+ explicit _Padding_sink(_Out __out, size_t __padwidth) > > Line break after 'explicit' plase. > > >+ : _M_padwidth(__padwidth), _M_maxwidth(-1), > >+ _M_out(std::move(__out)), _M_printwidth(0) > >+ { } > >+ > >+ [[__gnu__::__always_inline__]] > >+ explicit _Padding_sink(_Out __out, size_t __padwidth, size_t > __maxwidth) > > Same here. > > >+ : _M_padwidth(__padwidth), _M_maxwidth(__maxwidth), > >+ _M_out(std::move(__out)), _M_printwidth(0) > >+ { } > >+ > >+ _Out > >+ _M_finish(_Align __align, char32_t __fill_char) > >+ { > >+ // Handle any characters in the buffer. > >+ if (auto __rem = this->_M_used().size()) > >+ if (_M_ignoring()) > >+ this->_M_rewind(); > >+ else if (!_M_buffering()) > >+ _M_flush(); > >+ else > >+ _M_update(__rem); > >+ > >+ if (!_M_buffering() || !_M_force_update()) > >+ // Characters were already written to _M_out. > >+ if (_M_printwidth >= _M_padwidth) > >+ return std::move(_M_out); > >+ > >+ const auto __str = this->view(); > >+ if (_M_printwidth >= _M_padwidth) > >+ return __format::__write(std::move(_M_out), __str); > >+ > >+ const size_t __nfill = _M_padwidth - _M_printwidth; > >+ return __format::__write_padded(std::move(_M_out), __str, > >+ __align, __nfill, __fill_char); > >+ } > >+ }; > >+ > > enum _Arg_t : unsigned char { > > _Arg_none, _Arg_bool, _Arg_c, _Arg_i, _Arg_u, _Arg_ll, _Arg_ull, > > _Arg_flt, _Arg_dbl, _Arg_ldbl, _Arg_str, _Arg_sv, _Arg_ptr, > _Arg_handle, > >@@ -5160,7 +5371,8 @@ namespace __format > > // 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) > >+ const size_t __padwidth = __spec._M_get_width(__fc); > >+ if (__padwidth == 0) > > return __call(__fc); > > > > struct _Restore_out > >@@ -5169,48 +5381,30 @@ namespace __format > > : _M_ctx(std::addressof(__fc)), _M_out(__fc.out()) > > { } > > > >- void _M_trigger() > >+ void > >+ _M_disarm() > >+ { _M_ctx = nullptr; } > >+ > >+ ~_Restore_out() > > { > > if (_M_ctx) > > _M_ctx->advance_to(_M_out); > >- _M_ctx = nullptr; > > } > > > >- ~_Restore_out() > >- { _M_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()); > >+ _Padding_sink<_Sink_iter<_CharT>, _CharT> __sink(__fc.out(), > __padwidth); > >+ __fc.advance_to(__sink.out()); > > __call(__fc); > >- __restore._M_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); > >+ __fc.advance_to(__sink._M_finish(__spec._M_align, __spec._M_fill)); > >+ __restore._M_disarm(); > >+ return __fc.out(); > > } > > > >- // _Rg& and const _Rg& are both formattable and use same formatter > >- // specialization for their references. > >- template<typename _Rg, typename _CharT> > >- concept __simply_formattable_range > >- = __const_formattable_range<_Rg, _CharT> > >- && same_as<remove_cvref_t<ranges::range_reference_t<_Rg>>, > >- remove_cvref_t<ranges::range_reference_t<const _Rg>>>; > >- > > template<size_t _Pos, typename _Tp, typename _CharT> > > struct __indexed_formatter_storage > > { > >diff --git a/libstdc++-v3/testsuite/std/format/debug.cc > b/libstdc++-v3/testsuite/std/format/debug.cc > >index 71bb7f4a0fe..d3b025eabb4 100644 > >--- a/libstdc++-v3/testsuite/std/format/debug.cc > >+++ b/libstdc++-v3/testsuite/std/format/debug.cc > >@@ -2,6 +2,7 @@ > > // { dg-options "-fexec-charset=UTF-8 -fwide-exec-charset=UTF-32BE > -DUNICODE_ENC" { target be } } > > // { dg-do run { target c++23 } } > > // { dg-add-options no_pch } > >+// { dg-timeout-factor 2 } > > > > #include <format> > > #include <testsuite_hooks.h> > >@@ -114,11 +115,11 @@ test_extended_ascii() > > } > > } > > > >-#if UNICODE_ENC > > template<typename _CharT> > > void > > test_unicode_escapes() > > { > >+#if UNICODE_ENC > > std::basic_string<_CharT> res; > > > > const auto in = WIDEN( > >@@ -160,12 +161,14 @@ test_unicode_escapes() > > res = fdebug(in[5]); > > VERIFY( res == WIDEN("'\U0001f984'") ); > > } > >+#endif // UNICODE_ENC > > } > > > > template<typename _CharT> > > void > > test_grapheme_extend() > > { > >+#if UNICODE_ENC > > std::basic_string<_CharT> res; > > > > const auto vin = WIDEN("o\u0302\u0323"); > >@@ -184,12 +187,14 @@ test_grapheme_extend() > > res = fdebug(in[1]); > > VERIFY( res == WIDEN(R"('\u{302}')") ); > > } > >+#endif // UNICODE_ENC > > } > > > > template<typename _CharT> > > void > > test_replacement_char() > > { > >+#if UNICODE_ENC > > std::basic_string<_CharT> repl = WIDEN("\uFFFD"); > > std::basic_string<_CharT> res = fdebug(repl); > > VERIFY( res == WIDEN("\"\uFFFD\"") ); > >@@ -197,11 +202,13 @@ test_replacement_char() > > repl = WIDEN("\uFFFD\uFFFD"); > > res = fdebug(repl); > > VERIFY( res == WIDEN("\"\uFFFD\uFFFD\"") ); > >+#endif // UNICODE_ENC > > } > > > > void > > test_ill_formed_utf8_seq() > > { > >+#if UNICODE_ENC > > std::string_view seq = "\xf0\x9f\xa6\x84"; // \U0001F984 > > std::string res; > > > >@@ -233,11 +240,13 @@ test_ill_formed_utf8_seq() > > VERIFY( res == R"('\x{84}')" ); > > res = fdebug(seq.substr(3, 1)); > > VERIFY( res == R"("\x{84}")" ); > >+#endif // UNICODE_ENC > > } > > > > void > > test_ill_formed_utf32() > > { > >+#if UNICODE_ENC > > std::wstring res; > > > > wchar_t ic1 = static_cast<wchar_t>(0xff'ffff); > >@@ -255,8 +264,8 @@ test_ill_formed_utf32() > > std::wstring is2(1, ic2); > > res = fdebug(is2); > > VERIFY( res == LR"("\x{ffffffff}")" ); > >-} > > #endif // UNICODE_ENC > >+} > > > > template<typename _CharT> > > void > >@@ -331,6 +340,375 @@ test_prec() > > #endif // UNICODE_ENC > > } > > > >+bool strip_quote(std::string_view& v) > >+{ > >+ if (!v.starts_with('"')) > >+ return false; > >+ v.remove_prefix(1); > >+ return true; > >+} > >+ > >+bool strip_quotes(std::string_view& v) > >+{ > >+ if (!v.starts_with('"') || !v.ends_with('"')) > >+ return false; > >+ v.remove_prefix(1); > >+ v.remove_suffix(1); > >+ return true; > >+} > >+ > >+bool strip_prefix(std::string_view& v, size_t n, char c) > >+{ > >+ size_t pos = v.find_first_not_of(c); > >+ if (pos == std::string_view::npos) > >+ pos = v.size(); > >+ if (pos != n) > >+ return false; > >+ v.remove_prefix(n); > >+ return true; > >+} > >+ > >+void test_padding() > >+{ > >+ std::string res; > >+ std::string_view resv; > >+ > >+ // width and size are 26 > >+ std::string in = "abcdefghijklmnopqrstuvwxyz"; > >+ in += in; // width and size are 52 > >+ in += in; // width and size are 104 > >+ in += in; // width and size are 208 > >+ in += in; // width and size are 416 > >+ std::string_view inv = in; > >+ > >+ resv = res = std::format("{}", in); > >+ VERIFY( resv == inv ); > >+ > >+ resv = res = std::format("{:.500}", in); > >+ VERIFY( resv == inv ); > >+ > >+ resv = res = std::format("{:.400}", in); > >+ VERIFY( resv == inv.substr(0, 400) ); > >+ > >+ resv = res = std::format("{:.200}", in); > >+ VERIFY( resv == inv.substr(0, 200) ); > >+ > >+ resv = res = std::format("{:.10}", in); > >+ VERIFY( resv == inv.substr(0, 10) ); > >+ > >+ resv = res = std::format("{:.0}", in); > >+ VERIFY( resv == "" ); > >+ > >+ resv = res = std::format("{:*>20}", in); > >+ VERIFY( resv == inv ); > >+ > >+ resv = res = std::format("{:*>20.500}", in); > >+ VERIFY( resv == inv ); > >+ > >+ resv = res = std::format("{:*>20.400}", in); > >+ VERIFY( resv == inv.substr(0, 400) ); > >+ > >+ resv = res = std::format("{:*>20.200}", in); > >+ VERIFY( resv == inv.substr(0, 200) ); > >+ > >+ resv = res = std::format("{:*>20.10}", in); > >+ VERIFY( strip_prefix(resv, 10, '*') ); > >+ VERIFY( resv == inv.substr(0, 10) ); > >+ > >+ resv = res = std::format("{:*>20.0}", in); > >+ VERIFY( strip_prefix(resv, 20, '*') ); > >+ VERIFY( resv == "" ); > >+ > >+ resv = res = std::format("{:*>450}", in); > >+ VERIFY( strip_prefix(resv, 34, '*') ); > >+ VERIFY( resv == inv ); > >+ > >+ resv = res = std::format("{:*>450.500}", in); > >+ VERIFY( strip_prefix(resv, 34, '*') ); > >+ VERIFY( resv == inv ); > >+ > >+ resv = res = std::format("{:*>450.420}", in); > >+ VERIFY( strip_prefix(resv, 34, '*') ); > >+ VERIFY( resv == inv ); > >+ > >+ resv = res = std::format("{:*>450.400}", in); > >+ VERIFY( strip_prefix(resv, 50, '*') ); > >+ VERIFY( resv == inv.substr(0, 400) ); > >+ > >+ resv = res = std::format("{:*>450.200}", in); > >+ VERIFY( strip_prefix(resv, 250, '*') ); > >+ VERIFY( resv == inv.substr(0, 200) ); > >+ > >+ resv = res = std::format("{:*>450.10}", in); > >+ VERIFY( strip_prefix(resv, 440, '*') ); > >+ VERIFY( resv == inv.substr(0, 10) ); > >+ > >+ resv = res = std::format("{:*>450.0}", in); > >+ VERIFY( strip_prefix(resv, 450, '*') ); > >+ VERIFY( resv == "" ); > >+ > >+ resv = res = std::format("{:?}", in); > >+ VERIFY( strip_quotes(resv) ); > >+ VERIFY( resv == inv ); > >+ > >+ resv = res = std::format("{:.500?}", in); > >+ VERIFY( strip_quotes(resv) ); > >+ VERIFY( resv == inv ); > >+ > >+ resv = res = std::format("{:.400?}", in); > >+ VERIFY( strip_quote(resv) ); > >+ VERIFY( resv == inv.substr(0, 399) ); > >+ > >+ resv = res = std::format("{:.200?}", in); > >+ VERIFY( strip_quote(resv) ); > >+ VERIFY( resv == inv.substr(0, 199) ); > >+ > >+ resv = res = std::format("{:.10?}", in); > >+ VERIFY( strip_quote(resv) ); > >+ VERIFY( resv == inv.substr(0, 9) ); > >+ > >+ resv = res = std::format("{:.1?}", in); > >+ VERIFY( strip_quote(resv) ); > >+ VERIFY( resv == "" ); > >+ > >+ resv = res = std::format("{:.0?}", in); > >+ VERIFY( resv == "" ); > >+ > >+ resv = res = std::format("{:*>20?}", in); > >+ VERIFY( strip_quotes(resv) ); > >+ VERIFY( resv == inv ); > >+ > >+ resv = res = std::format("{:*>20.500?}", in); > >+ VERIFY( strip_quotes(resv) ); > >+ VERIFY( resv == inv ); > >+ > >+ resv = res = std::format("{:*>20.400?}", in); > >+ VERIFY( strip_quote(resv) ); > >+ VERIFY( resv == inv.substr(0, 399) ); > >+ > >+ resv = res = std::format("{:*>20.200?}", in); > >+ VERIFY( strip_quote(resv) ); > >+ VERIFY( resv == inv.substr(0, 199) ); > >+ > >+ resv = res = std::format("{:*>20.10?}", in); > >+ VERIFY( strip_prefix(resv, 10, '*') ); > >+ VERIFY( strip_quote(resv) ); > >+ VERIFY( resv == inv.substr(0, 9) ); > >+ > >+ resv = res = std::format("{:*>20.1?}", in); > >+ VERIFY( strip_prefix(resv, 19, '*') ); > >+ VERIFY( strip_quote(resv) ); > >+ VERIFY( resv == "" ); > >+ > >+ resv = res = std::format("{:*>20.0?}", in); > >+ VERIFY( strip_prefix(resv, 20, '*') ); > >+ VERIFY( resv == "" ); > >+ > >+ resv = res = std::format("{:*>450?}", in); > >+ VERIFY( strip_prefix(resv, 32, '*') ); > >+ VERIFY( strip_quotes(resv) ); > >+ VERIFY( resv == inv ); > >+ > >+ resv = res = std::format("{:*>450.500?}", in); > >+ VERIFY( strip_prefix(resv, 32, '*') ); > >+ VERIFY( strip_quotes(resv) ); > >+ VERIFY( resv == inv ); > >+ > >+ resv = res = std::format("{:*>450.420?}", in); > >+ VERIFY( strip_prefix(resv, 32, '*') ); > >+ VERIFY( strip_quotes(resv) ); > >+ VERIFY( resv == inv ); > >+ > >+ resv = res = std::format("{:*>450.400?}", in); > >+ VERIFY( strip_prefix(resv, 50, '*') ); > >+ VERIFY( strip_quote(resv) ); > >+ VERIFY( resv == inv.substr(0, 399) ); > >+ > >+ resv = res = std::format("{:*>450.200?}", in); > >+ VERIFY( strip_prefix(resv, 250, '*') ); > >+ VERIFY( strip_quote(resv) ); > >+ VERIFY( resv == inv.substr(0, 199) ); > >+ > >+ resv = res = std::format("{:*>450.10?}", in); > >+ VERIFY( strip_prefix(resv, 440, '*') ); > >+ VERIFY( strip_quote(resv) ); > >+ VERIFY( resv == inv.substr(0, 9) ); > >+ > >+ resv = res = std::format("{:*>450.1?}", in); > >+ VERIFY( strip_prefix(resv, 449, '*') ); > >+ VERIFY( strip_quote(resv) ); > >+ VERIFY( resv == "" ); > >+ > >+ resv = res = std::format("{:*>450.0?}", in); > >+ VERIFY( strip_prefix(resv, 450, '*') ); > >+ VERIFY( resv == "" ); > >+ > >+#if UNICODE_ENC > >+ // width is 3, size is 15 > >+ in = "o\u0302\u0323i\u0302\u0323u\u0302\u0323"; > >+ in += in; // width is 6, size is 30 > >+ in += in; // width is 12, size is 60 > >+ in += in; // width is 24, size is 120 > >+ in += in; // width is 48, size is 240 > >+ in += in; // width is 96, size is 480 > >+ in += in; // width is 192, size is 960 > >+ inv = in; > >+ > >+ resv = res = std::format("{:}", in); > >+ VERIFY( resv == inv ); > >+ > >+ resv = res = std::format("{:.200}", in); > >+ VERIFY( resv == inv ); > >+ > >+ resv = res = std::format("{:.96}", in); > >+ VERIFY( resv == inv.substr(0, 480) ); > >+ > >+ resv = res = std::format("{:.12}", in); > >+ VERIFY( resv == inv.substr(0, 60) ); > >+ > >+ resv = res = std::format("{:.3}", in); > >+ VERIFY( resv == inv.substr(0, 15) ); > >+ > >+ resv = res = std::format("{:.0}", in); > >+ VERIFY( resv == "" ); > >+ > >+ resv = res = std::format("{:*>10}", in); > >+ VERIFY( resv == inv ); > >+ > >+ resv = res = std::format("{:*>10.200}", in); > >+ VERIFY( resv == inv ); > >+ > >+ resv = res = std::format("{:*>10.96}", in); > >+ VERIFY( resv == inv.substr(0, 480) ); > >+ > >+ resv = res = std::format("{:*>10.12}", in); > >+ VERIFY( resv == inv.substr(0, 60) ); > >+ > >+ resv = res = std::format("{:*>10.3}", in); > >+ VERIFY( strip_prefix(resv, 7, '*') ); > >+ VERIFY( resv == inv.substr(0, 15) ); > >+ > >+ resv = res = std::format("{:*>10.0}", in); > >+ VERIFY( strip_prefix(resv, 10, '*') ); > >+ VERIFY( resv == "" ); > >+ > >+ resv = res = std::format("{:*>240s}", in); > >+ VERIFY( strip_prefix(resv, 48, '*') ); > >+ VERIFY( resv == inv ); > >+ > >+ resv = res = std::format("{:*>240.200s}", in); > >+ VERIFY( strip_prefix(resv, 48, '*') ); > >+ VERIFY( resv == inv ); > >+ > >+ resv = res = std::format("{:*>240.96s}", in); > >+ VERIFY( strip_prefix(resv, 144, '*') ); > >+ VERIFY( resv == inv.substr(0, 480) ); > >+ > >+ resv = res = std::format("{:*>240.12}", in); > >+ VERIFY( strip_prefix(resv, 228, '*') ); > >+ VERIFY( resv == inv.substr(0, 60) ); > >+ > >+ resv = res = std::format("{:*>240.3s}", in); > >+ VERIFY( strip_prefix(resv, 237, '*') ); > >+ VERIFY( resv == inv.substr(0, 15) ); > >+ > >+ resv = res = std::format("{:*>240.0s}", in); > >+ VERIFY( strip_prefix(resv, 240, '*') ); > >+ VERIFY( resv == "" ); > >+ > >+ resv = res = std::format("{:?}", in); > >+ VERIFY( strip_quotes(resv) ); > >+ VERIFY( resv == inv ); > >+ > >+ resv = res = std::format("{:.200?}", in); > >+ VERIFY( strip_quotes(resv) ); > >+ VERIFY( resv == inv ); > >+ > >+ resv = res = std::format("{:.97?}", in); > >+ VERIFY( strip_quote(resv) ); > >+ VERIFY( resv == inv.substr(0, 480) ); > >+ > >+ resv = res = std::format("{:.13?}", in); > >+ VERIFY( strip_quote(resv) ); > >+ VERIFY( resv == inv.substr(0, 60) ); > >+ > >+ resv = res = std::format("{:.4?}", in); > >+ VERIFY( strip_quote(resv) ); > >+ VERIFY( resv == inv.substr(0, 15) ); > >+ > >+ resv = res = std::format("{:.1?}", in); > >+ VERIFY( strip_quote(resv) ); > >+ VERIFY( resv == "" ); > >+ > >+ resv = res = std::format("{:.0?}", in); > >+ VERIFY( resv == "" ); > >+ > >+ resv = res = std::format("{:*>10?}", in); > >+ VERIFY( strip_quotes(resv) ); > >+ VERIFY( resv == inv ); > >+ > >+ resv = res = std::format("{:*>10.200?}", in); > >+ VERIFY( strip_quotes(resv) ); > >+ VERIFY( resv == inv ); > >+ > >+ resv = res = std::format("{:*>10.97?}", in); > >+ VERIFY( strip_quote(resv) ); > >+ VERIFY( resv == inv.substr(0, 480) ); > >+ > >+ resv = res = std::format("{:*>10.13?}", in); > >+ VERIFY( strip_quote(resv) ); > >+ VERIFY( resv == inv.substr(0, 60) ); > >+ > >+ resv = res = std::format("{:*>10.4?}", in); > >+ VERIFY( strip_prefix(resv, 6, '*') ); > >+ VERIFY( strip_quote(resv) ); > >+ VERIFY( resv == inv.substr(0, 15) ); > >+ > >+ resv = res = std::format("{:*>10.1?}", in); > >+ VERIFY( strip_prefix(resv, 9, '*') ); > >+ VERIFY( strip_quote(resv) ); > >+ VERIFY( resv == "" ); > >+ > >+ resv = res = std::format("{:*>10.0?}", in); > >+ VERIFY( strip_prefix(resv, 10, '*') ); > >+ VERIFY( resv == "" ); > >+ > >+ resv = res = std::format("{:*>240?}", in); > >+ VERIFY( strip_prefix(resv, 46, '*') ); > >+ VERIFY( strip_quotes(resv) ); > >+ VERIFY( resv == inv ); > >+ > >+ resv = res = std::format("{:*>240.200?}", in); > >+ VERIFY( strip_prefix(resv, 46, '*') ); > >+ VERIFY( strip_quotes(resv) ); > >+ VERIFY( resv == inv ); > >+ > >+ resv = res = std::format("{:*>240.97?}", in); > >+ VERIFY( strip_prefix(resv, 143, '*') ); > >+ VERIFY( strip_quote(resv) ); > >+ VERIFY( resv == inv.substr(0, 480) ); > >+ > >+ resv = res = std::format("{:*>240.13?}", in); > >+ VERIFY( strip_prefix(resv, 227, '*') ); > >+ VERIFY( strip_quote(resv) ); > >+ VERIFY( resv == inv.substr(0, 60) ); > >+ > >+ resv = res = std::format("{:*>240.4?}", in); > >+ VERIFY( strip_prefix(resv, 236, '*') ); > >+ VERIFY( strip_quote(resv) ); > >+ VERIFY( resv == inv.substr(0, 15) ); > >+ > >+ resv = res = std::format("{:*>240.1?}", in); > >+ VERIFY( strip_prefix(resv, 239, '*') ); > >+ VERIFY( strip_quote(resv) ); > >+ VERIFY( resv == "" ); > >+ > >+ resv = res = std::format("{:*>240.0?}", in); > >+ VERIFY( strip_prefix(resv, 240, '*') ); > >+ VERIFY( resv == "" ); > >+#endif // UNICODE_ENC > >+} > >+ > > void test_char_as_wchar() > > { > > std::wstring res; > >@@ -435,7 +813,6 @@ int main() > > test_extended_ascii<char>(); > > test_extended_ascii<wchar_t>(); > > > >-#if UNICODE_ENC > > test_unicode_escapes<char>(); > > test_unicode_escapes<wchar_t>(); > > test_grapheme_extend<char>(); > >@@ -444,12 +821,13 @@ int main() > > test_replacement_char<wchar_t>(); > > test_ill_formed_utf8_seq(); > > test_ill_formed_utf32(); > >-#endif // UNICODE_ENC > > > > test_fill<char>(); > > test_fill<wchar_t>(); > > test_prec<char>(); > > test_prec<wchar_t>(); > > > >+ test_padding(); > >+ > > test_formatters_c(); > > } > >diff --git a/libstdc++-v3/testsuite/std/format/ranges/sequence.cc > b/libstdc++-v3/testsuite/std/format/ranges/sequence.cc > >index f05f6ec1e1c..75fe4c19a52 100644 > >--- a/libstdc++-v3/testsuite/std/format/ranges/sequence.cc > >+++ b/libstdc++-v3/testsuite/std/format/ranges/sequence.cc > >@@ -1,4 +1,5 @@ > > // { dg-do run { target c++23 } } > >+// { dg-options "-fexec-charset=UTF-8" } > > // { dg-timeout-factor 2 } > > > > #include <array> > >@@ -6,6 +7,7 @@ > > #include <list> > > #include <ranges> > > #include <span> > >+#include <string> > > #include <testsuite_hooks.h> > > #include <testsuite_iterators.h> > > #include <vector> > >@@ -199,9 +201,123 @@ test_nested() > > VERIFY( res == "+[01, 02, 11, 12]+" ); > > } > > > >+bool strip_quote(std::string_view& v) > >+{ > >+ if (!v.starts_with('"')) > >+ return false; > >+ v.remove_prefix(1); > >+ return true; > >+} > >+ > >+bool strip_prefix(std::string_view& v, std::string_view expected, bool > quoted = false) > >+{ > >+ if (quoted && !strip_quote(v)) > >+ return false; > >+ if (!v.starts_with(expected)) > >+ return false; > >+ v.remove_prefix(expected.size()); > >+ if (quoted && !strip_quote(v)) > >+ return false; > >+ return true; > >+} > >+ > >+bool strip_squares(std::string_view& v) > >+{ > >+ if (!v.starts_with('[') || !v.ends_with(']')) > >+ return false; > >+ v.remove_prefix(1); > >+ v.remove_suffix(1); > >+ return true; > >+} > >+ > >+bool strip_prefix(std::string_view& v, size_t n, char c) > >+{ > >+ size_t pos = v.find_first_not_of(c); > >+ if (pos == std::string_view::npos) > >+ pos = v.size(); > >+ if (pos != n) > >+ return false; > >+ v.remove_prefix(n); > >+ return true; > >+} > >+ > >+void test_padding() > >+{ > >+ std::string res; > >+ std::string_view resv; > >+ > >+ // width is 3, size is 15 > >+ std::string in = "o\u0302\u0323i\u0302\u0323u\u0302\u0323"; > >+ in += in; // width is 6, size is 30 > >+ in += in; // width is 12, size is 60 > >+ in += in; // width is 24, size is 120 > >+ in += in; // width is 48, size is 240 > >+ // width is 192, size is 960 > >+ std::vector<std::string> const vs{in, in, in, in}; > >+ > >+ auto const check_elems = [=](std::string_view& v, bool quoted) > >+ { > >+ VERIFY( strip_prefix(v, in, quoted) ); > >+ VERIFY( strip_prefix(v, ", ", false) ); > >+ VERIFY( strip_prefix(v, in, quoted) ); > >+ VERIFY( strip_prefix(v, ", ", false) ); > >+ VERIFY( strip_prefix(v, in, quoted) ); > >+ VERIFY( strip_prefix(v, ", ", false) ); > >+ VERIFY( strip_prefix(v, in, quoted) ); > >+ return v.empty(); > >+ }; > >+ > >+ resv = res = std::format("{}", vs); > >+ VERIFY( strip_squares(resv) ); > >+ VERIFY( check_elems(resv, true) ); > >+ > >+ resv = res = std::format("{:n}", vs); > >+ VERIFY( check_elems(resv, true) ); > >+ > >+ resv = res = std::format("{::}", vs); > >+ VERIFY( strip_squares(resv) ); > >+ VERIFY( check_elems(resv, false) ); > >+ > >+ resv = res = std::format("{:n:}", vs); > >+ VERIFY( check_elems(resv, false) ); > >+ > >+ resv = res = std::format("{:*>10}", vs); > >+ VERIFY( strip_squares(resv) ); > >+ VERIFY( check_elems(resv, true) ); > >+ > >+ resv = res = std::format("{:*>10n}", vs); > >+ VERIFY( check_elems(resv, true) ); > >+ > >+ resv = res = std::format("{:*>10:}", vs); > >+ VERIFY( strip_squares(resv) ); > >+ VERIFY( check_elems(resv, false) ); > >+ > >+ resv = res = std::format("{:*>10n:}", vs); > >+ VERIFY( check_elems(resv, false) ); > >+ > >+ resv = res = std::format("{:*>240}", vs); > >+ VERIFY( strip_prefix(resv, 32, '*') ); > >+ VERIFY( strip_squares(resv) ); > >+ VERIFY( check_elems(resv, true) ); > >+ > >+ resv = res = std::format("{:*>240n}", vs); > >+ VERIFY( strip_prefix(resv, 34, '*') ); > >+ VERIFY( check_elems(resv, true) ); > >+ > >+ resv = res = std::format("{:*>240:}", vs); > >+ VERIFY( strip_prefix(resv, 40, '*') ); > >+ VERIFY( strip_squares(resv) ); > >+ VERIFY( check_elems(resv, false) ); > >+ > >+ resv = res = std::format("{:*>240n:}", vs); > >+ VERIFY( strip_prefix(resv, 42, '*') ); > >+ VERIFY( check_elems(resv, false) ); > >+} > >+ > > int main() > > { > > test_format_string(); > > test_outputs(); > > test_nested(); > >+ test_padding(); > > } > >diff --git a/libstdc++-v3/testsuite/std/format/ranges/string.cc > b/libstdc++-v3/testsuite/std/format/ranges/string.cc > >index cf39aa66e07..cebdd530168 100644 > >--- a/libstdc++-v3/testsuite/std/format/ranges/string.cc > >+++ b/libstdc++-v3/testsuite/std/format/ranges/string.cc > >@@ -1,7 +1,9 @@ > > // { dg-do run { target c++23 } } > >+// { dg-options "-fexec-charset=UTF-8" } > > // { dg-timeout-factor 2 } > > > > #include <format> > >+#include <forward_list> > > #include <span> > > #include <testsuite_hooks.h> > > #include <testsuite_iterators.h> > >@@ -218,6 +220,67 @@ test_nested() > > VERIFY( std::format("{::?s}", vv) == R"(["str1", "str2"])" ); > > } > > > >+bool strip_quotes(std::string_view& v) > >+{ > >+ if (!v.starts_with('"') || !v.ends_with('"')) > >+ return false; > >+ v.remove_prefix(1); > >+ v.remove_suffix(1); > >+ return true; > >+} > >+ > >+bool strip_prefix(std::string_view& v, size_t n, char c) > >+{ > >+ size_t pos = v.find_first_not_of(c); > >+ if (pos == std::string_view::npos) > >+ pos = v.size(); > >+ if (pos != n) > >+ return false; > >+ v.remove_prefix(n); > >+ return true; > >+} > >+ > >+ > >+void test_padding() > >+{ > >+ std::string res; > >+ std::string_view resv; > >+ > >+ // width is 3, size is 15 > >+ std::string in = "o\u0302\u0323i\u0302\u0323u\u0302\u0323"; > >+ in += in; // width is 6, size is 30 > >+ in += in; // width is 12, size is 60 > >+ in += in; // width is 24, size is 120 > >+ in += in; // width is 48, size is 240 > >+ in += in; // width is 96, size is 480 > >+ in += in; // width is 192, size is 960 > >+ > >+ std::forward_list<char> lc(std::from_range, in); > >+ > >+ resv = res = std::format("{:s}", lc); > >+ VERIFY( resv == in ); > >+ > >+ resv = res = std::format("{:*>10s}", lc); > >+ VERIFY( resv == in ); > >+ > >+ resv = res = std::format("{:*>240s}", lc); > >+ VERIFY( strip_prefix(resv, 48, '*') ); > >+ VERIFY( resv == in ); > >+ > >+ resv = res = std::format("{:?s}", lc); > >+ VERIFY( strip_quotes(resv) ); > >+ VERIFY( resv == in ); > >+ > >+ resv = res = std::format("{:*>10?s}", lc); > >+ VERIFY( strip_quotes(resv) ); > >+ VERIFY( resv == in ); > >+ > >+ resv = res = std::format("{:*>240?s}", lc); > >+ VERIFY( strip_prefix(resv, 46, '*') ); > >+ VERIFY( strip_quotes(resv) ); > >+ VERIFY( resv == in ); > >+} > >+ > > int main() > > { > > test_format_string(); > >diff --git a/libstdc++-v3/testsuite/std/format/tuple.cc > b/libstdc++-v3/testsuite/std/format/tuple.cc > >index 62f9d293aab..ff0359b9aba 100644 > >--- a/libstdc++-v3/testsuite/std/format/tuple.cc > >+++ b/libstdc++-v3/testsuite/std/format/tuple.cc > >@@ -1,4 +1,6 @@ > > // { dg-do run { target c++23 } } > >+// { dg-options "-fexec-charset=UTF-8" } > >+// { dg-timeout-factor 2 } > > > > #include <format> > > #include <string> > >@@ -250,10 +252,101 @@ void test_nested() > > VERIFY( res == R"((): (1, "abc"))" ); > > } > > > >+bool strip_quote(std::string_view& v) > >+{ > >+ if (!v.starts_with('"')) > >+ return false; > >+ v.remove_prefix(1); > >+ return true; > >+} > >+ > >+bool strip_prefix(std::string_view& v, std::string_view expected, bool > quoted = false) > >+{ > >+ if (quoted && !strip_quote(v)) > >+ return false; > >+ if (!v.starts_with(expected)) > >+ return false; > >+ v.remove_prefix(expected.size()); > >+ if (quoted && !strip_quote(v)) > >+ return false; > >+ return true; > >+} > >+ > >+bool strip_parens(std::string_view& v) > >+{ > >+ if (!v.starts_with('(') || !v.ends_with(')')) > >+ return false; > >+ v.remove_prefix(1); > >+ v.remove_suffix(1); > >+ return true; > >+} > >+ > >+bool strip_prefix(std::string_view& v, size_t n, char c) > >+{ > >+ size_t pos = v.find_first_not_of(c); > >+ if (pos == std::string_view::npos) > >+ pos = v.size(); > >+ if (pos != n) > >+ return false; > >+ v.remove_prefix(n); > >+ return true; > >+} > >+ > >+void test_padding() > >+{ > >+ std::string res; > >+ std::string_view resv; > >+ > >+ // width is 3, size is 15 > >+ std::string in = "o\u0302\u0323i\u0302\u0323u\u0302\u0323"; > >+ in += in; // width is 6, size is 30 > >+ in += in; // width is 12, size is 60 > >+ in += in; // width is 24, size is 120 > >+ in += in; // width is 48, size is 240 > >+ // width is 192, size is 960 > >+ auto const ts = std::make_tuple(in, in, in, in); > >+ > >+ auto const check_elems = [=](std::string_view& v) > >+ { > >+ VERIFY( strip_prefix(v, in, true) ); > >+ VERIFY( strip_prefix(v, ", ", false) ); > >+ VERIFY( strip_prefix(v, in, true) ); > >+ VERIFY( strip_prefix(v, ", ", false) ); > >+ VERIFY( strip_prefix(v, in, true) ); > >+ VERIFY( strip_prefix(v, ", ", false) ); > >+ VERIFY( strip_prefix(v, in, true) ); > >+ return v.empty(); > >+ }; > >+ > >+ resv = res = std::format("{}", ts); > >+ VERIFY( strip_parens(resv) ); > >+ VERIFY( check_elems(resv) ); > >+ > >+ resv = res = std::format("{:n}", ts); > >+ VERIFY( check_elems(resv) ); > >+ > >+ resv = res = std::format("{:*>10}", ts); > >+ VERIFY( strip_parens(resv) ); > >+ VERIFY( check_elems(resv) ); > >+ > >+ resv = res = std::format("{:*>10n}", ts); > >+ VERIFY( check_elems(resv) ); > >+ > >+ resv = res = std::format("{:*>240}", ts); > >+ VERIFY( strip_prefix(resv, 32, '*') ); > >+ VERIFY( strip_parens(resv) ); > >+ VERIFY( check_elems(resv) ); > >+ > >+ resv = res = std::format("{:*>240n}", ts); > >+ VERIFY( strip_prefix(resv, 34, '*') ); > >+ VERIFY( check_elems(resv) ); > >+} > >+ > > int main() > > { > > test_format_string(); > > test_outputs<char>(); > > test_outputs<wchar_t>(); > > test_nested(); > >+ test_padding(); > > } > >-- > >2.49.0 > > > > > >