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
> >
> >
>
>

Reply via email to