On Fri, Oct 23, 2026 at 11:08 PM Nathan Myers <[email protected]> wrote:
> Constructing a string from a streambuf iterator pair or range has
> been found to be 3x faster using the streambuf member `sgetn`.
> This is the minimal implementation as outlined in [PR112925], not
> checking for an underlying filebuf.
>
> libstdc++-v3/ChangeLog:
> PR libstdc++/112925
> * include/bits/basic_string.h: (basic_string range ctor)
> Special-case ranges of streambuf iterators.
> * include/bits/basic_string.tcc: (_M_construct) Special-case
> streambuf iterators. These always consume the whole stream.
> * testsuite/21_strings/basic_string/cons/from_range2.cc:
> New test.
> ---
> libstdc++-v3/include/bits/basic_string.h | 6 +++-
> libstdc++-v3/include/bits/basic_string.tcc | 15 ++++++++++
> .../basic_string/cons/from_range2.cc | 29 +++++++++++++++++++
> 3 files changed, 49 insertions(+), 1 deletion(-)
> create mode 100644
> libstdc++-v3/testsuite/21_strings/basic_string/cons/from_range2.cc
>
> diff --git a/libstdc++-v3/include/bits/basic_string.h
> b/libstdc++-v3/include/bits/basic_string.h
> index fd9419220a4..513ea274ca7 100644
> --- a/libstdc++-v3/include/bits/basic_string.h
> +++ b/libstdc++-v3/include/bits/basic_string.h
> @@ -826,7 +826,11 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
> basic_string(from_range_t, _Rg&& __rg, const _Alloc& __a =
> _Alloc())
> : basic_string(__a)
> {
> - if constexpr (ranges::forward_range<_Rg> ||
> ranges::sized_range<_Rg>)
> + using _SbufIter = istreambuf_iterator<_CharT,
> char_traits<_CharT>>;
> + if constexpr(is_same_v<ranges::iterator_t<_Rg>, _SbufIter>)
>
I do not think this condition is sufficient, as the end may not be
isteambuf_iterator, for example
when you apply take_while on the subrange of istreambuf_iterators
(take_while_view only
changes the sentinel). You should check that ranges::sentinel_t is
either _SbufIter or
default_sentinel (sentinel supported by istreambuf iterator).
+ _M_construct(__rg.begin(), _SbufIter(), input_iterator_tag{});
> + else if constexpr (
> + ranges::forward_range<_Rg> || ranges::sized_range<_Rg>)
> {
> const auto __n =
> static_cast<size_type>(ranges::distance(__rg));
> reserve(__n);
> diff --git a/libstdc++-v3/include/bits/basic_string.tcc
> b/libstdc++-v3/include/bits/basic_string.tcc
> index de82e997888..d68a4492ef0 100644
> --- a/libstdc++-v3/include/bits/basic_string.tcc
> +++ b/libstdc++-v3/include/bits/basic_string.tcc
> @@ -199,6 +199,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>
> _M_init_local_buf();
>
> + // NB: size() (i.e. _M_length) is initialized only at the end.
> while (__beg != __end && __len < __capacity)
> {
> _M_local_buf[__len++] = *__beg;
> @@ -228,6 +229,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> _M_data(__another.__ptr);
> _M_capacity(__capacity);
> }
> +
> +#if __cplusplus >= 201103L
> + using _SbufIter = istreambuf_iterator<_CharT,
> char_traits<_CharT>>;
> + if _GLIBCXX17_CONSTEXPR (is_same<_InIterator,
> _SbufIter>::value)
> + {
> + if (!std::__is_constant_evaluated())
> + {
> + auto __r = std::__copy_n_a(__beg, __capacity - __len,
> + data() + __len, false);
> + __len = __r - data();
> + continue;
> + }
> + }
> +#endif
> traits_type::assign(_M_data()[__len++],
> static_cast<_CharT>(*__beg));
> ++__beg;
> diff --git
> a/libstdc++-v3/testsuite/21_strings/basic_string/cons/from_range2.cc
> b/libstdc++-v3/testsuite/21_strings/basic_string/cons/from_range2.cc
> new file mode 100644
> index 00000000000..33a654f35af
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/21_strings/basic_string/cons/from_range2.cc
> @@ -0,0 +1,29 @@
> +// { dg-do run { target c++23 } }
> +
> +#include <string>
> +#include <sstream>
> +#include <streambuf>
> +#include <ranges>
> +#include <testsuite_hooks.h>
> +
> +struct istreambuf_view : std::ranges::view_interface<istreambuf_view> {
> + std::istream& str;
> + istreambuf_view(std::istream& str) : str(str) {}
> + auto begin() { return std::istreambuf_iterator<char>(str); }
> + auto end() { return std::istreambuf_iterator<char>(); }
> +};
> +
> +constexpr void
> +do_test()
> +{
> + std::string abcs{"abcdefghijklmnopqrstuvwxyz"};
> + std::istringstream stream{abcs + abcs};
> + istreambuf_view range(stream);
>
You could simply use, but having own view is also fine.
std::ranges::subrange<std::istreambuf_iterator<char>>
range(std::istreambuf_iterator<char>(str), std::istreambuf_iterator<char>());
> + std::basic_string str{std::from_range, range};
+ VERIFY(str == abcs + abcs);
>
Please add the test with range | std::views::take_while([] (char c) {
return c < "f" }); This should produce "abcde".
> +}
> +
> +int main()
> +{
> + do_test();
> +}
> --
> 2.56.0
>
>