On Thu, 05 Mar 2026 at 14:39 +0100, Tomasz Kamiński wrote:
This patch replaces the _Iter_sink specialization for contiguous
iterators of _CharT, with separate _Ptr_sink. This allow further
reduce the number of instantiations as single _Ptr_sink<_CharT>
specialization is used for all such iterators.

To make _Ptr_sink independent of iterator type, we no longer store
value of the iterator as _M_first member (the address pointed to is
still stored inside span). To produce the actual iterator position,
_Ptr_sink::_M_finish requires original pointer to be passed. As any
contiguous iterator is copyable, there is no issue in passing them
to both _Ptr_sink constructor and _M_finish method.

The __do_vformat_to method is reworked, to use _Ptr_sink when possible
(__contiguous_char_iterator is true). The implementation approach is
also changed to split the function into three constexpr branches:
_Sink_iter, contiguous char iterator, other iterators. The latter two
wrap iterators in _Ptr_sink and _Iter_sink respectively and forward
to _Sink_iter specialization.

To reduce the duplication, we introduce __do_format_to_n helper function,
that also handles wrapping iterator into _Ptr_sink and _Iter_sink as
appropriate.

libstdc++-v3/ChangeLog:

        * include/std/format (__format::_Ptr_sink): Reworked
        from _Iter_sink specialization below.
        (__format::_Iter_sink<_CharT, contiguous_iterator _OutIter>):
        Renamed and reworked to _Ptr_sink.
        (__format::__contiguous_char_iter, __format::__do_vformat_to_n):
        Define.
        (__format::__do_vformat_to): Use _Ptr_sink when appropriate,
        and delegate to _Sink_iter specialization.
        (std::format_to_n): Delegate to __do_vformat_to_n.
        (__format::_Counting_sink): Use _Ptr_sink as base class.
---
v3 add missing default argument for parameter that I have forgotten
to include in commit.

Just two whitespace tweaks noted below.

OK for trunk with those changes, thanks.

libstdc++-v3/include/std/format | 334 ++++++++++++++++++--------------
1 file changed, 185 insertions(+), 149 deletions(-)

diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
index 786edbe29b2..ec3d0018efd 100644
--- a/libstdc++-v3/include/std/format
+++ b/libstdc++-v3/include/std/format
@@ -3699,19 +3699,19 @@ namespace __format
      }
    };

-  // Partial specialization for contiguous iterators.
+  // Used for contiguous iterators.
  // No buffer is used, characters are written straight to the iterator.
  // We do not know the size of the output range, so the span size just grows
  // as needed. The end of the span might be an invalid pointer outside the
  // valid range, but we never actually call _M_span.end(). This class does
  // not introduce any invalid pointer arithmetic or overflows that would not
  // have happened anyway.
-  template<typename _CharT, contiguous_iterator _OutIter>
-    requires same_as<iter_value_t<_OutIter>, _CharT>
-    class _Iter_sink<_CharT, _OutIter> : public _Sink<_CharT>
+  template<typename _CharT>
+    class _Ptr_sink : public _Sink<_CharT>
    {
-      _OutIter _M_first;
-      iter_difference_t<_OutIter> _M_max = -1;
+      static constexpr size_t _S_no_limit = size_t(-1);
+
+      size_t _M_max;
    protected:
      size_t _M_count = 0;
    private:
@@ -3726,7 +3726,7 @@ namespace __format

        auto __s = this->_M_used();

-       if (_M_max >= 0)
+       if (_M_max != _S_no_limit)
          {
            _M_count += __s.size();
            // Span was already sized for the maximum character count,
@@ -3738,7 +3738,7 @@ namespace __format
          {
            // No maximum character count. Just extend the span to allow
            // writing more characters to it.
-           this->_M_reset({__s.data(), __s.size() + 1024}, __s.size());
+           _M_rebuf(__s.data(), __s.size() + 1024, __s.size());
          }
      }

@@ -3756,74 +3756,93 @@ namespace __format
        auto __avail = this->_M_unused();
        if (__n > __avail.size())
          {
-           if (_M_max >= 0)
+           if (_M_max != _S_no_limit)
              return {}; // cannot grow

            auto __s = this->_M_used();
-           this->_M_reset({__s.data(), __s.size() + __n}, __s.size());
+           _M_rebuf(__s.data(), __s.size() + __n, __s.size());
          }
        return { this };
      }

    private:
