On Tue, Feb 24, 2026 at 6:52 PM Tomasz Kamiński <[email protected]> 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 pointer to is
> still stored inside span). To produce the actual iterator position,
> _Ptr_sink::_M_finish requires original pointer to be passed. As any
> continuous 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 tree constexpr branches:
> _Sink_iter, contiguous char iterator, other iterators. The later 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.
> ---
> I belive this is beneficial by itself, but also make it easier to
> make unbounded format_to constexpr, by edditing __do_vformat_to
> to not use _Ptr_sink at compile time:
> + if constexpr (__contiguous_char_iter<_CharT, _Out>)
> + if (!std::__is_constant_evaluated())
> + {
> + _Ptr_sink<_CharT> __sink(__out);
> + __format::__do_vformat_to(__sink.out(), __fmt, __args, __loc);
> + return std::move(__sink)._M_finish(__out).out;
> + }
> + _Iter_sink<_CharT, _OutIter> __sink(std::move(__out));
> The problem with above is, that without compile-time support of
> __builtin_dynamic_object_size, _Iter_sink needs to create span from
> ptr with arbitrally size (like 1024). This is fine for GCC as constexpr
> evalutor does not detect the out of bound pointer arithmetic, but clang
> does (see https://compiler-explorer.com/z/534os894f).
> We do not have issues if the size is know, by either being provided
> (format_to_n) or if we could get it from __builtin_dynamic_object_size
> (clang does support this builting at compile time).
>
> Testing on x86_64-linux. *format* test passed, and also compile-time
> test from Ivan branch passes with that change.
> OK for truink when all test finishes?
>
All test passed.
>
>
> libstdc++-v3/include/std/format | 342 ++++++++++++++++++--------------
> 1 file changed, 193 insertions(+), 149 deletions(-)
>
> diff --git a/libstdc++-v3/include/std/format
> b/libstdc++-v3/include/std/format
> index 66bf3c246e5..245ea964675 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)
> + 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)
> + 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
> @@ -4923,6 +4942,14 @@ namespace __format
> const basic_format_args<_Context>&,
> const locale* = nullptr);
>
> + template<typename _Out, typename _CharT>
> + format_to_n_result<_Out>
> + __do_vformat_to_n(_Out, iter_difference_t<_Out>,
> + basic_string_view<_CharT>,
> + const type_identity_t<
> + basic_format_args<__format_context<_CharT>>>&,
> + const locale* = nullptr);
> +
> template<typename _CharT> struct __formatter_chrono;
>
> } // namespace __format
> @@ -5244,92 +5271,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)
> + {
> + 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
> @@ -5535,10 +5583,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
> @@ -5548,10 +5595,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
>
> @@ -5561,10 +5607,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
> @@ -5574,10 +5619,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
>
> @@ -5586,10 +5630,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
>
>