On Fri, Apr 11, 2025 at 1:32 AM Jonathan Wakely <jwak...@redhat.com> wrote:
> This is the last piece of P1206R7, adding new members to > std::basic_string. > > libstdc++-v3/ChangeLog: > > PR libstdc++/111055 > * include/bits/basic_string.h (_S_copy_range): New function. > (basic_string(from_range_t, R%%, const Alloc&)): New > constructor. > (append_range, assign_range, insert_range, replace_with_range): > New functions. > * include/bits/cow_string.h: Likewise. > * testsuite/21_strings/basic_string/cons/from_range.cc: New > test. > * > testsuite/21_strings/basic_string/modifiers/append/append_range.cc: > New test. > * > testsuite/21_strings/basic_string/modifiers/assign/assign_range.cc: > New test. > * > testsuite/21_strings/basic_string/modifiers/insert/insert_range.cc: > New test. > * > testsuite/21_strings/basic_string/modifiers/replace/replace_with_range.cc: > New test. > > Co-authored-by: Tomasz Kamiński <tkami...@redhat.com> > --- > > Thanks to Tomasz for the tests and fixes to this. > LGTM for reasons that may be obvious. > > Tested x86_64-linux. > > libstdc++-v3/include/bits/basic_string.h | 182 ++++++++++++++++++ > libstdc++-v3/include/bits/cow_string.h | 115 +++++++++++ > .../basic_string/cons/from_range.cc | 124 ++++++++++++ > .../modifiers/append/append_range.cc | 125 ++++++++++++ > .../modifiers/assign/assign_range.cc | 116 +++++++++++ > .../modifiers/insert/insert_range.cc | 130 +++++++++++++ > .../modifiers/replace/replace_with_range.cc | 133 +++++++++++++ > 7 files changed, 925 insertions(+) > create mode 100644 > libstdc++-v3/testsuite/21_strings/basic_string/cons/from_range.cc > create mode 100644 > libstdc++-v3/testsuite/21_strings/basic_string/modifiers/append/append_range.cc > create mode 100644 > libstdc++-v3/testsuite/21_strings/basic_string/modifiers/assign/assign_range.cc > create mode 100644 > libstdc++-v3/testsuite/21_strings/basic_string/modifiers/insert/insert_range.cc > create mode 100644 > libstdc++-v3/testsuite/21_strings/basic_string/modifiers/replace/replace_with_range.cc > > diff --git a/libstdc++-v3/include/bits/basic_string.h > b/libstdc++-v3/include/bits/basic_string.h > index 067c7915c76..1ca8f5fb749 100644 > --- a/libstdc++-v3/include/bits/basic_string.h > +++ b/libstdc++-v3/include/bits/basic_string.h > @@ -51,6 +51,11 @@ > # include <string_view> > #endif > > +#if __glibcxx_ranges_to_container // C++ >= 23 > +# include <bits/ranges_algobase.h> // ranges::copy > +# include <bits/ranges_util.h> // ranges::subrange > +#endif > + > #if __cplusplus > 202302L > # include <charconv> > #endif > @@ -516,6 +521,21 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > { _S_copy(__p, __k1, __k2 - __k1); } > #endif > > +#if __glibcxx_ranges_to_container // C++ >= 23 > + // pre: __n == ranges::distance(__rg). __p+[0,__n) is a valid range. > + template<typename _Rg> > + static constexpr void > + _S_copy_range(pointer __p, _Rg&& __rg, size_type __n) > + { > + if constexpr (ranges::contiguous_range<_Rg> > + && is_same_v<ranges::range_value_t<_Rg>, _CharT>) > + _S_copy(__p, ranges::data(std::forward<_Rg>(__rg)), __n); > + else > + for (auto&& __e : __rg) > + traits_type::assign(*__p++, > std::forward<decltype(__e)>(__e)); > + } > +#endif > + > _GLIBCXX20_CONSTEXPR > static int > _S_compare(size_type __n1, size_type __n2) _GLIBCXX_NOEXCEPT > @@ -732,6 +752,33 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > __str._M_set_length(0); > } > > +#if __glibcxx_ranges_to_container // C++ >= 23 > + /** > + * @brief Construct a string from a range. > + * @since C++23 > + */ > + template<__detail::__container_compatible_range<_CharT> _Rg> > + constexpr > + basic_string(from_range_t, _Rg&& __rg, const _Alloc& __a = > _Alloc()) > + : basic_string(__a) > + { > + if constexpr (ranges::forward_range<_Rg> || > ranges::sized_range<_Rg>) > + { > + const auto __n = > static_cast<size_type>(ranges::distance(__rg)); > + reserve(__n); > + _S_copy_range(_M_data(), std::forward<_Rg>(__rg), __n); > + _M_set_length(__n); > + } > + else > + { > + auto __first = ranges::begin(__rg); > + const auto __last = ranges::end(__rg); > + for (; __first != __last; ++__first) > + push_back(*__first); > + } > + } > +#endif > + > /** > * @brief Construct string from an initializer %list. > * @param __l std::initializer_list of characters. > @@ -1541,6 +1588,58 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > append(size_type __n, _CharT __c) > { return _M_replace_aux(this->size(), size_type(0), __n, __c); } > > +#if __glibcxx_ranges_to_container // C++ >= 23 > + /** > + * @brief Append a range to the string. > + * @param __rg A range of values that are convertible to > `value_type`. > + * @since C++23 > + * > + * The range `__rg` is allowed to overlap with `*this`. > + */ > + template<__detail::__container_compatible_range<_CharT> _Rg> > + constexpr basic_string& > + append_range(_Rg&& __rg) > + { > + // N.B. __rg may overlap with *this, so we must copy from __rg > before > + // existing elements or iterators referring to *this are > invalidated. > + // e.g. in s.append_range(views::concat(s, str)), rg overlaps s. > + if constexpr (ranges::forward_range<_Rg> || > ranges::sized_range<_Rg>) > + { > + const auto __len = size_type(ranges::distance(__rg)); > + > + // Don't care if this addition wraps around, we check it > below: > + const size_type __newlen = size() + __len; > + > + if ((capacity() - size()) >= __len) > + _S_copy_range(_M_data() + size(), std::forward<_Rg>(__rg), > + __len); > + else > + { > + _M_check_length(0, __len, "basic_string::append_range"); > + basic_string __s(_M_get_allocator()); > + __s.reserve(__newlen); > + _S_copy_range(__s._M_data() + size(), > std::forward<_Rg>(__rg), > + __len); > + _S_copy(__s._M_data(), _M_data(), size()); > + if (!_M_is_local()) > + _M_destroy(_M_allocated_capacity); > + _M_data(__s._M_data()); > + _M_capacity(__s._M_allocated_capacity); > + __s._M_data(__s._M_local_data()); > + __s._M_length(0); > + } > + _M_set_length(__newlen); // adds null-terminator > + } > + else > + { > + basic_string __s(from_range, std::forward<_Rg>(__rg), > + _M_get_allocator()); > + append(__s); > + } > + return *this; > + } > +#endif > + > #if __cplusplus >= 201103L > /** > * @brief Append an initializer_list of characters. > @@ -1800,6 +1899,25 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > { return this->replace(begin(), end(), __first, __last); } > #endif > > +#if __glibcxx_ranges_to_container // C++ >= 23 > + /** > + * @brief Assign a range to the string. > + * @param __rg A range of values that are convertible to > `value_type`. > + * @since C++23 > + * > + * The range `__rg` is allowed to overlap with `*this`. > + */ > + template<__detail::__container_compatible_range<_CharT> _Rg> > + constexpr basic_string& > + assign_range(_Rg&& __rg) > + { > + basic_string __s(from_range, std::forward<_Rg>(__rg), > + _M_get_allocator()); > + assign(std::move(__s)); > + return *this; > + } > +#endif > + > #if __cplusplus >= 201103L > /** > * @brief Set value to an initializer_list of characters. > @@ -1949,6 +2067,37 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > { this->replace(__p, __p, __beg, __end); } > #endif > > +#if __glibcxx_ranges_to_container // C++ >= 23 > + /** > + * @brief Insert a range into the string. > + * @param __rg A range of values that are convertible to > `value_type`. > + * @since C++23 > + * > + * The range `__rg` is allowed to overlap with `*this`. > + */ > + template<__detail::__container_compatible_range<_CharT> _Rg> > + constexpr iterator > + insert_range(const_iterator __p, _Rg&& __rg) > + { > + auto __pos = __p - cbegin(); > + > + if constexpr (ranges::forward_range<_Rg>) > + if (ranges::empty(__rg)) > + return begin() + __pos; > + > + > + if (__p == cend()) > + append_range(std::forward<_Rg>(__rg)); > + else > + { > + basic_string __s(from_range, std::forward<_Rg>(__rg), > + _M_get_allocator()); > + insert(__pos, __s); > + } > + return begin() + __pos; > + } > +#endif > + > #if __cplusplus >= 201103L > /** > * @brief Insert an initializer_list of characters. > @@ -2537,6 +2686,30 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > __k1.base(), __k2 - __k1); > } > > +#if __glibcxx_ranges_to_container // C++ >= 23 > + /** > + * @brief Replace part of the string with a range. > + * @param __rg A range of values that are convertible to > `value_type`. > + * @since C++23 > + * > + * The range `__rg` is allowed to overlap with `*this`. > + */ > + template<__detail::__container_compatible_range<_CharT> _Rg> > + constexpr basic_string& > + replace_with_range(const_iterator __i1, const_iterator __i2, _Rg&& > __rg) > + { > + if (__i1 == cend()) > + append_range(std::forward<_Rg>(__rg)); > + else > + { > + basic_string __s(from_range, std::forward<_Rg>(__rg), > + _M_get_allocator()); > + replace(__i1, __i2, __s); > + } > + return *this; > + } > +#endif > + > #if __cplusplus >= 201103L > /** > * @brief Replace range of characters with initializer_list. > @@ -3614,6 +3787,15 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > typename basic_string<_CharT, _Traits, > _Allocator>::size_type, > const _Allocator& = _Allocator()) > -> basic_string<_CharT, _Traits, _Allocator>; > + > +#if __glibcxx_ranges_to_container // C++ >= 23 > + template<ranges::input_range _Rg, > + typename _Allocator = allocator<ranges::range_value_t<_Rg>>> > + basic_string(from_range_t, _Rg&&, _Allocator = _Allocator()) > + -> basic_string<ranges::range_value_t<_Rg>, > + char_traits<ranges::range_value_t<_Rg>>, > + _Allocator>; > +#endif > _GLIBCXX_END_NAMESPACE_CXX11 > #endif > > diff --git a/libstdc++-v3/include/bits/cow_string.h > b/libstdc++-v3/include/bits/cow_string.h > index d5b39799254..22a9814d2a8 100644 > --- a/libstdc++-v3/include/bits/cow_string.h > +++ b/libstdc++-v3/include/bits/cow_string.h > @@ -639,6 +639,41 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > #endif > } > > +#if __glibcxx_ranges_to_container // C++ >= 23 > + /** > + * @brief Construct a string from a range. > + * @since C++23 > + */ > + template<__detail::__container_compatible_range<_CharT> _Rg> > + basic_string(from_range_t, _Rg&& __rg, const _Alloc& __a = > _Alloc()) > + : basic_string(__a) > + { > + if constexpr (ranges::forward_range<_Rg> || > ranges::sized_range<_Rg>) > + { > + const auto __n = > static_cast<size_type>(ranges::distance(__rg)); > + if (__n == 0) > + return; > + > + reserve(__n); > + pointer __p = _M_data(); > + if constexpr (ranges::contiguous_range<_Rg> > + && is_same_v<ranges::range_value_t<_Rg>, > _CharT>) > + _M_copy(__p, ranges::data(std::forward<_Rg>(__rg)), __n); > + else > + for (auto&& __e : __rg) > + traits_type::assign(*__p++, > std::forward<decltype(__e)>(__e)); > + _M_rep()->_M_set_length_and_sharable(__n); > + } > + else > + { > + auto __first = ranges::begin(__rg); > + const auto __last = ranges::end(__rg); > + for (; __first != __last; ++__first) > + push_back(*__first); > + } > + } > +#endif > + > /** > * @brief Construct string from an initializer %list. > * @param __l std::initializer_list of characters. > @@ -1314,6 +1349,22 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > basic_string& > append(size_type __n, _CharT __c); > > +#if __glibcxx_ranges_to_container // C++ >= 23 > + /** > + * @brief Append a range to the string. > + * @since C++23 > + */ > + template<__detail::__container_compatible_range<_CharT> _Rg> > + basic_string& > + append_range(_Rg&& __rg) > + { > + basic_string __s(from_range, std::forward<_Rg>(__rg), > + get_allocator()); > + append(__s); > + return *this; > + } > +#endif > + > #if __cplusplus >= 201103L > /** > * @brief Append an initializer_list of characters. > @@ -1485,6 +1536,22 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > assign(_InputIterator __first, _InputIterator __last) > { return this->replace(_M_ibegin(), _M_iend(), __first, __last); } > > +#if __glibcxx_ranges_to_container // C++ >= 23 > + /** > + * @brief Set value to a range of characters. > + * @since C++23 > + */ > + template<__detail::__container_compatible_range<_CharT> _Rg> > + basic_string& > + assign_range(_Rg&& __rg) > + { > + basic_string __s(from_range, std::forward<_Rg>(__rg), > + get_allocator()); > + assign(std::move(__s)); > + return *this; > + } > +#endif > + > #if __cplusplus >= 201103L > /** > * @brief Set value to an initializer_list of characters. > @@ -1562,6 +1629,33 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > insert(iterator __p, _InputIterator __beg, _InputIterator __end) > { this->replace(__p, __p, __beg, __end); } > > +#if __glibcxx_ranges_to_container // C++ >= 23 > + /** > + * @brief Insert a range into the string. > + * @since C++23 > + */ > + template<__detail::__container_compatible_range<_CharT> _Rg> > + iterator > + insert_range(const_iterator __p, _Rg&& __rg) > + { > + auto __pos = __p - cbegin(); > + > + if constexpr (ranges::forward_range<_Rg>) > + if (ranges::empty(__rg)) > + return begin() + __pos; > + > + if (__p == cend()) > + append_range(std::forward<_Rg>(__rg)); > + else > + { > + basic_string __s(from_range, std::forward<_Rg>(__rg), > + get_allocator()); > + insert(__pos, __s); > + } > + return begin() + __pos; > + } > +#endif > + > #if __cplusplus >= 201103L > /** > * @brief Insert an initializer_list of characters. > @@ -2072,6 +2166,27 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > __k1.base(), __k2 - __k1); > } > > +#if __glibcxx_ranges_to_container // C++ >= 23 > + /** > + * @brief Replace part of the string with a range. > + * @since C++23 > + */ > + template<__detail::__container_compatible_range<_CharT> _Rg> > + basic_string& > + replace_with_range(const_iterator __i1, const_iterator __i2, _Rg&& > __rg) > + { > + if (__i1 == cend()) > + append_range(std::forward<_Rg>(__rg)); > + else > + { > + basic_string __s(from_range, std::forward<_Rg>(__rg), > + get_allocator()); > + replace(__i1 - cbegin(), __i2 - __i1, __s); > + } > + return *this; > + } > +#endif > + > #if __cplusplus >= 201103L > /** > * @brief Replace range of characters with initializer_list. > diff --git > a/libstdc++-v3/testsuite/21_strings/basic_string/cons/from_range.cc > b/libstdc++-v3/testsuite/21_strings/basic_string/cons/from_range.cc > new file mode 100644 > index 00000000000..0795cb41ee9 > --- /dev/null > +++ b/libstdc++-v3/testsuite/21_strings/basic_string/cons/from_range.cc > @@ -0,0 +1,124 @@ > +// { dg-do run { target c++23 } } > + > +#include <string> > +#include <span> > +#include <testsuite_hooks.h> > +#include <testsuite_iterators.h> > +#include <testsuite_allocator.h> > + > +void > +test_deduction_guide(char* p) > +{ > + __gnu_test::test_input_range<char> r(nullptr, nullptr); > + std::basic_string v(std::from_range, r); > + static_assert(std::is_same_v<decltype(v), std::string>); > + > + using Alloc = __gnu_test::SimpleAllocator<char>; > + Alloc alloc; > + std::basic_string v2(std::from_range, r, alloc); > + static_assert(std::is_same_v<decltype(v2), std::basic_string<char, > std::char_traits<char>, Alloc>>); > + > + __gnu_test::test_input_range<wchar_t> wr(nullptr, nullptr); > + std::basic_string w(std::from_range, wr); > + static_assert(std::is_same_v<decltype(w), std::wstring>); > + > + using WAlloc = __gnu_test::SimpleAllocator<wchar_t>; > + WAlloc walloc; > + std::basic_string w2(std::from_range, wr, walloc); > + static_assert(std::is_same_v<decltype(w2), std::basic_string<wchar_t, > std::char_traits<wchar_t>, WAlloc>>); > +} > + > +template<typename Range, typename Alloc> > +constexpr void > +do_test(Alloc alloc) > +{ > + // The basic_string's value_type. > + using V = typename std::allocator_traits<Alloc>::value_type; > + using CT = std::char_traits<V>; > + > + // The range's value_type. > + using T = std::ranges::range_value_t<Range>; > + T a[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', > + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}; > + > + auto eq = [](const std::basic_string<V, CT, Alloc>& l, std::span<T> r) { > + if (l.size() != r.size()) > + return false; > + for (auto i = 0u; i < l.size(); ++i) > + if (l[i] != r[i]) > + return false; > + return true; > + }; > + > + std::basic_string<V, CT, Alloc> v0(std::from_range, Range(a, a+0)); > + VERIFY( v0.empty() ); > + VERIFY( v0.get_allocator() == Alloc() ); > + > + std::basic_string<V, CT, Alloc> v4(std::from_range, Range(a, a+4)); > + VERIFY( eq(v4, {a, 4}) ); > + VERIFY( v4.get_allocator() == Alloc() ); > + > + std::basic_string<V, CT, Alloc> v9(std::from_range, Range(a, a+9), > alloc); > + VERIFY( eq(v9, {a, 9}) ); > + VERIFY( v9.get_allocator() == alloc ); > + > + std::basic_string<V, CT, Alloc> v20(std::from_range, Range(a, a+20), > alloc); > + VERIFY( eq(v20, {a, 20}) ); > + VERIFY( v20.get_allocator() == alloc ); > +} > + > +template<typename Range> > +void > +do_test_a() > +{ > + do_test<Range>(std::allocator<char>()); > + do_test<Range>(__gnu_test::uneq_allocator<char>(42)); > + do_test<Range>(std::allocator<wchar_t>()); > + do_test<Range>(__gnu_test::uneq_allocator<wchar_t>(42)); > +} > + > +bool > +test_ranges() > +{ > + using namespace __gnu_test; > + > + do_test_a<test_forward_range<char>>(); > + do_test_a<test_forward_sized_range<char>>(); > + do_test_a<test_sized_range_sized_sent<char, > forward_iterator_wrapper>>(); > + > + do_test_a<test_input_range<char>>(); > + do_test_a<test_input_sized_range<char>>(); > + do_test_a<test_sized_range_sized_sent<char, input_iterator_wrapper>>(); > + > + do_test_a<test_range<char, input_iterator_wrapper_nocopy>>(); > + do_test_a<test_sized_range<char, input_iterator_wrapper_nocopy>>(); > + do_test_a<test_sized_range_sized_sent<char, > input_iterator_wrapper_nocopy>>(); > + > + // Not lvalue-convertible to char > + struct C { > + C(char v) : val(v) { } > + operator char() && { return val; } > + bool operator==(char b) const { return b == val; } > + char val; > + }; > + using rvalue_input_range = test_range<C, input_iterator_wrapper_rval>; > + do_test<rvalue_input_range>(std::allocator<char>()); > + > + return true; > +} > + > +constexpr bool > +test_constexpr() > +{ > +#if _GLIBCXX_USE_CXX11_ABI > + // XXX: this doesn't test the non-forward_range code paths are > constexpr. > + do_test<std::string_view>(std::allocator<char>()); > +#endif // _GLIBCXX_USE_CXX11_ABI > + return true; > +} > + > +int main() > +{ > + test_ranges(); > + static_assert( test_constexpr() ); > +} > diff --git > a/libstdc++-v3/testsuite/21_strings/basic_string/modifiers/append/append_range.cc > b/libstdc++-v3/testsuite/21_strings/basic_string/modifiers/append/append_range.cc > new file mode 100644 > index 00000000000..bf32639a23a > --- /dev/null > +++ > b/libstdc++-v3/testsuite/21_strings/basic_string/modifiers/append/append_range.cc > @@ -0,0 +1,125 @@ > +// { dg-do run { target c++23 } } > + > +#include <span> > +#include <string> > +#include <testsuite_allocator.h> > +#include <testsuite_hooks.h> > +#include <testsuite_iterators.h> > + > +template<typename Range, typename Alloc> > +constexpr void > +do_test() > +{ > + // The vector's value_type. > + using V = typename std::allocator_traits<Alloc>::value_type; > + using CT = std::char_traits<V>; > + > + // The range's value_type. > + using T = std::ranges::range_value_t<Range>; > + T a[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', > + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}; > + > + auto eq = [](const std::basic_string<V, CT, Alloc>& l, std::span<T> r) { > + if (l.size() != r.size()) > + return false; > + for (auto i = 0u; i < l.size(); ++i) > + if (l[i] != r[i]) > + return false; > + return true; > + }; > + > + Range r4(a, a+4); > + Range r5(a+4, a+9); > + Range r11(a+9, a+20); > + > + std::basic_string<V, CT, Alloc> v; > + v.append_range(r4); > + VERIFY( eq(v, {a, 4}) ); > + v.append_range(r5); > + VERIFY( eq(v, {a, 9}) ); > + > + std::basic_string<V, CT, Alloc> const s = v; > + v.append_range(r11); > + VERIFY( eq(v, a) ); > + v.append_range(Range(a, a)); > + VERIFY( eq(v, a) ); > + v.clear(); > + v.append_range(Range(a, a)); > + VERIFY( v.empty() ); > +} > + > +template<typename Range> > +void > +do_test_a() > +{ > + do_test<Range, std::allocator<char>>(); > + do_test<Range, __gnu_test::SimpleAllocator<char>>(); > + do_test<Range, std::allocator<wchar_t>>(); > + do_test<Range, __gnu_test::SimpleAllocator<wchar_t>>(); > +} > + > +bool > +test_ranges() > +{ > + using namespace __gnu_test; > + > + do_test_a<test_forward_range<char>>(); > + do_test_a<test_forward_sized_range<char>>(); > + do_test_a<test_sized_range_sized_sent<char, > forward_iterator_wrapper>>(); > + > + do_test_a<test_input_range<char>>(); > + do_test_a<test_input_sized_range<char>>(); > + do_test_a<test_sized_range_sized_sent<char, input_iterator_wrapper>>(); > + > + do_test_a<test_range<char, input_iterator_wrapper_nocopy>>(); > + do_test_a<test_sized_range<char, input_iterator_wrapper_nocopy>>(); > + do_test_a<test_sized_range_sized_sent<char, > input_iterator_wrapper_nocopy>>(); > + > + // Not lvalue-convertible to char > + struct C { > + C(char v) : val(v) { } > + operator char() && { return val; } > + bool operator==(char b) const { return b == val; } > + char val; > + }; > + using rvalue_input_range = test_range<C, input_iterator_wrapper_rval>; > + do_test<rvalue_input_range, std::allocator<char>>(); > + > + return true; > +} > + > +void > +test_overlapping() > +{ > + std::string const s = "1234abcd"; > + > + std::string c = s; > + c.append_range(std::string_view(c)); > + VERIFY( c == "1234abcd1234abcd" ); > + > + c = s; > + c.append_range(std::string_view(c).substr(4, 4)); > + VERIFY( c == "1234abcdabcd" ); > + > + c = s; > + c.reserve(12); > + c.append_range(std::string_view(c).substr(0, 4)); > + VERIFY( c == "1234abcd1234" ); > +} > + > +constexpr bool > +test_constexpr() > +{ > +#if _GLIBCXX_USE_CXX11_ABI > + // XXX: this doesn't test the non-forward_range code paths are > constexpr. > + do_test<std::string_view, std::allocator<char>>(); > +#endif // _GLIBCXX_USE_CXX11_ABI > + return true; > +} > + > +int main() > +{ > + test_ranges(); > + test_overlapping(); > + static_assert( test_constexpr() ); > +} > diff --git > a/libstdc++-v3/testsuite/21_strings/basic_string/modifiers/assign/assign_range.cc > b/libstdc++-v3/testsuite/21_strings/basic_string/modifiers/assign/assign_range.cc > new file mode 100644 > index 00000000000..a10ccabded9 > --- /dev/null > +++ > b/libstdc++-v3/testsuite/21_strings/basic_string/modifiers/assign/assign_range.cc > @@ -0,0 +1,116 @@ > +// { dg-do run { target c++23 } } > + > +#include <vector> > +#include <span> > +#include <testsuite_hooks.h> > +#include <testsuite_iterators.h> > +#include <testsuite_allocator.h> > + > +template<typename Range, typename Alloc> > +constexpr void > +do_test() > +{ > + // The vector's value_type. > + using V = typename std::allocator_traits<Alloc>::value_type; > + using CT = std::char_traits<V>; > + > + // The range's value_type. > + using T = std::ranges::range_value_t<Range>; > + T a[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', > + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}; > + > + auto eq = [](const std::basic_string<V, CT, Alloc>& l, std::span<T> r) { > + if (l.size() != r.size()) > + return false; > + for (auto i = 0u; i < l.size(); ++i) > + if (l[i] != r[i]) > + return false; > + return true; > + }; > + > + std::basic_string<V, CT, Alloc> v; > + v.assign_range(Range(a, a)); > + VERIFY( v.empty() ); > + v.assign_range(Range(a, a+4)); > + VERIFY( eq(v, {a, 4}) ); > + v.assign_range(Range(a, a+9)); > + VERIFY( eq(v, {a, 9}) ); > + std::basic_string<V, CT, Alloc> const s = v; > + v.assign_range(Range(a, a+20)); > + VERIFY( eq(v, {a, 20}) ); > +} > + > +template<typename Range> > +void > +do_test_a() > +{ > + do_test<Range, std::allocator<char>>(); > + do_test<Range, __gnu_test::SimpleAllocator<char>>(); > + do_test<Range, std::allocator<wchar_t>>(); > + do_test<Range, __gnu_test::SimpleAllocator<wchar_t>>(); > +} > + > +bool > +test_ranges() > +{ > + using namespace __gnu_test; > + > + do_test_a<test_forward_range<char>>(); > + do_test_a<test_forward_sized_range<char>>(); > + do_test_a<test_sized_range_sized_sent<char, > forward_iterator_wrapper>>(); > + > + do_test_a<test_input_range<char>>(); > + do_test_a<test_input_sized_range<char>>(); > + do_test_a<test_sized_range_sized_sent<char, input_iterator_wrapper>>(); > + > + do_test_a<test_range<char, input_iterator_wrapper_nocopy>>(); > + do_test_a<test_sized_range<char, input_iterator_wrapper_nocopy>>(); > + do_test_a<test_sized_range_sized_sent<char, > input_iterator_wrapper_nocopy>>(); > + > + // Not lvalue-convertible to char > + struct C { > + C(char v) : val(v) { } > + operator char() && { return val; } > + bool operator==(char b) const { return b == val; } > + char val; > + }; > + using rvalue_input_range = test_range<C, input_iterator_wrapper_rval>; > + do_test<rvalue_input_range, std::allocator<char>>(); > + > + return true; > +} > + > +void > +test_overlapping() > +{ > + std::string const s = "1234abcd"; > + > + std::string c = s; > + c.assign_range(std::string_view(c)); > + VERIFY( c == "1234abcd" ); > + > + c = s; > + c.assign_range(std::string_view(c).substr(4, 4)); > + VERIFY( c == "abcd" ); > + > + c = s; > + c.assign_range(std::string_view(c).substr(0, 4)); > + VERIFY( c == "1234" ); > +} > + > +constexpr bool > +test_constexpr() > +{ > +#if _GLIBCXX_USE_CXX11_ABI > + // XXX: this doesn't test the non-forward_range code paths are > constexpr. > + do_test<std::string_view, std::allocator<char>>(); > +#endif // _GLIBCXX_USE_CXX11_ABI > + return true; > +} > + > +int main() > +{ > + test_ranges(); > + test_overlapping(); > + static_assert( test_constexpr() ); > +} > diff --git > a/libstdc++-v3/testsuite/21_strings/basic_string/modifiers/insert/insert_range.cc > b/libstdc++-v3/testsuite/21_strings/basic_string/modifiers/insert/insert_range.cc > new file mode 100644 > index 00000000000..4fead3245d1 > --- /dev/null > +++ > b/libstdc++-v3/testsuite/21_strings/basic_string/modifiers/insert/insert_range.cc > @@ -0,0 +1,130 @@ > +// { dg-do run { target c++23 } } > + > +#include <span> > +#include <string> > +#include <testsuite_allocator.h> > +#include <testsuite_hooks.h> > +#include <testsuite_iterators.h> > + > +template<typename Range, typename Alloc> > +constexpr void > +do_test() > +{ > + // The vector's value_type. > + using V = typename std::allocator_traits<Alloc>::value_type; > + using CT = std::char_traits<V>; > + > + // The range's value_type. > + using T = std::ranges::range_value_t<Range>; > + T a[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', > + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}; > + > + auto eq = [](const std::basic_string<V, CT, Alloc>& l, std::span<T> r) { > + if (l.size() != r.size()) > + return false; > + for (auto i = 0u; i < l.size(); ++i) > + if (l[i] != r[i]) > + return false; > + return true; > + }; > + > + std::basic_string<V, CT, Alloc> v; > + auto it = v.insert_range(v.end(), Range(a, a)); > + VERIFY( v.empty() ); > + VERIFY( it == v.begin() ); > + it = v.insert_range(v.end(), Range(a, a+4)); > + VERIFY( eq(v, {a, 4}) ); > + VERIFY( it == v.begin() ); > + it = v.insert_range(v.end(), Range(a+4, a+9)); > + VERIFY( eq(v, {a, 9}) ); > + VERIFY( it == v.begin()+4 ); > + > + std::basic_string<V, CT, Alloc> s = v; > + it = v.insert_range(v.end(), Range(a+9, a+20)); > + VERIFY( eq(v, {a, 20}) ); > + VERIFY( it == v.begin()+9 ); > + > + v = std::basic_string<V, CT, Alloc>(); > + it = v.insert_range(v.begin(), Range(a, a+5)); > + VERIFY( it == v.begin() ); > + s = v; > + it = v.insert_range(v.begin() + 5, Range(a+5, a+20)); > + VERIFY( eq(v, {a, 20}) ); > + VERIFY( it == v.begin()+5 ); > +} > + > +template<typename Range> > +void > +do_test_a() > +{ > + do_test<Range, std::allocator<char>>(); > + do_test<Range, __gnu_test::SimpleAllocator<char>>(); > + do_test<Range, std::allocator<wchar_t>>(); > + do_test<Range, __gnu_test::SimpleAllocator<wchar_t>>(); > +} > + > +bool > +test_ranges() > +{ > + using namespace __gnu_test; > + > + do_test_a<test_forward_range<char>>(); > + do_test_a<test_forward_sized_range<char>>(); > + do_test_a<test_sized_range_sized_sent<char, > forward_iterator_wrapper>>(); > + > + do_test_a<test_input_range<char>>(); > + do_test_a<test_input_sized_range<char>>(); > + do_test_a<test_sized_range_sized_sent<char, input_iterator_wrapper>>(); > + > + do_test_a<test_range<char, input_iterator_wrapper_nocopy>>(); > + do_test_a<test_sized_range<char, input_iterator_wrapper_nocopy>>(); > + do_test_a<test_sized_range_sized_sent<char, > input_iterator_wrapper_nocopy>>(); > + > + // Not lvalue-convertible to char > + struct C { > + C(char v) : val(v) { } > + operator char() && { return val; } > + bool operator==(char b) const { return b == val; } > + char val; > + }; > + using rvalue_input_range = test_range<C, input_iterator_wrapper_rval>; > + do_test<rvalue_input_range, std::allocator<char>>(); > + > + return true; > +} > + > +void > +test_overlapping() > +{ > + std::string const s = "1234abcd"; > + > + std::string c = s; > + c.insert_range(c.end(), std::string_view(c)); > + VERIFY( c == "1234abcd1234abcd" ); > + > + c = s; > + c.insert_range(c.begin()+4, std::string_view(c).substr(4, 4)); > + VERIFY( c == "1234abcdabcd" ); > + > + c = s; > + c.reserve(12); > + c.insert_range(c.begin()+2, std::string_view(c).substr(0, 4)); > + VERIFY( c == "12123434abcd" ); > +} > + > +constexpr bool > +test_constexpr() > +{ > +#if _GLIBCXX_USE_CXX11_ABI > + // XXX: this doesn't test the non-forward_range code paths are > constexpr. > + do_test<std::string_view, std::allocator<char>>(); > +#endif // _GLIBCXX_USE_CXX11_ABI > + return true; > +} > + > +int main() > +{ > + test_ranges(); > + test_overlapping(); > + static_assert( test_constexpr() ); > +} > diff --git > a/libstdc++-v3/testsuite/21_strings/basic_string/modifiers/replace/replace_with_range.cc > b/libstdc++-v3/testsuite/21_strings/basic_string/modifiers/replace/replace_with_range.cc > new file mode 100644 > index 00000000000..9be3bb2d27a > --- /dev/null > +++ > b/libstdc++-v3/testsuite/21_strings/basic_string/modifiers/replace/replace_with_range.cc > @@ -0,0 +1,133 @@ > +// { dg-do run { target c++23 } } > + > +#include <span> > +#include <string> > +#include <testsuite_allocator.h> > +#include <testsuite_hooks.h> > +#include <testsuite_iterators.h> > + > +template<typename Range, typename Alloc> > +constexpr void > +do_test() > +{ > + // The vector's value_type. > + using V = typename std::allocator_traits<Alloc>::value_type; > + using CT = std::char_traits<V>; > + > + // The range's value_type. > + using T = std::ranges::range_value_t<Range>; > + T a[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', > + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}; > + > + auto eq = [](const std::basic_string<V, CT, Alloc>& l, std::span<T> r) { > + if (l.size() != r.size()) > + return false; > + for (auto i = 0u; i < l.size(); ++i) > + if (l[i] != r[i]) > + return false; > + return true; > + }; > + > + std::basic_string<V, CT, Alloc> v; > + v.replace_with_range(v.end(), v.end(), Range(a, a)); > + VERIFY( v.empty() ); > + v.replace_with_range(v.end(), v.end(), Range(a, a+4)); > + VERIFY( eq(v, {a, 4}) ); > + v.replace_with_range(v.end(), v.end(), Range(a+4, a+9)); > + VERIFY( eq(v, {a, 9}) ); > + std::basic_string<V, CT, Alloc> s = v; > + v.replace_with_range(v.end(), v.end(), Range(a+9, a+20)); > + VERIFY( eq(v, {a, 20}) ); > + > + v.replace_with_range(v.begin()+10, v.begin()+20, Range(a+5, a+10)); > + VERIFY( v.size() == 15 ); > + v.replace_with_range(v.begin(), v.begin()+10, Range(a, a+5)); > + VERIFY( eq(v, {a, 10}) ); > + > + s = v; > + v.replace_with_range(v.begin(), v.begin()+4, Range(a, a+8)); > + VERIFY( v.size() == 14 ); > + v.replace_with_range(v.begin()+8, v.begin()+12, Range(a+8, a+16)); > + VERIFY( v.size() == 18 ); > + v.replace_with_range(v.begin()+16, v.begin()+18, Range(a+16, a+20)); > + VERIFY( eq(v, {a, 20}) ); > +} > + > +template<typename Range> > +void > +do_test_a() > +{ > + do_test<Range, std::allocator<char>>(); > + do_test<Range, __gnu_test::SimpleAllocator<char>>(); > + do_test<Range, std::allocator<wchar_t>>(); > + do_test<Range, __gnu_test::SimpleAllocator<wchar_t>>(); > +} > + > +bool > +test_ranges() > +{ > + using namespace __gnu_test; > + > + do_test_a<test_forward_range<char>>(); > + do_test_a<test_forward_sized_range<char>>(); > + do_test_a<test_sized_range_sized_sent<char, > forward_iterator_wrapper>>(); > + > + do_test_a<test_input_range<char>>(); > + do_test_a<test_input_sized_range<char>>(); > + do_test_a<test_sized_range_sized_sent<char, input_iterator_wrapper>>(); > + > + do_test_a<test_range<char, input_iterator_wrapper_nocopy>>(); > + do_test_a<test_sized_range<char, input_iterator_wrapper_nocopy>>(); > + do_test_a<test_sized_range_sized_sent<char, > input_iterator_wrapper_nocopy>>(); > + > + // Not lvalue-convertible to char > + struct C { > + C(char v) : val(v) { } > + operator char() && { return val; } > + bool operator==(char b) const { return b == val; } > + char val; > + }; > + using rvalue_input_range = test_range<C, input_iterator_wrapper_rval>; > + do_test<rvalue_input_range, std::allocator<char>>(); > + > + return true; > +} > + > +void > +test_overlapping() > +{ > + std::string const s = "1234abcd"; > + > + std::string c = s; > + c.replace_with_range(c.end(), c.end(), std::string_view(c)); > + VERIFY( c == "1234abcd1234abcd" ); > + > + c = s; > + c.replace_with_range(c.begin(), c.begin()+4, > std::string_view(c).substr(4, 4)); > + VERIFY( c == "abcdabcd" ); > + > + c = s; > + c.replace_with_range(c.begin()+2, c.begin()+4, > std::string_view(c).substr(0, 4)); > + VERIFY( c == "121234abcd" ); > + > + c = s; > + c.replace_with_range(c.begin()+2, c.begin()+2, > std::string_view(c).substr(0, 4)); > + VERIFY( c == "12123434abcd" ); > +} > + > +constexpr bool > +test_constexpr() > +{ > +#if _GLIBCXX_USE_CXX11_ABI > + // XXX: this doesn't test the non-forward_range code paths are > constexpr. > + do_test<std::string_view, std::allocator<char>>(); > +#endif // _GLIBCXX_USE_CXX11_ABI > + return true; > +} > + > +int main() > +{ > + test_ranges(); > + test_overlapping(); > + static_assert( test_constexpr() ); > +} > -- > 2.49.0 > >