Re: [PATCH v3] libstdc++: Introduce __format::_Ptr_sink for contiguous iterators.

2026-03-09 Thread Jonathan Wakely

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
-requires same_as, _CharT>
-class _Iter_sink<_CharT, _OutIter> : public _Sink<_CharT>
+  template
+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>
-   || 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
+   static size_t
+   _S_trim

[PATCH v3] libstdc++: Introduce __format::_Ptr_sink for contiguous iterators.

2026-03-05 Thread Tomasz Kamiński
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.

 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
-requires same_as, _CharT>
-class _Iter_sink<_CharT, _OutIter> : public _Sink<_CharT>
+  template
+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>
-   || 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
+   static size_t
+   _S_trim_max(_IterDifference __max)
+   {
+ if (__max < 0)
+   return _S_no_limit;
+ if constexpr (!is_