-      static span<_CharT>
-      _S_make_span(_CharT* __ptr, iter_difference_t<_OutIter> __n,
-                  span<_CharT> __buf) noexcept
-      {
-       if (__n == 0)
-         return __buf; // Only write to the internal buffer.
-
-       if (__n > 0)
-         {
-           if constexpr (!is_integral_v<iter_difference_t<_OutIter>>
-                           || sizeof(__n) > sizeof(size_t))
-             {
-               // __int128 or __detail::__max_diff_type
-               auto __m = iter_difference_t<_OutIter>((size_t)-1);
-               if (__n > __m)
-                 __n = __m;
-             }
-           return {__ptr, (size_t)__n};
-         }
+      template<typename _IterDifference>
+       static size_t
+       _S_trim_max(_IterDifference __max)
+       {
+         if (__max < 0)

The line above has leading spaces before the tab character.

I use the following settings to show me those:

git config core.whitespace 
trailing-space,space-before-tab,tabwidth=8,indent-with-non-tab
git config diff.wsErrorHighlight old,new


+           return _S_no_limit;
+         if constexpr (!is_integral_v<_IterDifference> || sizeof(__max) > 
sizeof(size_t))
+           // __int128 or __detail::__max_diff_type
+           if (_IterDifference((size_t)-1) < __max)
+             return _S_no_limit;
+         return size_t(__max);
+       }

-#if __has_builtin(__builtin_dynamic_object_size)
-       if (size_t __bytes = __builtin_dynamic_object_size(__ptr, 2))
-         return {__ptr, __bytes / sizeof(_CharT)};
-#endif
-       // Avoid forming a pointer to a different memory page.
-       const auto __off = reinterpret_cast<__UINTPTR_TYPE__>(__ptr) % 1024;
-       __n = (1024 - __off) / sizeof(_CharT);
-       if (__n > 0) [[likely]]
-         return {__ptr, static_cast<size_t>(__n)};
-       else // Misaligned/packed buffer of wchar_t?
-         return {__ptr, 1};
+      [[__gnu__::__always_inline__]]
+      void
+      _M_rebuf(_CharT* __ptr, size_t __total, size_t __inuse = 0)
+      {
+       std::span<_CharT> __span(__ptr, __total);
+       this->_M_reset(__span, __inuse);
      }

    public:
      explicit
-      _Iter_sink(_OutIter __out, iter_difference_t<_OutIter> __n = -1) noexcept
-      : _Sink<_CharT>(_S_make_span(std::to_address(__out), __n, _M_buf)),
-       _M_first(__out), _M_max(__n)
-      { }
-
-      format_to_n_result<_OutIter>
-      _M_finish() &&
-      {
-       auto __s = this->_M_used();
-       if (__s.data() == _M_buf)
-         {
-           // Switched to internal buffer, so must have written _M_max.
-           iter_difference_t<_OutIter> __count(_M_count + __s.size());
-           return { _M_first + _M_max, __count };
-         }
-       else // Not using internal buffer yet
+      _Ptr_sink(_CharT* __ptr, size_t __n = _S_no_limit) noexcept
+      : _Sink<_CharT>(_M_buf), _M_max(__n)
+      {
+       if (__n == 0)

Another case of spaces before a tab.

+         return; // Only write to the internal buffer.
+       else if (__n != _S_no_limit)
+         _M_rebuf(__ptr, __n);
+#if __has_builtin(__builtin_dynamic_object_size)
+       else if (size_t __bytes = __builtin_dynamic_object_size(__ptr, 2))
+         _M_rebuf(__ptr, __bytes / sizeof(_CharT));
+#endif
+       else
          {
-           iter_difference_t<_OutIter> __count(__s.size());
-           return { _M_first + __count, __count };
+           // Avoid forming a pointer to a different memory page.
+           const auto __off = reinterpret_cast<__UINTPTR_TYPE__>(__ptr) % 1024;
+           __n = (1024 - __off) / sizeof(_CharT);
+           if (__n > 0) [[likely]]
+             _M_rebuf(__ptr, __n);
+           else // Misaligned/packed buffer of wchar_t?
+             _M_rebuf(__ptr, 1);
          }
      }
+
+      template<contiguous_iterator _OutIter>
+       explicit
+       _Ptr_sink(_OutIter __out, iter_difference_t<_OutIter> __n = -1)
+       : _Ptr_sink(std::to_address(__out), _S_trim_max(__n))
+       { }
+
+      template<contiguous_iterator _OutIter>
+       format_to_n_result<_OutIter>
+       _M_finish(_OutIter __first) const
+       {
+         auto __s = this->_M_used();
+         if (__s.data() == _M_buf)
+           {
+             // Switched to internal buffer, so must have written _M_max.
+             iter_difference_t<_OutIter> __max(_M_max);
+             iter_difference_t<_OutIter> __count(_M_count + __s.size());
+             return { __first + __max, __count };
+           }
+         else // Not using internal buffer yet
+           {
+             iter_difference_t<_OutIter> __count(__s.size());
+             return { __first + __count, __count };
+           }
+        }
    };

+  template<typename _CharT, typename _OutIter>
+    concept __contiguous_char_iter
+      = contiguous_iterator<_OutIter>
+       && same_as<iter_value_t<_OutIter>, _CharT>;
+
  // 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
@@ -5250,92 +5269,113 @@ namespace __format
                    const basic_format_args<_Context>& __args,
                    const locale* __loc)
    {
-      _Iter_sink<_CharT, _Out> __sink(std::move(__out));
-      _Sink_iter<_CharT> __sink_out;
-
      if constexpr (is_same_v<_Out, _Sink_iter<_CharT>>)
-       __sink_out = __out; // Already a sink iterator, safe to use post-move.
-      else
-       __sink_out = __sink.out();
-
-      if constexpr (is_same_v<_CharT, char>)
-       // Fast path for "{}" format strings and simple format arg types.
-       if (__fmt.size() == 2 && __fmt[0] == '{' && __fmt[1] == '}')
-         {
-           bool __done = false;
-           __format::__visit_format_arg([&](auto& __arg) {
-             using _Tp = remove_cvref_t<decltype(__arg)>;
-             if constexpr (is_same_v<_Tp, bool>)
-               {
-                 size_t __len = 4 + !__arg;
-                 const char* __chars[] = { "false", "true" };
-                 if (auto __res = __sink_out._M_reserve(__len))
+       {
+         if constexpr (is_same_v<_CharT, char>)
+           // Fast path for "{}" format strings and simple format arg types.
+           if (__fmt.size() == 2 && __fmt[0] == '{' && __fmt[1] == '}')
+             {
+               bool __done = false;
+               __format::__visit_format_arg([&](auto& __arg) {
+                 using _Tp = remove_cvref_t<decltype(__arg)>;
+                 if constexpr (is_same_v<_Tp, bool>)
                    {
-                     __builtin_memcpy(__res.get(), __chars[__arg], __len);
-                     __res._M_bump(__len);
-                     __done = true;
+                     size_t __len = 4 + !__arg;
+                     const char* __chars[] = { "false", "true" };
+                     if (auto __res = __out._M_reserve(__len))
+                       {
+                         __builtin_memcpy(__res.get(), __chars[__arg], __len);
+                         __res._M_bump(__len);
+                         __done = true;
+                       }
                    }
-               }
-             else if constexpr (is_same_v<_Tp, char>)
-               {
-                 if (auto __res = __sink_out._M_reserve(1))
+                 else if constexpr (is_same_v<_Tp, char>)
                    {
-                     *__res.get() = __arg;
-                     __res._M_bump(1);
-                     __done = true;
+                     if (auto __res = __out._M_reserve(1))
+                       {
+                         *__res.get() = __arg;
+                         __res._M_bump(1);
+                         __done = true;
+                       }
                    }
-               }
-             else if constexpr (is_integral_v<_Tp>)
-               {
-                 make_unsigned_t<_Tp> __uval;
-                 const bool __neg = __arg < 0;
-                 if (__neg)
-                   __uval = make_unsigned_t<_Tp>(~__arg) + 1u;
-                 else
-                   __uval = __arg;
-                 const auto __n = __detail::__to_chars_len(__uval);
-                 if (auto __res = __sink_out._M_reserve(__n + __neg))
+                 else if constexpr (is_integral_v<_Tp>)
                    {
-                     auto __ptr = __res.get();
-                     *__ptr = '-';
-                     __detail::__to_chars_10_impl(__ptr + (int)__neg, __n,
-                                                  __uval);
-                     __res._M_bump(__n + __neg);
-                     __done = true;
+                     make_unsigned_t<_Tp> __uval;
+                     const bool __neg = __arg < 0;
+                     if (__neg)
+                       __uval = make_unsigned_t<_Tp>(~__arg) + 1u;
+                     else
+                       __uval = __arg;
+                     const auto __n = __detail::__to_chars_len(__uval);
+                     if (auto __res = __out._M_reserve(__n + __neg))
+                       {
+                         auto __ptr = __res.get();
+                         *__ptr = '-';
+                         __detail::__to_chars_10_impl(__ptr + (int)__neg, __n,
+                                                      __uval);
+                         __res._M_bump(__n + __neg);
+                         __done = true;
+                       }
                    }
-               }
-             else if constexpr (is_convertible_v<_Tp, string_view>)
-               {
-                 string_view __sv = __arg;
-                 if (auto __res = __sink_out._M_reserve(__sv.size()))
+                 else if constexpr (is_convertible_v<_Tp, string_view>)
                    {
-                     __builtin_memcpy(__res.get(), __sv.data(), __sv.size());
-                     __res._M_bump(__sv.size());
-                     __done = true;
+                     string_view __sv = __arg;
+                     if (auto __res = __out._M_reserve(__sv.size()))
+                       {
+                         __builtin_memcpy(__res.get(), __sv.data(), 
__sv.size());
+                         __res._M_bump(__sv.size());
+                         __done = true;
+                       }
                    }
-               }
-           }, __args.get(0));
-
-           if (__done)
-             {
-               if constexpr (is_same_v<_Out, _Sink_iter<_CharT>>)
-                 return __sink_out;
-               else
-                 return std::move(__sink)._M_finish().out;
+               }, __args.get(0));
+
+               if (__done)
+                 return __out;
              }
-         }
-
-      auto __ctx = __loc == nullptr
-                    ? _Context(__args, __sink_out)
-                    : _Context(__args, __sink_out, *__loc);
-      _Formatting_scanner<_Sink_iter<_CharT>, _CharT> __scanner(__ctx, __fmt);
-      __scanner._M_scan();
+
+         auto __ctx = __loc == nullptr
+                        ? _Context(__args, __out)
+                        : _Context(__args, __out, *__loc);
+         _Formatting_scanner<_Sink_iter<_CharT>, _CharT> __scanner(__ctx, 
__fmt);
+         __scanner._M_scan();
+         return __out;
+       }
+      else if constexpr (__contiguous_char_iter<_CharT, _Out>)
+       {
+         _Ptr_sink<_CharT> __sink(__out);
+         __format::__do_vformat_to(__sink.out(), __fmt, __args, __loc);
+         return std::move(__sink)._M_finish(__out).out;
+       }
+      else
+       {
+         _Iter_sink<_CharT, _Out> __sink(std::move(__out));
+         __format::__do_vformat_to(__sink.out(), __fmt, __args, __loc);
+         return std::move(__sink)._M_finish().out;
+       }
+    }

-      if constexpr (is_same_v<_Out, _Sink_iter<_CharT>>)
-       return __ctx.out();
+  template<typename _Out, typename _CharT>
+    format_to_n_result<_Out>
+    __do_vformat_to_n(_Out __out, iter_difference_t<_Out> __n,
+                     basic_string_view<_CharT> __fmt,
+                     const type_identity_t<
+                       basic_format_args<__format_context<_CharT>>>& __args,
+                     const locale* __loc = nullptr)
+    {
+      if constexpr (__contiguous_char_iter<_CharT, _Out>)
+       {
+         _Ptr_sink<_CharT> __sink(__out, __n);
+         __format::__do_vformat_to(__sink.out(), __fmt, __args, __loc);
+         return std::move(__sink)._M_finish(__out);
+       }
      else
-       return std::move(__sink)._M_finish().out;
+       {
+         _Iter_sink<_CharT, _Out> __sink(std::move(__out), __n);
+         __format::__do_vformat_to(__sink.out(), __fmt, __args, __loc);
+         return std::move(__sink)._M_finish();
+       }
    }
+
#pragma GCC diagnostic pop

} // namespace __format
@@ -5541,10 +5581,9 @@ namespace __format
    format_to_n(_Out __out, iter_difference_t<_Out> __n,
                format_string<_Args...> __fmt, _Args&&... __args)
    {
-      __format::_Iter_sink<char, _Out> __sink(std::move(__out), __n);
-      std::vformat_to(__sink.out(), __fmt.get(),
-                     std::make_format_args(__args...));
-      return std::move(__sink)._M_finish();
+      return __format::__do_vformat_to_n(
+              std::move(__out), __n, __fmt.get(),
+              std::make_format_args(__args...));
    }

