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

Reply via email to