"contiguous" in the subject line
On Tue, 24 Feb 2026 at 17:51, 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 "pointer to" -> "pointed to" ? > 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 "continuous" -> "contiguous iterator" > _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: "three" > _Sink_iter, contiguous char iterator, other iterators. The later two "latter" > 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. I haven't reviewed the code yet, will send another mail later ... > > 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? > > > 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 >
