[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