On Mon, Feb 16, 2026 at 5:12 PM Patrick Palka <[email protected]> wrote:
> On Mon, 16 Feb 2026, Tomasz Kamiński wrote: > > > This paper implement the changes from P2438R2 basic_string::substr() && > > paper into C++26. The additional substr and constructor overload are > > implemented only for SSO string, as they require mutating the content > > (if reused), and thus would require copy of the string anyway for COW > > strings. (In consequence allocators implicitly constructible from int > > remain ambiguous for C++11 ABI strings, see r16-7497-gfa1149534d8580). > > > > In addition to cases when the allocators are not compatible (equal), > > this patch does not reuse (transfer) allocator storage, if the selected > > substring fits inside the SSO buffer, so we do not risk keeping large > > chunk of memory for few characters. (This also covers cases when the > > source stored the content in the local buffer). > > > > As this additional overloads are meant to be optimization, in contrast > > to move constructor, the source is left unmodified if the allocation > > is not transferred. This avoid introducing a write (of null terminator) > > to previously untouched, heap allocated, memory. > > > > Separate overloads for and substr(size_type __pos, size_type __n) and > > substr(size_type __pos == 0), that delegate to corresponding constructor, > > are provided to avoid the check of __n against the length() in the later > > case. > > > > Finally, the signatures of existing substr() overload are not modified > > (no longer required since C++20), which avoid any impact on the ABI. > > > > PR libstdc++/119745 > > > > libstdc++-v3/ChangeLog: > > > > * include/bits/basic_string.h (basic_string::_M_construct) > > [__cplusplus >= 202302L]: Declare. > > (basic_string::basic_string(basic_string&&, size_type, const > _Alloc&)) > > (basic_string(basic_string&&, size_type, size_type, const _Alloc&)) > > (basic_string::substr(size_type, size_type) &&) > > ((basic_string::substr(size_type) &&) [__cplusplus >= 202302L]: > Define. > > * include/bits/basic_string.tcc (basic_string::_M_construct) > > [__cplusplus >= 202302L]: Define. > > * testsuite/21_strings/basic_string/operations/substr/rvalue.cc: > New test. > > > > Reviewed-by: Jonathan Wakely <[email protected]> > > Reviewed-by: Patrick Palka <[email protected]> > > Signed-off-by: Tomasz Kamiński <[email protected]> > > --- > > v2: > > - fixes _S_local_capacity test and add test for selection of 15 chars. > > - fixes missing and in commit description > > - "enough" should be spelled correctly now > > - add // C++23 comments for langauge version > > - fixes whitespace > > > > Tested on x86_64-linux locally. rvalue.cc also tested with all > > supported language version, debug and old ABI. Full-test in progress. > > OK for trunk when test passes? > > > > > > libstdc++-v3/include/bits/basic_string.h | 37 ++ > > libstdc++-v3/include/bits/basic_string.tcc | 32 ++ > > .../basic_string/operations/substr/rvalue.cc | 359 ++++++++++++++++++ > > 3 files changed, 428 insertions(+) > > create mode 100644 > libstdc++-v3/testsuite/21_strings/basic_string/operations/substr/rvalue.cc > > > > diff --git a/libstdc++-v3/include/bits/basic_string.h > b/libstdc++-v3/include/bits/basic_string.h > > index cd6f312f1bd..864b6ab373e 100644 > > --- a/libstdc++-v3/include/bits/basic_string.h > > +++ b/libstdc++-v3/include/bits/basic_string.h > > @@ -354,6 +354,11 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > > void > > _M_construct(const _CharT *__c, size_type __n); > > > > +#if __cplusplus >= 202302L > > + constexpr void > > + _M_construct(basic_string&& __str, size_type __pos, size_type > __n); > > +#endif > > + > > _GLIBCXX20_CONSTEXPR > > allocator_type& > > _M_get_allocator() > > @@ -671,6 +676,26 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > > std::forward_iterator_tag()); > > } > > > > +#if __cplusplus >= 202302L // C++23 > > Small nit but I think the // C++23 goes on the endif line, not on the > if, to match the de facto convention. Besides that LGTM > Changed locally. > > > + _GLIBCXX20_CONSTEXPR > > + basic_string(basic_string&& __str, size_type __pos, > > + const _Alloc& __a = _Alloc()) > > + : _M_dataplus(_M_local_data(), __a) > > + { > > + __pos = __str._M_check(__pos, "string::string"); > > + _M_construct(std::move(__str), __pos, __str.length() - __pos); > > + } > > + > > + _GLIBCXX20_CONSTEXPR > > + basic_string(basic_string&& __str, size_type __pos, size_type __n, > > + const _Alloc& __a = _Alloc()) > > + : _M_dataplus(_M_local_data(), __a) > > + { > > + __pos = __str._M_check(__pos, "string::string"); > > + _M_construct(std::move(__str), __pos, __str._M_limit(__pos, __n)); > > + } > > +#endif > > + > > /** > > * @brief Construct string initialized by a character %array. > > * @param __s Source character %array. > > @@ -3442,6 +3467,18 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > > { return basic_string(*this, > > _M_check(__pos, "basic_string::substr"), __n); > } > > > > +#if __cplusplus >= 202302L // C++23 > > + _GLIBCXX_NODISCARD > > + constexpr basic_string > > + substr(size_type __pos = 0) && > > + { return basic_string(std::move(*this), __pos); } > > + > > + _GLIBCXX_NODISCARD > > + constexpr basic_string > > + substr(size_type __pos, size_type __n) && > > + { return basic_string(std::move(*this), __pos, __n); } > > +#endif // __cplusplus > > + > > #ifdef __glibcxx_string_subview // >= C++26 > > /** > > * @brief Get a subview. > > diff --git a/libstdc++-v3/include/bits/basic_string.tcc > b/libstdc++-v3/include/bits/basic_string.tcc > > index 75ffea42ced..4677e776bdd 100644 > > --- a/libstdc++-v3/include/bits/basic_string.tcc > > +++ b/libstdc++-v3/include/bits/basic_string.tcc > > @@ -302,6 +302,38 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > traits_type::assign(_M_data()[__n], _CharT()); > > } > > > > +#if __cplusplus >= 202302L // C++23 > > + template<typename _CharT, typename _Traits, typename _Alloc> > > + constexpr void > > + basic_string<_CharT, _Traits, _Alloc>:: > > + _M_construct(basic_string&& __str, size_type __pos, size_type __n) > > + { > > + const _CharT* __start = __str._M_data() + __pos; > > + if (__n <= _S_local_capacity) > > + { > > + _M_init_local_buf(); > > + traits_type::copy(_M_local_buf, __start, __n); > > + _M_set_length(__n); > > + return; > > + } > > + > > + if constexpr (!allocator_traits<_Alloc>::is_always_equal::value) > > + if (get_allocator() != __str.get_allocator()) > > + { > > + _M_construct<false>(__start, __n); > > + return; > > + } > > + > > + _M_data(__str._M_data()); > > + _M_capacity(__str._M_allocated_capacity); > > + __str._M_data(__str._M_use_local_data()); > > + __str._M_set_length(0); > > + > > + _S_move(_M_data(), _M_data() + __pos, __n); > > + _M_set_length(__n); > > + } > > +#endif > > + > > template<typename _CharT, typename _Traits, typename _Alloc> > > _GLIBCXX20_CONSTEXPR > > void > > diff --git > a/libstdc++-v3/testsuite/21_strings/basic_string/operations/substr/rvalue.cc > b/libstdc++-v3/testsuite/21_strings/basic_string/operations/substr/rvalue.cc > > new file mode 100644 > > index 00000000000..70ccea6f06f > > --- /dev/null > > +++ > b/libstdc++-v3/testsuite/21_strings/basic_string/operations/substr/rvalue.cc > > @@ -0,0 +1,359 @@ > > +// { dg-do run { target c++26 } } > > + > > +#include <string_view> > > +#include <testsuite_hooks.h> > > +#include <testsuite_allocator.h> > > + > > +// When selection is short enough to fit into SSO, the rhs > > +// is left unchanged. > > +template<typename CharT, typename Allocator> > > +constexpr void > > +do_test_short_in(const CharT* cstr, Allocator alloc) > > +{ > > + using StringView = std::basic_string_view<CharT>; > > + using String = std::basic_string<CharT, std::char_traits<CharT>, > Allocator>; > > + > > + const Allocator defAlloc; > > + String src, res; > > + > > + // substr(0) > > + src = String(cstr, alloc); > > + res = std::move(src).substr(0); > > + VERIFY( res == StringView(cstr).substr(0) ); > > + VERIFY( res.get_allocator() == defAlloc ); > > + VERIFY( src == cstr ); > > + > > + src = String(cstr, alloc); > > + String res1(std::move(src), 0); > > + VERIFY( res1 == StringView(cstr).substr(0) ); > > + VERIFY( res1.get_allocator() == defAlloc ); > > + VERIFY( src == cstr ); > > + > > + src = String(cstr, alloc); > > + String res2(std::move(src), 0, alloc); > > + VERIFY( res2 == StringView(cstr).substr(0) ); > > + VERIFY( res2.get_allocator() == alloc ); > > + VERIFY( src == cstr ); > > + > > + // substr(1) > > + src = String(cstr, alloc); > > + res = std::move(src).substr(1); > > + VERIFY( res == StringView(cstr).substr(1) ); > > + VERIFY( res.get_allocator() == defAlloc ); > > + VERIFY( src == cstr ); > > + > > + src = String(cstr, alloc); > > + String res3(std::move(src), 1); > > + VERIFY( res3 == StringView(cstr).substr(1) ); > > + VERIFY( res3.get_allocator() == defAlloc ); > > + VERIFY( src == cstr ); > > + > > + src = String(cstr, alloc); > > + String res4(std::move(src), 1, alloc); > > + VERIFY( res4 == StringView(cstr).substr(1) ); > > + VERIFY( res4.get_allocator() == alloc ); > > + VERIFY( src == cstr ); > > + > > + // substr(1, 1000) > > + src = String(cstr, alloc); > > + res = std::move(src).substr(1, 1000); > > + VERIFY( res == StringView(cstr).substr(1, 1000) ); > > + VERIFY( res.get_allocator() == defAlloc ); > > + VERIFY( src == cstr ); > > + > > + src = String(cstr, alloc); > > + String res5(std::move(src), 1, 1000); > > + VERIFY( res5 == StringView(cstr).substr(1, 1000) ); > > + VERIFY( res5.get_allocator() == defAlloc ); > > + VERIFY( src == cstr ); > > + > > + src = String(cstr, alloc); > > + String res6(std::move(src), 1, 1000, alloc); > > + VERIFY( res6 == StringView(cstr).substr(1, 1000) ); > > + VERIFY( res6.get_allocator() == alloc ); > > + VERIFY( src == cstr ); > > +} > > + > > +template<typename CharT, typename Allocator> > > +constexpr void > > +do_test_short_sel(const CharT* cstr, Allocator alloc) > > +{ > > + using StringView = std::basic_string_view<CharT>; > > + using String = std::basic_string<CharT, std::char_traits<CharT>, > Allocator>; > > + > > + const Allocator defAlloc; > > + String src, res; > > + > > + // substr(0, 2) > > + src = String(cstr, alloc); > > + res = std::move(src).substr(0, 2); > > + VERIFY( res == StringView(cstr).substr(0, 2) ); > > + VERIFY( res.get_allocator() == defAlloc ); > > + VERIFY( src == cstr ); > > + > > + src = String(cstr, alloc); > > + String res1(std::move(src), 0, 2); > > + VERIFY( res1 == StringView(cstr).substr(0, 2) ); > > + VERIFY( res1.get_allocator() == defAlloc ); > > + VERIFY( src == cstr ); > > + > > + src = String(cstr, alloc); > > + String res2(std::move(src), 0, 2, alloc); > > + VERIFY( res2 == StringView(cstr).substr(0, 2) ); > > + VERIFY( res2.get_allocator() == alloc ); > > + VERIFY( src == cstr ); > > + > > + // substr(1, 2) > > + src = String(cstr, alloc); > > + res = std::move(src).substr(1, 2); > > + VERIFY( res == StringView(cstr).substr(1, 2) ); > > + VERIFY( res.get_allocator() == defAlloc ); > > + VERIFY( src == cstr ); > > + > > + src = String(cstr, alloc); > > + String res3(std::move(src), 1, 2); > > + VERIFY( res3 == StringView(cstr).substr(1, 2) ); > > + VERIFY( res3.get_allocator() == defAlloc ); > > + VERIFY( src == cstr ); > > + > > + src = String(cstr, alloc); > > + String res4(std::move(src), 1, 2, alloc); > > + VERIFY( res4 == StringView(cstr).substr(1, 2) ); > > + VERIFY( res4.get_allocator() == alloc ); > > + VERIFY( src == cstr ); > > + > > + // libstdc++ specific > > + constexpr int max_short = (sizeof(CharT) == 1) ? 15 : 3; > > + // substr(0, max_short) > > + src = String(cstr, alloc); > > + res = std::move(src).substr(0, max_short); > > + VERIFY( res == StringView(cstr).substr(0, max_short) ); > > + VERIFY( res.get_allocator() == defAlloc ); > > + VERIFY( src == cstr ); > > + > > + src = String(cstr, alloc); > > + String res5(std::move(src), 0, max_short); > > + VERIFY( res5 == StringView(cstr).substr(0, max_short) ); > > + VERIFY( res5.get_allocator() == defAlloc ); > > + VERIFY( src == cstr ); > > + > > + src = String(cstr, alloc); > > + String res6(std::move(src), 0, max_short, alloc); > > + VERIFY( res6 == StringView(cstr).substr(0, max_short) ); > > + VERIFY( res6.get_allocator() == alloc ); > > + VERIFY( src == cstr ); > > +} > > + > > +// If the selection is long enough to do not fit into SSO, > > +// the memory is moved if allocators are compatible. > > +template<typename CharT, typename Allocator> > > +constexpr void > > +do_test_long(const CharT* cstr, Allocator alloc) > > +{ > > + using StringView = std::basic_string_view<CharT>; > > + using String = std::basic_string<CharT, std::char_traits<CharT>, > Allocator>; > > + > > + const Allocator defAlloc; > > + String src, res; > > + const CharT* data; > > + > > + auto verifyMove = [&](const String& str) > > + { > > + // Only SSO string provide rvalue overloads of > > + // substr and corresponding constructor. > > +#if _GLIBCXX_USE_CXX11_ABI || !_GLIBCXX_USE_DUAL_ABI > > + if (str.get_allocator() == alloc) { > > + VERIFY( str.data() == data ); > > + VERIFY( src.empty() ); > > + } else > > +#endif > > + VERIFY( src == cstr ); > > + }; > > + > > + // substr(0) > > + src = String(cstr, alloc); > > + data = src.data(); > > + res = std::move(src).substr(0); > > + VERIFY( res == StringView(cstr).substr(0) ); > > + VERIFY( res.get_allocator() == defAlloc ); > > + verifyMove(res); > > + > > + src = String(cstr, alloc); > > + data = src.data(); > > + String res1(std::move(src), 0); > > + VERIFY( res1 == StringView(cstr).substr(0) ); > > + VERIFY( res1.get_allocator() == defAlloc ); > > + verifyMove(res1); > > + > > + src = String(cstr, alloc); > > + data = src.data(); > > + String res2(std::move(src), 0, alloc); > > + VERIFY( res2 == StringView(cstr).substr(0) ); > > + VERIFY( res2.get_allocator() == alloc ); > > + verifyMove(res2); > > + > > + // substr(5) > > + src = String(cstr, alloc); > > + data = src.data(); > > + res = std::move(src).substr(5); > > + VERIFY( res == StringView(cstr).substr(5) ); > > + VERIFY( res.get_allocator() == defAlloc ); > > + verifyMove(res); > > + > > + src = String(cstr, alloc); > > + data = src.data(); > > + String res3(std::move(src), 5); > > + VERIFY( res3 == StringView(cstr).substr(5) ); > > + VERIFY( res3.get_allocator() == defAlloc ); > > + verifyMove(res3); > > + > > + src = String(cstr, alloc); > > + data = src.data(); > > + String res4(std::move(src), 5, alloc); > > + VERIFY( res4 == StringView(cstr).substr(5) ); > > + verifyMove(res4); > > + > > + // substr(0, 50) > > + src = String(cstr, alloc); > > + data = src.data(); > > + res = std::move(src).substr(0, 50); > > + VERIFY( res == StringView(cstr).substr(0, 50) ); > > + VERIFY( res.get_allocator() == defAlloc ); > > + verifyMove(res); > > + > > + src = String(cstr, alloc); > > + data = src.data(); > > + String res5(std::move(src), 0, 50); > > + VERIFY( res5 == StringView(cstr).substr(0, 50) ); > > + VERIFY( res5.get_allocator() == defAlloc ); > > + verifyMove(res5); > > + > > + src = String(cstr, alloc); > > + data = src.data(); > > + String res6(std::move(src), 0, 50, alloc); > > + VERIFY( res6 == StringView(cstr).substr(0, 50) ); > > + VERIFY( res6.get_allocator() == alloc ); > > + verifyMove(res6); > > + > > + // substr(5, 50) > > + src = String(cstr, alloc); > > + data = src.data(); > > + res = std::move(src).substr(5, 50); > > + VERIFY( res == StringView(cstr).substr(5, 50) ); > > + VERIFY( res.get_allocator() == defAlloc ); > > + verifyMove(res); > > + > > + src = String(cstr, alloc); > > + data = src.data(); > > + String res7(std::move(src), 5, 50); > > + VERIFY( res7 == StringView(cstr).substr(5, 50) ); > > + VERIFY( res7.get_allocator() == defAlloc ); > > + verifyMove(res7); > > + > > + src = String(cstr, alloc); > > + data = src.data(); > > + String res8(std::move(src), 5, 50, alloc); > > + VERIFY( res8 == StringView(cstr).substr(5, 50) ); > > + VERIFY( res8.get_allocator() == alloc ); > > + verifyMove(res8); > > + > > + // substr(5, 100) > > + src = String(cstr, alloc); > > + data = src.data(); > > + res = std::move(src).substr(5, 1000); > > + VERIFY( res == StringView(cstr).substr(5, 1000) ); > > + VERIFY( res.get_allocator() == defAlloc ); > > + verifyMove(res); > > + > > + src = String(cstr, alloc); > > + data = src.data(); > > + String res9(std::move(src), 5, 1000); > > + VERIFY( res9 == StringView(cstr).substr(5, 1000) ); > > + VERIFY( res9.get_allocator() == defAlloc ); > > + verifyMove(res9); > > + > > + src = String(cstr, alloc); > > + data = src.data(); > > + String res10(std::move(src), 5, 1000, alloc); > > + VERIFY( res10 == StringView(cstr).substr(5, 1000) ); > > + VERIFY( res10.get_allocator() == alloc ); > > + verifyMove(res10); > > +} > > + > > +template<typename CharT, typename Allocator> > > +constexpr void > > +do_test_alloc(const CharT* sht, const CharT* lng, Allocator alloc) > > +{ > > + do_test_short_in(sht, alloc); > > + do_test_short_sel(sht, alloc); > > + do_test_short_sel(lng, alloc); > > + do_test_long(lng, alloc); > > +} > > + > > +template<typename CharT> > > +constexpr void > > +do_test_bounds(const CharT* cstr) > > +{ > > + using String = std::basic_string<CharT>; > > + try > > + { > > + auto res = String(cstr).substr(30); > > + VERIFY(false); > > + } catch (const std::out_of_range&) { > > + VERIFY(true); > > + } > > + > > + try > > + { > > + auto res = String(cstr).substr(30, 20); > > + VERIFY(false); > > + } catch (const std::out_of_range&) { > > + VERIFY(true); > > + } > > + > > + try > > + { > > + String res(String(cstr), 30); > > + VERIFY(false); > > + } catch (const std::out_of_range&) { > > + VERIFY(true); > > + } > > + > > + try > > + { > > + String res(String(cstr), 30, 20); > > + VERIFY(false); > > + } catch (const std::out_of_range&) { > > + VERIFY(true); > > + } > > +} > > + > > +template<typename CharT> > > +constexpr void > > +do_test(const CharT* sht, const CharT* lng) > > +{ > > + do_test_alloc(sht, lng, std::allocator<CharT>()); > > + if ! consteval { > > + do_test_alloc(sht, lng, __gnu_test::uneq_allocator<CharT>()); > > + do_test_alloc(sht, lng, __gnu_test::uneq_allocator<CharT>(42)); > > + do_test_bounds(sht); > > + } > > +} > > + > > +constexpr bool > > +test_all() > > +{ > > + do_test( > > + "abcdefghijklmop", > > + "some very very long string, that will not use SSO, and have at > least fifty characters"); > > + do_test( > > + L"abc", > > + L"some very very long string, that will not use SSO, and have at > least fifty characters"); > > + > > + return true; > > +} > > + > > +int main() > > +{ > > + test_all(); > > +} > > -- > > 2.53.0 > > > >