#ifdef _GLIBCXX_USE_WCHAR_T
@@ -5554,10 +5593,9 @@ namespace __format
    format_to_n(_Out __out, iter_difference_t<_Out> __n,
                wformat_string<_Args...> __fmt, _Args&&... __args)
    {
-      __format::_Iter_sink<wchar_t, _Out> __sink(std::move(__out), __n);
-      std::vformat_to(__sink.out(), __fmt.get(),
-                     std::make_wformat_args(__args...));
-      return std::move(__sink)._M_finish();
+      return __format::__do_vformat_to_n(
+              std::move(__out), __n, __fmt.get(),
+              std::make_wformat_args(__args...));
    }
#endif

@@ -5567,10 +5605,9 @@ namespace __format
    format_to_n(_Out __out, iter_difference_t<_Out> __n, const locale& __loc,
                format_string<_Args...> __fmt, _Args&&... __args)
    {
-      __format::_Iter_sink<char, _Out> __sink(std::move(__out), __n);
-      std::vformat_to(__sink.out(), __loc, __fmt.get(),
-                     std::make_format_args(__args...));
-      return std::move(__sink)._M_finish();
+      return __format::__do_vformat_to_n(
+              std::move(__out), __n, __fmt.get(),
+              std::make_format_args(__args...), &__loc);
    }

