On Mon, Feb 16, 2026 at 4:57 PM Tomasz Kamiński <[email protected]> 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 > Also removed first and on this line. > 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 > + _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 > >
