[gcc r16-7971] libstdc++: Introduce __format::_Ptr_sink for contiguous iterators.

Tomasz Kaminski tkaminsk@gcc.gnu.org
Tue Mar 10 07:53:27 GMT 2026


https://gcc.gnu.org/g:ee7c2ff9393a440c114fca80fd0ec127a7ac3017

commit r16-7971-gee7c2ff9393a440c114fca80fd0ec127a7ac3017
Author: Tomasz Kamiński <tkaminsk@redhat.com>
Date:   Tue Feb 24 16:17:30 2026 +0100

    libstdc++: Introduce __format::_Ptr_sink for contiguous iterators.
    
    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.
    
    Reviewed-by: Jonathan Wakely <jwakely@redhat.com>
    Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>

Diff:
---
 libstdc++-v3/include/std/format | 328 ++++++++++++++++++++++------------------
 1 file changed, 182 insertions(+), 146 deletions(-)

diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
index cfb57f4a7a66..eaf128f5ca6f 100644
--- a/libstdc++-v3/include/std/format
+++ b/libstdc++-v3/include/std/format
@@ -3655,19 +3655,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:
@@ -3682,7 +3682,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,
@@ -3694,7 +3694,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());
 	  }
       }
 
@@ -3712,74 +3712,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)
+	    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() &&
+      _Ptr_sink(_CharT* __ptr, size_t __n = _S_no_limit) noexcept
+      : _Sink<_CharT>(_M_buf), _M_max(__n)
       {
-	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
+      	if (__n == 0)
+	  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
@@ -5206,92 +5225,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));
+		}, __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;
+		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
@@ -5497,10 +5537,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
@@ -5510,10 +5549,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
 
@@ -5523,10 +5561,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
@@ -5536,10 +5573,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
 
@@ -5548,10 +5584,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


More information about the Libstdc++-cvs mailing list