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

Reply via email to