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

Jonathan Wakely jwakely@redhat.com
Mon Mar 9 21:13:48 GMT 2026


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



More information about the Libstdc++ mailing list