On Mon, Oct 26, 2026 at 8:21 AM Tomasz Kaminski <[email protected]> wrote:

>
>
> 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>());
>
You could also test:
          std::rangs::subrange r2(std::istreambuf_iterator<char>(str),
std::default_sentinel);

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