#ifdef _GLIBCXX_USE_WCHAR_T
@@ -5580,10 +5617,9 @@ namespace __format
    format_to_n(_Out __out, iter_difference_t<_Out> __n, const locale& __loc,
                wformat_string<_Args...> __fmt, _Args&&... __args)
    {
-      __format::_Iter_sink<wchar_t, _Out> __sink(std::move(__out), __n);
-      std::vformat_to(__sink.out(), __loc, __fmt.get(),
-                     std::make_wformat_args(__args...));
-      return std::move(__sink)._M_finish();
+      return __format::__do_vformat_to_n(
+              std::move(__out), __n, __fmt.get(),
+              std::make_wformat_args(__args...), &__loc);
    }
#endif

@@ -5592,10 +5628,10 @@ namespace __format
{
#if 1
  template<typename _CharT>
-    class _Counting_sink final : public _Iter_sink<_CharT, _CharT*>
+    class _Counting_sink final : public _Ptr_sink<_CharT>
    {
    public:
-      _Counting_sink() : _Iter_sink<_CharT, _CharT*>(nullptr, 0) { }
+      _Counting_sink() : _Ptr_sink<_CharT>(nullptr, 0) { }

      [[__gnu__::__always_inline__]]
      size_t
--
2.53.0



Reply via email to