https://gcc.gnu.org/g:01e5ef3e8b91288f5d387a27708f9f8979a50edf

commit r16-142-g01e5ef3e8b91288f5d387a27708f9f8979a50edf
Author: Tomasz Kamiński <tkami...@redhat.com>
Date:   Wed Apr 23 13:17:09 2025 +0200

    libstdc++: Minimalize temporary allocations when width is specified 
[PR109162]
    
    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,
    only 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.
    
    Reviewed-by: Jonathan Wakely <jwak...@redhat.com>
    Signed-off-by: Tomasz Kamiński <tkami...@redhat.com>

Diff:
---
 libstdc++-v3/include/bits/formatfwd.h              |   8 +
 libstdc++-v3/include/std/format                    | 399 +++++++++++++++------
 libstdc++-v3/testsuite/std/format/debug.cc         | 388 +++++++++++++++++++-
 .../testsuite/std/format/ranges/sequence.cc        | 116 ++++++
 libstdc++-v3/testsuite/std/format/ranges/string.cc |  63 ++++
 libstdc++-v3/testsuite/std/format/tuple.cc         |  93 +++++
 6 files changed, 961 insertions(+), 106 deletions(-)

diff --git a/libstdc++-v3/include/bits/formatfwd.h 
b/libstdc++-v3/include/bits/formatfwd.h
index 12ae2ad2ac08..3fa01ad1eab7 100644
--- a/libstdc++-v3/include/bits/formatfwd.h
+++ b/libstdc++-v3/include/bits/formatfwd.h
@@ -145,6 +145,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 86c93f0e6ebd..69d8d189db62 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>
@@ -885,6 +878,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,
@@ -1320,82 +1332,111 @@ 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), __w),
+                   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
@@ -1404,23 +1445,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{};
     };
 
@@ -3294,12 +3318,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
@@ -3371,6 +3395,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?
@@ -3396,7 +3431,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())
@@ -3407,9 +3442,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>
@@ -3577,6 +3624,173 @@ 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)
+  // 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,
+       // write direclty to _M_out.
+       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.
+       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.
+       _Sink<_CharT>::_M_bump(__n);
+       if (_M_buffering())
+         _M_update(__n);
+      }
+
+    public:
+      [[__gnu__::__always_inline__]]
+      explicit _Padding_sink(_Out __out, size_t __padwidth)
+      : _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)
+      : _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,
@@ -5183,7 +5397,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
@@ -5192,48 +5407,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 71bb7f4a0fe8..d3402f80f4b6 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>
@@ -96,7 +97,7 @@ test_extended_ascii()
   res = fdebug(in);
   VERIFY( res == WIDEN(R"("Åëÿ")") );
 
-  static constexpr bool __test_characters 
+  static constexpr bool __test_characters
 #if UNICODE_ENC
     = sizeof(_CharT) >= 2;
 #else // ISO8859-1
@@ -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 f05f6ec1e1ca..75fe4c19a523 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 cf39aa66e071..cebdd5301680 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 62f9d293aab5..ff0359b9aba3 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();
 }

Reply via email to