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