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