[libc++][ranges] Implement the changes to `basic_string` from P1206 (`ranges::to`):
authorvarconst <varconsteq@gmail.com>
Sat, 1 Jul 2023 00:04:33 +0000 (17:04 -0700)
committervarconst <varconsteq@gmail.com>
Wed, 5 Jul 2023 21:50:59 +0000 (14:50 -0700)
- add the `from_range_t` constructors and the related deduction guides;
- add the `insert_range`/`assign_range`/etc. member functions.

(Note: this patch is split from https://reviews.llvm.org/D142335)

Differential Revision: https://reviews.llvm.org/D149832

libcxx/include/string
libcxx/test/std/strings/basic.string/string.cons/from_range.pass.cpp [new file with mode: 0644]
libcxx/test/std/strings/basic.string/string.cons/from_range_deduction.pass.cpp [new file with mode: 0644]
libcxx/test/std/strings/basic.string/string.cons/iter_alloc_deduction.pass.cpp
libcxx/test/std/strings/basic.string/string.modifiers/string_append/append_range.pass.cpp [new file with mode: 0644]
libcxx/test/std/strings/basic.string/string.modifiers/string_assign/assign_range.pass.cpp [new file with mode: 0644]
libcxx/test/std/strings/basic.string/string.modifiers/string_insert/insert_range.pass.cpp [new file with mode: 0644]
libcxx/test/std/strings/basic.string/string.modifiers/string_replace/replace_with_range.pass.cpp [new file with mode: 0644]

index e0cbea6..e8fbd1f 100644 (file)
@@ -124,6 +124,8 @@ public:
     template<class InputIterator>
         basic_string(InputIterator begin, InputIterator end,
                      const allocator_type& a = allocator_type());                               // constexpr since C++20
+    template<container-compatible-range<charT> R>
+      constexpr basic_string(from_range_t, R&& rg, const Allocator& a = Allocator());           // since C++23
     basic_string(initializer_list<value_type>, const Allocator& = Allocator());                 // constexpr since C++20
     basic_string(const basic_string&, const Allocator&);                                        // constexpr since C++20
     basic_string(basic_string&&, const Allocator&);                                             // constexpr since C++20
@@ -200,6 +202,8 @@ public:
     basic_string& append(size_type n, value_type c);                                            // constexpr since C++20
     template<class InputIterator>
         basic_string& append(InputIterator first, InputIterator last);                          // constexpr since C++20
+    template<container-compatible-range<charT> R>
+      constexpr basic_string& append_range(R&& rg);                                             // C++23
     basic_string& append(initializer_list<value_type>);                                         // constexpr since C++20
 
     void push_back(value_type c);                                                               // constexpr since C++20
@@ -221,6 +225,8 @@ public:
     basic_string& assign(size_type n, value_type c);                                            // constexpr since C++20
     template<class InputIterator>
         basic_string& assign(InputIterator first, InputIterator last);                          // constexpr since C++20
+    template<container-compatible-range<charT> R>
+      constexpr basic_string& assign_range(R&& rg);                                             // C++23
     basic_string& assign(initializer_list<value_type>);                                         // constexpr since C++20
 
     basic_string& insert(size_type pos1, const basic_string& str);                              // constexpr since C++20
@@ -237,6 +243,8 @@ public:
     iterator      insert(const_iterator p, size_type n, value_type c);                          // constexpr since C++20
     template<class InputIterator>
         iterator insert(const_iterator p, InputIterator first, InputIterator last);             // constexpr since C++20
+    template<container-compatible-range<charT> R>
+      constexpr iterator insert_range(const_iterator p, R&& rg);                                // C++23
     iterator      insert(const_iterator p, initializer_list<value_type>);                       // constexpr since C++20
 
     basic_string& erase(size_type pos = 0, size_type n = npos);                                 // constexpr since C++20
@@ -262,6 +270,8 @@ public:
     basic_string& replace(const_iterator i1, const_iterator i2, size_type n, value_type c);     // constexpr since C++20
     template<class InputIterator>
         basic_string& replace(const_iterator i1, const_iterator i2, InputIterator j1, InputIterator j2); // constexpr since C++20
+    template<container-compatible-range<charT> R>
+      constexpr basic_string& replace_with_range(const_iterator i1, const_iterator i2, R&& rg); // C++23
     basic_string& replace(const_iterator i1, const_iterator i2, initializer_list<value_type>);  // constexpr since C++20
 
     size_type copy(value_type* s, size_type n, size_type pos = 0) const;                        // constexpr since C++20
@@ -354,6 +364,26 @@ basic_string(InputIterator, InputIterator, Allocator = Allocator())
                   char_traits<typename iterator_traits<InputIterator>::value_type>,
                   Allocator>;   // C++17
 
+template<ranges::input_range R,
+         class Allocator = allocator<ranges::range_value_t<R>>>
+  basic_string(from_range_t, R&&, Allocator = Allocator())
+    -> basic_string<ranges::range_value_t<R>, char_traits<ranges::range_value_t<R>>,
+                    Allocator>; // C++23
+
+template<class charT,
+         class traits,
+         class Allocator = allocator<charT>>
+  explicit basic_string(basic_string_view<charT, traits>, const Allocator& = Allocator())
+    -> basic_string<charT, traits, Allocator>; // C++17
+
+template<class charT,
+         class traits,
+         class Allocator = allocator<charT>>
+  basic_string(basic_string_view<charT, traits>,
+                typename see below::size_type, typename see below::size_type,
+                const Allocator& = Allocator())
+    -> basic_string<charT, traits, Allocator>; // C++17
+
 template<class charT, class traits, class Allocator>
 basic_string<charT, traits, Allocator>
 operator+(const basic_string<charT, traits, Allocator>& lhs,
@@ -558,6 +588,11 @@ basic_string<char32_t> operator "" s( const char32_t *str, size_t len );
 #include <__memory/pointer_traits.h>
 #include <__memory/swap_allocator.h>
 #include <__memory_resource/polymorphic_allocator.h>
+#include <__ranges/access.h>
+#include <__ranges/concepts.h>
+#include <__ranges/container_compatible_range.h>
+#include <__ranges/from_range.h>
+#include <__ranges/size.h>
 #include <__string/char_traits.h>
 #include <__string/extern_template_lists.h>
 #include <__type_traits/is_allocator.h>
@@ -573,6 +608,7 @@ basic_string<char32_t> operator "" s( const char32_t *str, size_t len );
 #include <__type_traits/void_t.h>
 #include <__utility/auto_cast.h>
 #include <__utility/declval.h>
+#include <__utility/forward.h>
 #include <__utility/is_pointer_in_range.h>
 #include <__utility/move.h>
 #include <__utility/swap.h>
@@ -662,6 +698,7 @@ struct __can_be_converted_to_string_view : public _BoolConstant<
     > {};
 
 struct __uninitialized_size_tag {};
+struct __init_with_sentinel_tag {};
 
 template<class _CharT, class _Traits, class _Allocator>
 class basic_string
@@ -822,6 +859,13 @@ private:
         }
     }
 
+    template <class _Iter, class _Sent>
+    _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
+    basic_string(__init_with_sentinel_tag, _Iter __first, _Sent __last, const allocator_type& __a)
+        : __r_(__default_init_tag(), __a) {
+      __init_with_sentinel(std::move(__first), std::move(__last));
+    }
+
     _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 iterator __make_iterator(pointer __p) {
         return iterator(__p);
     }
@@ -1022,6 +1066,19 @@ public:
     __init(__first, __last);
   }
 
+#if _LIBCPP_STD_VER >= 23
+  template <_ContainerCompatibleRange<_CharT> _Range>
+  _LIBCPP_HIDE_FROM_ABI constexpr
+  basic_string(from_range_t, _Range&& __range, const allocator_type& __a = allocator_type())
+      : __r_(__default_init_tag(), __a) {
+    if constexpr (ranges::forward_range<_Range> || ranges::sized_range<_Range>) {
+      __init_with_size(ranges::begin(__range), ranges::end(__range), ranges::distance(__range));
+    } else {
+      __init_with_sentinel(ranges::begin(__range), ranges::end(__range));
+    }
+  }
+#endif
+
 #ifndef _LIBCPP_CXX03_LANG
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string(initializer_list<_CharT> __il)
       : __r_(__default_init_tag(), __default_init_tag()) {
@@ -1232,6 +1289,15 @@ public:
     _LIBCPP_METHOD_TEMPLATE_IMPLICIT_INSTANTIATION_VIS _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string&
     append(_ForwardIterator __first, _ForwardIterator __last);
 
+#if _LIBCPP_STD_VER >= 23
+    template <_ContainerCompatibleRange<_CharT> _Range>
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr basic_string& append_range(_Range&& __range) {
+      insert_range(end(), std::forward<_Range>(__range));
+      return *this;
+    }
+#endif
+
 #ifndef _LIBCPP_CXX03_LANG
     _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
     basic_string& append(initializer_list<value_type> __il) {return append(__il.begin(), __il.size());}
@@ -1295,6 +1361,23 @@ public:
     _LIBCPP_METHOD_TEMPLATE_IMPLICIT_INSTANTIATION_VIS _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string&
     assign(_ForwardIterator __first, _ForwardIterator __last);
 
+#if _LIBCPP_STD_VER >= 23
+    template <_ContainerCompatibleRange<_CharT> _Range>
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr basic_string& assign_range(_Range&& __range) {
+      if constexpr (__string_is_trivial_iterator<ranges::iterator_t<_Range>>::value &&
+          (ranges::forward_range<_Range> || ranges::sized_range<_Range>)) {
+        size_type __n = static_cast<size_type>(ranges::distance(__range));
+        __assign_trivial(ranges::begin(__range), ranges::end(__range), __n);
+
+      } else {
+        __assign_with_sentinel(ranges::begin(__range), ranges::end(__range));
+      }
+
+      return *this;
+    }
+#endif
+
 #ifndef _LIBCPP_CXX03_LANG
     _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
     basic_string& assign(initializer_list<value_type> __il) {return assign(__il.begin(), __il.size());}
@@ -1326,6 +1409,21 @@ public:
   _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& insert(size_type __pos, size_type __n, value_type __c);
   _LIBCPP_CONSTEXPR_SINCE_CXX20 iterator insert(const_iterator __pos, value_type __c);
 
+#if _LIBCPP_STD_VER >= 23
+    template <_ContainerCompatibleRange<_CharT> _Range>
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr iterator insert_range(const_iterator __position, _Range&& __range) {
+      if constexpr (ranges::forward_range<_Range> || ranges::sized_range<_Range>) {
+        auto __n = static_cast<size_type>(ranges::distance(__range));
+        return __insert_with_size(__position, ranges::begin(__range), ranges::end(__range), __n);
+
+      } else {
+        basic_string __temp(from_range, std::forward<_Range>(__range), __alloc());
+        return insert(__position, __temp.data(), __temp.data() + __temp.size());
+      }
+    }
+#endif
+
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 iterator
   insert(const_iterator __pos, size_type __n, value_type __c) {
     difference_type __p = __pos - begin();
@@ -1412,6 +1510,15 @@ public:
   _LIBCPP_METHOD_TEMPLATE_IMPLICIT_INSTANTIATION_VIS _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string&
   replace(const_iterator __i1, const_iterator __i2, _InputIterator __j1, _InputIterator __j2);
 
+#if _LIBCPP_STD_VER >= 23
+  template <_ContainerCompatibleRange<_CharT> _Range>
+  _LIBCPP_HIDE_FROM_ABI
+  constexpr basic_string& replace_with_range(const_iterator __i1, const_iterator __i2, _Range&& __range) {
+    basic_string __temp(from_range, std::forward<_Range>(__range), __alloc());
+    return replace(__i1, __i2, __temp);
+  }
+#endif
+
 #ifndef _LIBCPP_CXX03_LANG
     _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
     basic_string& replace(const_iterator __i1, const_iterator __i2, initializer_list<value_type> __il)
@@ -1661,9 +1768,17 @@ private:
         return !__libcpp_is_constant_evaluated() && (__sz < __min_cap);
     }
 
-    template <class _ForwardIterator>
+    template <class _Iterator, class _Sentinel>
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI
+    void __assign_trivial(_Iterator __first, _Sentinel __last, size_type __n);
+
+    template <class _Iterator, class _Sentinel>
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI
+    void __assign_with_sentinel(_Iterator __first, _Sentinel __last);
+
+    template <class _ForwardIterator, class _Sentinel>
     _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14
-    iterator __insert_from_safe_copy(size_type __n, size_type __ip, _ForwardIterator __first, _ForwardIterator __last) {
+    iterator __insert_from_safe_copy(size_type __n, size_type __ip, _ForwardIterator __first, _Sentinel __last) {
         size_type __sz = size();
         size_type __cap = capacity();
         value_type* __p;
@@ -1688,6 +1803,10 @@ private:
         return begin() + __ip;
     }
 
+    template<class _Iterator, class _Sentinel>
+    _LIBCPP_CONSTEXPR_SINCE_CXX20
+    iterator __insert_with_size(const_iterator __pos, _Iterator __first, _Sentinel __last, size_type __n);
+
     _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 allocator_type& __alloc() _NOEXCEPT { return __r_.second(); }
     _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR const allocator_type& __alloc() const _NOEXCEPT { return __r_.second(); }
 
@@ -1792,6 +1911,13 @@ private:
     template <class _ForwardIterator, __enable_if_t<__has_forward_iterator_category<_ForwardIterator>::value, int> = 0>
     inline _LIBCPP_CONSTEXPR_SINCE_CXX20 void __init(_ForwardIterator __first, _ForwardIterator __last);
 
+    template <class _InputIterator, class _Sentinel>
+    _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
+    void __init_with_sentinel(_InputIterator __first, _Sentinel __last);
+    template <class _InputIterator, class _Sentinel>
+    _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
+    void __init_with_size(_InputIterator __first, _Sentinel __last, size_type __sz);
+
     _LIBCPP_CONSTEXPR_SINCE_CXX20
     void __grow_by(size_type __old_cap, size_type __delta_cap, size_type __old_sz,
                    size_type __n_copy,  size_type __n_del,     size_type __n_add = 0);
@@ -1970,6 +2096,15 @@ basic_string(basic_string_view<_CharT, _Traits>, _Sz, _Sz, const _Allocator& = _
   -> basic_string<_CharT, _Traits, _Allocator>;
 #endif
 
+#if _LIBCPP_STD_VER >= 23
+template <ranges::input_range _Range,
+          class _Allocator = allocator<ranges::range_value_t<_Range>>,
+          class = enable_if_t<__is_allocator<_Allocator>::value>
+          >
+basic_string(from_range_t, _Range&&, _Allocator = _Allocator())
+  -> basic_string<ranges::range_value_t<_Range>, char_traits<ranges::range_value_t<_Range>>, _Allocator>;
+#endif
+
 template <class _CharT, class _Traits, class _Allocator>
 _LIBCPP_CONSTEXPR_SINCE_CXX20
 void basic_string<_CharT, _Traits, _Allocator>::__init(const value_type* __s,
@@ -2085,7 +2220,15 @@ template <class _InputIterator, __enable_if_t<__has_exactly_input_iterator_categ
 _LIBCPP_CONSTEXPR_SINCE_CXX20
 void basic_string<_CharT, _Traits, _Allocator>::__init(_InputIterator __first, _InputIterator __last)
 {
+  __init_with_sentinel(std::move(__first), std::move(__last));
+}
+
+template <class _CharT, class _Traits, class _Allocator>
+template <class _InputIterator, class _Sentinel>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
+void basic_string<_CharT, _Traits, _Allocator>::__init_with_sentinel(_InputIterator __first, _Sentinel __last) {
     __default_init();
+
 #ifndef _LIBCPP_HAS_NO_EXCEPTIONS
     try
     {
@@ -2108,16 +2251,27 @@ template <class _ForwardIterator, __enable_if_t<__has_forward_iterator_category<
 _LIBCPP_CONSTEXPR_SINCE_CXX20 void
 basic_string<_CharT, _Traits, _Allocator>::__init(_ForwardIterator __first, _ForwardIterator __last)
 {
+  size_type __sz = static_cast<size_type>(std::distance(__first, __last));
+  __init_with_size(__first, __last, __sz);
+}
+
+template <class _CharT, class _Traits, class _Allocator>
+template <class _InputIterator, class _Sentinel>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
+void basic_string<_CharT, _Traits, _Allocator>::__init_with_size(
+    _InputIterator __first, _Sentinel __last, size_type __sz) {
     if (__libcpp_is_constant_evaluated())
         __r_.first() = __rep();
-    size_type __sz = static_cast<size_type>(std::distance(__first, __last));
+
     if (__sz > max_size())
         __throw_length_error();
+
     pointer __p;
     if (__fits_in_sso(__sz))
     {
         __set_short_size(__sz);
         __p = __get_short_pointer();
+
     }
     else
     {
@@ -2369,9 +2523,17 @@ template<class _InputIterator, __enable_if_t<__has_exactly_input_iterator_catego
 _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string<_CharT, _Traits, _Allocator>&
 basic_string<_CharT, _Traits, _Allocator>::assign(_InputIterator __first, _InputIterator __last)
 {
-    const basic_string __temp(__first, __last, __alloc());
-    assign(__temp.data(), __temp.size());
-    return *this;
+  __assign_with_sentinel(__first, __last);
+  return *this;
+}
+
+template <class _CharT, class _Traits, class _Allocator>
+template <class _InputIterator, class _Sentinel>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
+void
+basic_string<_CharT, _Traits, _Allocator>::__assign_with_sentinel(_InputIterator __first, _Sentinel __last) {
+  const basic_string __temp(__init_with_sentinel_tag(), std::move(__first), std::move(__last), __alloc());
+  assign(__temp.data(), __temp.size());
 }
 
 template <class _CharT, class _Traits, class _Allocator>
@@ -2379,30 +2541,40 @@ template<class _ForwardIterator, __enable_if_t<__has_forward_iterator_category<_
 _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string<_CharT, _Traits, _Allocator>&
 basic_string<_CharT, _Traits, _Allocator>::assign(_ForwardIterator __first, _ForwardIterator __last)
 {
-    size_type __cap = capacity();
-    size_type __n = __string_is_trivial_iterator<_ForwardIterator>::value ?
-        static_cast<size_type>(std::distance(__first, __last)) : 0;
+  if (__string_is_trivial_iterator<_ForwardIterator>::value) {
+    size_type __n = static_cast<size_type>(std::distance(__first, __last));
+    __assign_trivial(__first, __last, __n);
+  } else {
+    __assign_with_sentinel(__first, __last);
+  }
 
-    if (__string_is_trivial_iterator<_ForwardIterator>::value &&
-        (__cap >= __n || !__addr_in_range(*__first)))
-    {
-        if (__cap < __n)
-        {
-            size_type __sz = size();
-            __grow_by(__cap, __n - __cap, __sz, 0, __sz);
-        }
-        pointer __p = __get_pointer();
-        for (; __first != __last; ++__p, (void) ++__first)
-            traits_type::assign(*__p, *__first);
-        traits_type::assign(*__p, value_type());
-        __set_size(__n);
-    }
-    else
-    {
-        const basic_string __temp(__first, __last, __alloc());
-        assign(__temp.data(), __temp.size());
+  return *this;
+}
+
+template <class _CharT, class _Traits, class _Allocator>
+template <class _Iterator, class _Sentinel>
+_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI
+void
+basic_string<_CharT, _Traits, _Allocator>::__assign_trivial(_Iterator __first, _Sentinel __last, size_type __n) {
+  _LIBCPP_ASSERT_UNCATEGORIZED(
+      __string_is_trivial_iterator<_Iterator>::value, "The iterator type given to `__assign_trivial` must be trivial");
+
+  size_type __cap = capacity();
+  if (__cap < __n) {
+    // Unlike `append` functions, if the input range points into the string itself, there is no case that the input
+    // range could get invalidated by reallocation:
+    // 1. If the input range is a subset of the string itself, its size cannot exceed the capacity of the string,
+    //    thus no reallocation would happen.
+    // 2. In the exotic case where the input range is the byte representation of the string itself, the string
+    //    object itself stays valid even if reallocation happens.
+    size_type __sz = size();
+    __grow_by(__cap, __n - __cap, __sz, 0, __sz);
     }
-    return *this;
+    pointer __p = __get_pointer();
+    for (; __first != __last; ++__p, (void) ++__first)
+        traits_type::assign(*__p, *__first);
+    traits_type::assign(*__p, value_type());
+    __set_size(__n);
 }
 
 template <class _CharT, class _Traits, class _Allocator>
@@ -2703,18 +2875,27 @@ template<class _ForwardIterator, __enable_if_t<__has_forward_iterator_category<_
 _LIBCPP_CONSTEXPR_SINCE_CXX20 typename basic_string<_CharT, _Traits, _Allocator>::iterator
 basic_string<_CharT, _Traits, _Allocator>::insert(const_iterator __pos, _ForwardIterator __first, _ForwardIterator __last)
 {
+    auto __n = static_cast<size_type>(std::distance(__first, __last));
+    return __insert_with_size(__pos, __first, __last, __n);
+}
+
+template <class _CharT, class _Traits, class _Allocator>
+template<class _Iterator, class _Sentinel>
+_LIBCPP_CONSTEXPR_SINCE_CXX20
+typename basic_string<_CharT, _Traits, _Allocator>::iterator
+basic_string<_CharT, _Traits, _Allocator>::__insert_with_size(
+    const_iterator __pos, _Iterator __first, _Sentinel __last, size_type __n) {
     size_type __ip = static_cast<size_type>(__pos - begin());
-    size_type __n = static_cast<size_type>(std::distance(__first, __last));
     if (__n == 0)
         return begin() + __ip;
 
-    if (__string_is_trivial_iterator<_ForwardIterator>::value && !__addr_in_range(*__first))
+    if (__string_is_trivial_iterator<_Iterator>::value && !__addr_in_range(*__first))
     {
         return __insert_from_safe_copy(__n, __ip, __first, __last);
     }
     else
     {
-        const basic_string __temp(__first, __last, __alloc());
+        const basic_string __temp(__init_with_sentinel_tag(), __first, __last, __alloc());
         return __insert_from_safe_copy(__n, __ip, __temp.begin(), __temp.end());
     }
 }
diff --git a/libcxx/test/std/strings/basic.string/string.cons/from_range.pass.cpp b/libcxx/test/std/strings/basic.string/string.cons/from_range.pass.cpp
new file mode 100644 (file)
index 0000000..b8ec2f4
--- /dev/null
@@ -0,0 +1,131 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// template<container-compatible-range<charT> R>
+//   constexpr basic_string(from_range_t, R&& rg, const Allocator& a = Allocator());           // since C++23
+
+#include <algorithm>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "../../../containers/from_range_helpers.h"
+#include "../../../containers/sequences/from_range_sequence_containers.h"
+#include "test_macros.h"
+
+template <class Container, class Range, class Alloc>
+concept StringHasFromRangeAllocCtr = requires (Range&& range, const Alloc& alloc) {
+  Container(std::from_range, std::forward<Range>(range), alloc);
+};
+
+constexpr bool test_constraints() {
+  // (from_range, range)
+  //
+  // Input range with the same value type.
+  static_assert(HasFromRangeCtr<std::string, InputRange<char>>);
+  // Input range with a convertible value type.
+  static_assert(HasFromRangeCtr<std::string, InputRange<int>>);
+  // Input range with a non-convertible value type.
+  static_assert(!HasFromRangeCtr<std::string, InputRange<Empty>>);
+  // Not an input range.
+  static_assert(!HasFromRangeCtr<std::string, InputRangeNotDerivedFrom>);
+  static_assert(!HasFromRangeCtr<std::string, InputRangeNotIndirectlyReadable>);
+  static_assert(!HasFromRangeCtr<std::string, InputRangeNotInputOrOutputIterator>);
+
+  // (from_range, range, alloc)
+  //
+  // Input range with the same value type.
+  using Alloc = test_allocator<char>;
+  using StringWithAlloc = std::basic_string<char, std::char_traits<char>, Alloc>;
+  static_assert(StringHasFromRangeAllocCtr<StringWithAlloc, InputRange<char>, Alloc>);
+  // Input range with a convertible value type.
+  static_assert(StringHasFromRangeAllocCtr<StringWithAlloc, InputRange<int>, Alloc>);
+  // Input range with a non-convertible value type.
+  static_assert(!StringHasFromRangeAllocCtr<StringWithAlloc, InputRange<Empty>, Alloc>);
+  // Not an input range.
+  static_assert(!StringHasFromRangeAllocCtr<StringWithAlloc, InputRangeNotDerivedFrom, Alloc>);
+  static_assert(!StringHasFromRangeAllocCtr<StringWithAlloc, InputRangeNotIndirectlyReadable, Alloc>);
+  static_assert(!StringHasFromRangeAllocCtr<StringWithAlloc, InputRangeNotInputOrOutputIterator, Alloc>);
+  // Not an allocator.
+  static_assert(!StringHasFromRangeAllocCtr<StringWithAlloc, InputRange<char>, Empty>);
+
+  return true;
+}
+
+template <class Iter,
+          class Sent,
+          class Alloc>
+constexpr void test_with_input(std::vector<char> input) {
+  auto b = Iter(input.data());
+  auto e = Iter(input.data() + input.size());
+  std::ranges::subrange in(std::move(b), Sent(std::move(e)));
+
+  { // (range)
+    std::string c(std::from_range, in);
+
+    LIBCPP_ASSERT(c.__invariants());
+    assert(c.size() == static_cast<std::size_t>(std::distance(c.begin(), c.end())));
+    assert(std::ranges::equal(in, c));
+  }
+
+  { // (range, allocator)
+    Alloc alloc;
+    std::basic_string<char, std::char_traits<char>, Alloc> c(std::from_range, in, alloc);
+
+    LIBCPP_ASSERT(c.__invariants());
+    assert(c.get_allocator() == alloc);
+    assert(c.size() == static_cast<std::size_t>(std::distance(c.begin(), c.end())));
+    assert(std::ranges::equal(in, c));
+  }
+}
+
+void test_string_exception_safety_throwing_allocator() {
+#if !defined(TEST_HAS_NO_EXCEPTIONS)
+  try {
+    ThrowingAllocator<char> alloc;
+
+    globalMemCounter.reset();
+    // Note: the input string must be long enough to prevent SSO, otherwise the allocator won't be used.
+    std::basic_string<char, std::char_traits<char>, ThrowingAllocator<char>> c(
+        std::from_range, std::vector<char>(64, 'A'), alloc);
+    assert(false); // The constructor call should throw.
+
+  } catch (int) {
+    assert(globalMemCounter.new_called == globalMemCounter.delete_called);
+  }
+#endif
+}
+
+constexpr bool test_inputs() {
+  for_all_iterators_and_allocators<char>([]<class Iter, class Sent, class Alloc>() {
+    // Shorter input -- SSO.
+    test_with_input<Iter, Sent, Alloc>({'a', 'b', 'c', 'd', 'e'});
+    // Longer input -- no SSO.
+    test_with_input<Iter, Sent, Alloc>(std::vector<char>(64, 'A'));
+    // Empty input.
+    test_with_input<Iter, Sent, Alloc>({});
+    // Single-element input.
+    test_with_input<Iter, Sent, Alloc>({'a'});
+  });
+
+  return true;
+}
+
+int main(int, char**) {
+  test_inputs();
+  static_assert(test_inputs());
+
+  static_assert(test_constraints());
+
+  // Note: `test_exception_safety_throwing_copy` doesn't apply because copying a `char` cannot throw.
+  test_string_exception_safety_throwing_allocator();
+
+  return 0;
+}
diff --git a/libcxx/test/std/strings/basic.string/string.cons/from_range_deduction.pass.cpp b/libcxx/test/std/strings/basic.string/string.cons/from_range_deduction.pass.cpp
new file mode 100644 (file)
index 0000000..4df4d39
--- /dev/null
@@ -0,0 +1,50 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <string>
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// To silence a GCC warning-turned-error re. `BadAlloc::value_type`.
+// ADDITIONAL_COMPILE_FLAGS: -Wno-unused-local-typedefs
+
+// template<ranges::input_range R,
+//          class Allocator = allocator<ranges::range_value_t<R>>>
+//   basic_string(from_range_t, R&&, Allocator = Allocator())
+//     -> basic_string<ranges::range_value_t<R>, char_traits<ranges::range_value_t<R>>,
+//                     Allocator>; // C++23
+//
+// The deduction guide shall not participate in overload resolution if Allocator
+// is a type that does not qualify as an allocator (in addition to the `input_range` concept being satisfied by `R`).
+
+#include <array>
+#include <string>
+
+#include "deduction_guides_sfinae_checks.h"
+#include "test_allocator.h"
+
+int main(int, char**) {
+  using Char = char16_t;
+
+  {
+    std::basic_string c(std::from_range, std::array<Char, 0>());
+    static_assert(std::is_same_v<decltype(c), std::basic_string<Char>>);
+  }
+
+  {
+    using Alloc = test_allocator<Char>;
+    std::basic_string c(std::from_range, std::array<Char, 0>(), Alloc());
+    static_assert(std::is_same_v<decltype(c), std::basic_string<Char, std::char_traits<Char>, Alloc>>);
+  }
+
+  // Note: defining `value_type` is a workaround because one of the deduction guides will end up instantiating
+  // `basic_string`, and that would fail with a hard error if the given allocator doesn't define `value_type`.
+  struct BadAlloc { using value_type = char; };
+  SequenceContainerDeductionGuidesSfinaeAway<std::basic_string, std::basic_string<char>, BadAlloc>();
+
+  return 0;
+}
index d6db253..d90d3f5 100644 (file)
@@ -47,6 +47,7 @@ struct CanDeduce<Iter, Alloc, decltype((void)
 static_assert( CanDeduce<char*, std::allocator<char>>::value);
 static_assert(!CanDeduce<NotAnIterator, std::allocator<char>>::value);
 static_assert(!CanDeduce<NotAnInputIterator, std::allocator<char16_t>>::value);
+static_assert(!CanDeduce<char*, NotAnAllocator<char>>::value);
 #ifndef TEST_HAS_NO_WIDE_CHARACTERS
 static_assert( CanDeduce<wchar_t*, std::allocator<wchar_t>>::value);
 static_assert(!CanDeduce<wchar_t const*, NotAnAllocator<wchar_t>>::value);
diff --git a/libcxx/test/std/strings/basic.string/string.modifiers/string_append/append_range.pass.cpp b/libcxx/test/std/strings/basic.string/string.modifiers/string_append/append_range.pass.cpp
new file mode 100644 (file)
index 0000000..bc73697
--- /dev/null
@@ -0,0 +1,76 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// template<container-compatible-range<charT> R>
+//   constexpr basic_string& append_range(R&& rg); // C++23
+
+#include <string>
+
+#include "../../../../containers/sequences/insert_range_sequence_containers.h"
+#include "test_macros.h"
+
+// Tested cases:
+// - different kinds of insertions (appending an {empty/one-element/mid-sized/long range} into an
+//   {empty/one-element/full} container);
+// - an exception is thrown when allocating new elements.
+
+constexpr bool test_constexpr() {
+  for_all_iterators_and_allocators_constexpr<char, const char*>([]<class Iter, class Sent, class Alloc>() {
+    test_sequence_append_range<std::basic_string<char, std::char_traits<char>, Alloc>, Iter, Sent>([](auto&& c) {
+      LIBCPP_ASSERT(c.__invariants());
+    });
+  });
+
+  return true;
+}
+
+int main(int, char**) {
+  static_assert(test_constraints_append_range<std::basic_string, char, int>());
+
+  for_all_iterators_and_allocators<char, const char*>([]<class Iter, class Sent, class Alloc>() {
+    test_sequence_append_range<std::basic_string<char, std::char_traits<char>, Alloc>, Iter, Sent>([](auto&& c) {
+      LIBCPP_ASSERT(c.__invariants());
+    });
+  });
+  static_assert(test_constexpr());
+
+  { // Check that `append_range` returns a reference to the string.
+    std::string c;
+    static_assert(std::is_lvalue_reference_v<decltype(
+        c.append_range(FullContainer_Begin_EmptyRange<char>.input)
+    )>);
+    assert(&c.append_range(FullContainer_Begin_EmptyRange<char>.input) == &c);
+    assert(&c.append_range(FullContainer_Begin_OneElementRange<char>.input) == &c);
+    assert(&c.append_range(FullContainer_Begin_MidRange<char>.input) == &c);
+    assert(&c.append_range(FullContainer_Begin_LongRange<char>.input) == &c);
+  }
+
+  // Note: `test_append_range_exception_safety_throwing_copy` doesn't apply because copying chars cannot throw.
+  {
+#if !defined(TEST_HAS_NO_EXCEPTIONS)
+    // Note: the input string must be long enough to prevent SSO, otherwise the allocator won't be used.
+    std::string in(64, 'a');
+
+    try {
+      ThrowingAllocator<char> alloc;
+
+      globalMemCounter.reset();
+      std::basic_string<char, std::char_traits<char>, ThrowingAllocator<char>> c(alloc);
+      c.append_range(in);
+      assert(false); // The function call above should throw.
+
+    } catch (int) {
+      assert(globalMemCounter.new_called == globalMemCounter.delete_called);
+    }
+#endif
+  }
+
+  return 0;
+}
diff --git a/libcxx/test/std/strings/basic.string/string.modifiers/string_assign/assign_range.pass.cpp b/libcxx/test/std/strings/basic.string/string.modifiers/string_assign/assign_range.pass.cpp
new file mode 100644 (file)
index 0000000..fe213df
--- /dev/null
@@ -0,0 +1,76 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// template<container-compatible-range<charT> R>
+//   constexpr basic_string& assign_range(R&& rg); // C++23
+
+#include <string>
+
+#include "../../../../containers/sequences/insert_range_sequence_containers.h"
+#include "test_macros.h"
+
+// Tested cases:
+// - different kinds of assignments (assigning an {empty/one-element/mid-sized/long range} to an
+//   {empty/one-element/full} container);
+// - an exception is thrown when allocating new elements.
+
+constexpr bool test_constexpr() {
+  for_all_iterators_and_allocators_constexpr<char, const char*>([]<class Iter, class Sent, class Alloc>() {
+    test_sequence_assign_range<std::basic_string<char, std::char_traits<char>, Alloc>, Iter, Sent>([](auto&& c) {
+      LIBCPP_ASSERT(c.__invariants());
+    });
+  });
+
+  return true;
+}
+
+int main(int, char**) {
+  static_assert(test_constraints_assign_range<std::basic_string, char, int>());
+
+  for_all_iterators_and_allocators<char, const char*>([]<class Iter, class Sent, class Alloc>() {
+    test_sequence_assign_range<std::basic_string<char, std::char_traits<char>, Alloc>, Iter, Sent>([](auto&& c) {
+      LIBCPP_ASSERT(c.__invariants());
+    });
+  });
+  static_assert(test_constexpr());
+
+  { // Check that `assign_range` returns a reference to the string.
+    std::string c;
+    static_assert(std::is_lvalue_reference_v<decltype(
+        c.assign_range(FullContainer_Begin_EmptyRange<char>.input)
+    )>);
+    assert(&c.assign_range(FullContainer_Begin_EmptyRange<char>.input) == &c);
+    assert(&c.assign_range(FullContainer_Begin_OneElementRange<char>.input) == &c);
+    assert(&c.assign_range(FullContainer_Begin_MidRange<char>.input) == &c);
+    assert(&c.assign_range(FullContainer_Begin_LongRange<char>.input) == &c);
+  }
+
+  // Note: `test_assign_range_exception_safety_throwing_copy` doesn't apply because copying chars cannot throw.
+  {
+#if !defined(TEST_HAS_NO_EXCEPTIONS)
+    // Note: the input string must be long enough to prevent SSO, otherwise the allocator won't be used.
+    std::string in(64, 'a');
+
+    try {
+      ThrowingAllocator<char> alloc;
+
+      globalMemCounter.reset();
+      std::basic_string<char, std::char_traits<char>, ThrowingAllocator<char>> c(alloc);
+      c.assign_range(in);
+      assert(false); // The function call above should throw.
+
+    } catch (int) {
+      assert(globalMemCounter.new_called == globalMemCounter.delete_called);
+    }
+#endif
+  }
+
+  return 0;
+}
diff --git a/libcxx/test/std/strings/basic.string/string.modifiers/string_insert/insert_range.pass.cpp b/libcxx/test/std/strings/basic.string/string.modifiers/string_insert/insert_range.pass.cpp
new file mode 100644 (file)
index 0000000..aefe73a
--- /dev/null
@@ -0,0 +1,65 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// template<container-compatible-range<charT> R>
+//   constexpr iterator insert_range(const_iterator p, R&& rg);                                // C++23
+
+#include <string>
+
+#include "../../../../containers/sequences/insert_range_sequence_containers.h"
+#include "test_macros.h"
+
+// Tested cases:
+// - different kinds of insertions (inserting an {empty/one-element/mid-sized/long range} into an
+//   {empty/one-element/full} container at the {beginning/middle/end});
+// - an exception is thrown when allocating new elements.
+
+constexpr bool test_constexpr() {
+  for_all_iterators_and_allocators_constexpr<char, const char*>([]<class Iter, class Sent, class Alloc>() {
+    test_sequence_insert_range<std::basic_string<char, std::char_traits<char>, Alloc>, Iter, Sent>([](auto&& c) {
+      LIBCPP_ASSERT(c.__invariants());
+    });
+  });
+
+  return true;
+}
+
+int main(int, char**) {
+  static_assert(test_constraints_insert_range<std::basic_string, char, int>());
+
+  for_all_iterators_and_allocators<char, const char*>([]<class Iter, class Sent, class Alloc>() {
+    test_sequence_insert_range<std::basic_string<char, std::char_traits<char>, Alloc>, Iter, Sent>([](auto&& c) {
+      LIBCPP_ASSERT(c.__invariants());
+    });
+  });
+  static_assert(test_constexpr());
+
+  // Note: `test_insert_range_exception_safety_throwing_copy` doesn't apply because copying chars cannot throw.
+  {
+#if !defined(TEST_HAS_NO_EXCEPTIONS)
+    // Note: the input string must be long enough to prevent SSO, otherwise the allocator won't be used.
+    std::string in(64, 'a');
+
+    try {
+      ThrowingAllocator<char> alloc;
+
+      globalMemCounter.reset();
+      std::basic_string<char, std::char_traits<char>, ThrowingAllocator<char>> c(alloc);
+      c.insert_range(c.end(), in);
+      assert(false); // The function call above should throw.
+
+    } catch (int) {
+      assert(globalMemCounter.new_called == globalMemCounter.delete_called);
+    }
+#endif
+  }
+
+  return 0;
+}
diff --git a/libcxx/test/std/strings/basic.string/string.modifiers/string_replace/replace_with_range.pass.cpp b/libcxx/test/std/strings/basic.string/string.modifiers/string_replace/replace_with_range.pass.cpp
new file mode 100644 (file)
index 0000000..e9bfe41
--- /dev/null
@@ -0,0 +1,916 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// ADDITIONAL_COMPILE_FLAGS(has-fconstexpr-steps): -fconstexpr-steps=10000000
+// ADDITIONAL_COMPILE_FLAGS(has-fconstexpr-ops-limit): -fconstexpr-ops-limit=70000000
+
+// template<container-compatible-range<charT> R>
+//   constexpr basic_string& replace_with_range(const_iterator i1, const_iterator i2, R&& rg); // C++23
+
+#include <string>
+#include <utility>
+
+#include "../../../../containers/sequences/insert_range_sequence_containers.h"
+#include "test_macros.h"
+
+template <class Range>
+concept HasReplaceWithRange = requires (std::string& c, Range&& range) {
+  c.replace_with_range(c.end(), c.end(), std::forward<Range>(range));
+};
+
+constexpr bool test_constraints_replace_with_range() {
+  // Input range with the same value type.
+  static_assert(HasReplaceWithRange<InputRange<char>>);
+  // Input range with a convertible value type.
+  static_assert(HasReplaceWithRange<InputRange<unsigned char>>);
+  // Input range with a non-convertible value type.
+  static_assert(!HasReplaceWithRange<InputRange<Empty>>);
+  // Not an input range.
+  static_assert(!HasReplaceWithRange<InputRangeNotDerivedFrom>);
+  static_assert(!HasReplaceWithRange<InputRangeNotIndirectlyReadable>);
+  static_assert(!HasReplaceWithRange<InputRangeNotInputOrOutputIterator>);
+
+  return true;
+}
+
+using StrBuffer = Buffer<char, 256>;
+
+struct TestCaseReplacement {
+  StrBuffer initial;
+  std::size_t from = 0;
+  std::size_t to = 0;
+  StrBuffer input;
+  StrBuffer expected;
+};
+
+// Permutation matrix:
+// - initial string: empty / one-element / n elements;
+// - cut starts from: beginning / middle / end;
+// - cut size: empty / one-element / several elements / until the end;
+// - input range: empty / one-element / middle-sized / longer than SSO / longer than the current string capacity.
+
+// Empty string.
+
+constexpr TestCaseReplacement EmptyString_End_EmptyCut_EmptyRange {
+  .initial = "", .from = 0, .to = 0, .input = "", .expected = ""
+};
+
+constexpr TestCaseReplacement EmptyString_End_EmptyCut_OneElementRange {
+  .initial = "", .from = 0, .to = 0, .input = "a", .expected = "a"
+};
+
+constexpr TestCaseReplacement EmptyString_End_EmptyCut_MidRange {
+  .initial = "", .from = 0, .to = 0, .input = "aeiou", .expected = "aeiou"
+};
+
+constexpr TestCaseReplacement EmptyString_End_EmptyCut_LongRange {
+  .initial = "", .from = 0, .to = 0,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "abcdefghijklmnopqrstuvwxyz0123456789"
+};
+
+// One-element string.
+
+constexpr TestCaseReplacement OneElementString_Begin_EmptyCut_EmptyRange {
+  .initial = "B", .from = 0, .to = 0, .input = "", .expected = "B"
+};
+
+constexpr TestCaseReplacement OneElementString_Begin_EmptyCut_OneElementRange {
+  .initial = "B", .from = 0, .to = 0, .input = "a", .expected = "aB"
+};
+
+constexpr TestCaseReplacement OneElementString_Begin_EmptyCut_MidRange {
+  .initial = "B", .from = 0, .to = 0, .input = "aeiou", .expected = "aeiouB"
+};
+
+constexpr TestCaseReplacement OneElementString_Begin_EmptyCut_LongRange {
+  .initial = "B", .from = 0, .to = 0,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "abcdefghijklmnopqrstuvwxyz0123456789B"
+};
+
+constexpr TestCaseReplacement OneElementString_Begin_OneElementCut_EmptyRange {
+  .initial = "B", .from = 0, .to = 1, .input = "", .expected = ""
+};
+
+constexpr TestCaseReplacement OneElementString_Begin_OneElementCut_OneElementRange {
+  .initial = "B", .from = 0, .to = 1, .input = "a", .expected = "a"
+};
+
+constexpr TestCaseReplacement OneElementString_Begin_OneElementCut_MidRange {
+  .initial = "B", .from = 0, .to = 1, .input = "aeiou", .expected = "aeiou"
+};
+
+constexpr TestCaseReplacement OneElementString_Begin_OneElementCut_LongRange {
+  .initial = "B", .from = 0, .to = 1,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "abcdefghijklmnopqrstuvwxyz0123456789"
+};
+
+constexpr TestCaseReplacement OneElementString_End_EmptyCut_EmptyRange {
+  .initial = "B", .from = 1, .to = 1, .input = "", .expected = "B"
+};
+
+constexpr TestCaseReplacement OneElementString_End_EmptyCut_OneElementRange {
+  .initial = "B", .from = 1, .to = 1, .input = "a", .expected = "Ba"
+};
+
+constexpr TestCaseReplacement OneElementString_End_EmptyCut_MidRange {
+  .initial = "B", .from = 1, .to = 1, .input = "aeiou", .expected = "Baeiou"
+};
+
+constexpr TestCaseReplacement OneElementString_End_EmptyCut_LongRange {
+  .initial = "B", .from = 1, .to = 1,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "Babcdefghijklmnopqrstuvwxyz0123456789"
+};
+
+// Short string (using SSO).
+
+// Replace at the beginning.
+
+constexpr TestCaseReplacement ShortString_Begin_EmptyCut_EmptyRange {
+  .initial = "_BCDFGHJ_", .from = 0, .to = 0, .input = "", .expected = "_BCDFGHJ_"
+};
+
+constexpr TestCaseReplacement ShortString_Begin_EmptyCut_OneElementRange {
+  .initial = "_BCDFGHJ_", .from = 0, .to = 0, .input = "a", .expected = "a_BCDFGHJ_"
+};
+
+constexpr TestCaseReplacement ShortString_Begin_EmptyCut_MidRange {
+  .initial = "_BCDFGHJ_", .from = 0, .to = 0, .input = "aeiou", .expected = "aeiou_BCDFGHJ_"
+};
+
+constexpr TestCaseReplacement ShortString_Begin_EmptyCut_LongRange {
+  .initial = "_BCDFGHJ_", .from = 0, .to = 0,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "abcdefghijklmnopqrstuvwxyz0123456789_BCDFGHJ_"
+};
+
+constexpr TestCaseReplacement ShortString_Begin_OneElementCut_EmptyRange {
+  .initial = "_BCDFGHJ_", .from = 0, .to = 1, .input = "", .expected = "BCDFGHJ_"
+};
+
+constexpr TestCaseReplacement ShortString_Begin_OneElementCut_OneElementRange {
+  .initial = "_BCDFGHJ_", .from = 0, .to = 1, .input = "a", .expected = "aBCDFGHJ_"
+};
+
+constexpr TestCaseReplacement ShortString_Begin_OneElementCut_MidRange {
+  .initial = "_BCDFGHJ_", .from = 0, .to = 1, .input = "aeiou", .expected = "aeiouBCDFGHJ_"
+};
+
+constexpr TestCaseReplacement ShortString_Begin_OneElementCut_LongRange {
+  .initial = "_BCDFGHJ_", .from = 0, .to = 1,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "abcdefghijklmnopqrstuvwxyz0123456789BCDFGHJ_"
+};
+
+constexpr TestCaseReplacement ShortString_Begin_MidSizedCut_EmptyRange {
+  .initial = "_BCDFGHJ_", .from = 0, .to = 3, .input = "", .expected = "DFGHJ_"
+};
+
+constexpr TestCaseReplacement ShortString_Begin_MidSizedCut_OneElementRange {
+  .initial = "_BCDFGHJ_", .from = 0, .to = 3, .input = "a", .expected = "aDFGHJ_"
+};
+
+constexpr TestCaseReplacement ShortString_Begin_MidSizedCut_MidRange {
+  .initial = "_BCDFGHJ_", .from = 0, .to = 3, .input = "aeiou", .expected = "aeiouDFGHJ_"
+};
+
+// Note: this test case switches from SSO to non-SSO.
+constexpr TestCaseReplacement ShortString_Begin_MidSizedCut_LongRange {
+  .initial = "_BCDFGHJ_", .from = 0, .to = 3,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "abcdefghijklmnopqrstuvwxyz0123456789DFGHJ_"
+};
+
+constexpr TestCaseReplacement ShortString_Begin_CutToEnd_EmptyRange {
+  .initial = "_BCDFGHJ_", .from = 0, .to = 9, .input = "", .expected = ""
+};
+
+constexpr TestCaseReplacement ShortString_Begin_CutToEnd_OneElementRange {
+  .initial = "_BCDFGHJ_", .from = 0, .to = 9, .input = "a", .expected = "a"
+};
+
+constexpr TestCaseReplacement ShortString_Begin_CutToEnd_MidRange {
+  .initial = "_BCDFGHJ_", .from = 0, .to = 9, .input = "aeiou", .expected = "aeiou"
+};
+
+// Note: this test case switches from SSO to non-SSO.
+constexpr TestCaseReplacement ShortString_Begin_CutToEnd_LongRange {
+  .initial = "_BCDFGHJ_", .from = 0, .to = 9,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "abcdefghijklmnopqrstuvwxyz0123456789"
+};
+
+// Replace in the middle.
+
+constexpr TestCaseReplacement ShortString_Mid_EmptyCut_EmptyRange {
+  .initial = "_BCDFGHJ_", .from = 4, .to = 4, .input = "", .expected = "_BCDFGHJ_"
+};
+
+constexpr TestCaseReplacement ShortString_Mid_EmptyCut_OneElementRange {
+  .initial = "_BCDFGHJ_", .from = 4, .to = 4, .input = "a", .expected = "_BCDaFGHJ_"
+};
+
+constexpr TestCaseReplacement ShortString_Mid_EmptyCut_MidRange {
+  .initial = "_BCDFGHJ_", .from = 4, .to = 4, .input = "aeiou", .expected = "_BCDaeiouFGHJ_"
+};
+
+constexpr TestCaseReplacement ShortString_Mid_EmptyCut_LongRange {
+  .initial = "_BCDFGHJ_", .from = 4, .to = 4,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "_BCDabcdefghijklmnopqrstuvwxyz0123456789FGHJ_"
+};
+
+constexpr TestCaseReplacement ShortString_Mid_OneElementCut_EmptyRange {
+  .initial = "_BCDFGHJ_", .from = 4, .to = 5, .input = "", .expected = "_BCDGHJ_"
+};
+
+constexpr TestCaseReplacement ShortString_Mid_OneElementCut_OneElementRange {
+  .initial = "_BCDFGHJ_", .from = 4, .to = 5, .input = "a", .expected = "_BCDaGHJ_"
+};
+
+constexpr TestCaseReplacement ShortString_Mid_OneElementCut_MidRange {
+  .initial = "_BCDFGHJ_", .from = 4, .to = 5, .input = "aeiou", .expected = "_BCDaeiouGHJ_"
+};
+
+constexpr TestCaseReplacement ShortString_Mid_OneElementCut_LongRange {
+  .initial = "_BCDFGHJ_", .from = 4, .to = 5,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "_BCDabcdefghijklmnopqrstuvwxyz0123456789GHJ_"
+};
+
+constexpr TestCaseReplacement ShortString_Mid_MidSizedCut_EmptyRange {
+  .initial = "_BCDFGHJ_", .from = 4, .to = 7, .input = "", .expected = "_BCDJ_"
+};
+
+constexpr TestCaseReplacement ShortString_Mid_MidSizedCut_OneElementRange {
+  .initial = "_BCDFGHJ_", .from = 4, .to = 7, .input = "a", .expected = "_BCDaJ_"
+};
+
+constexpr TestCaseReplacement ShortString_Mid_MidSizedCut_MidRange {
+  .initial = "_BCDFGHJ_", .from = 4, .to = 7, .input = "aeiou", .expected = "_BCDaeiouJ_"
+};
+
+constexpr TestCaseReplacement ShortString_Mid_MidSizedCut_LongRange {
+  .initial = "_BCDFGHJ_", .from = 4, .to = 7,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "_BCDabcdefghijklmnopqrstuvwxyz0123456789J_"
+};
+
+constexpr TestCaseReplacement ShortString_Mid_CutToEnd_EmptyRange {
+  .initial = "_BCDFGHJ_", .from = 4, .to = 9, .input = "", .expected = "_BCD"
+};
+
+constexpr TestCaseReplacement ShortString_Mid_CutToEnd_OneElementRange {
+  .initial = "_BCDFGHJ_", .from = 4, .to = 9, .input = "a", .expected = "_BCDa"
+};
+
+constexpr TestCaseReplacement ShortString_Mid_CutToEnd_MidRange {
+  .initial = "_BCDFGHJ_", .from = 4, .to = 9, .input = "aeiou", .expected = "_BCDaeiou"
+};
+
+constexpr TestCaseReplacement ShortString_Mid_CutToEnd_LongRange {
+  .initial = "_BCDFGHJ_", .from = 4, .to = 9,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "_BCDabcdefghijklmnopqrstuvwxyz0123456789"
+};
+
+// Replace at the end.
+
+constexpr TestCaseReplacement ShortString_End_EmptyCut_EmptyRange {
+  .initial = "_BCDFGHJ_", .from = 9, .to = 9, .input = "", .expected = "_BCDFGHJ_"
+};
+
+constexpr TestCaseReplacement ShortString_End_EmptyCut_OneElementRange {
+  .initial = "_BCDFGHJ_", .from = 9, .to = 9, .input = "a", .expected = "_BCDFGHJ_a"
+};
+
+constexpr TestCaseReplacement ShortString_End_EmptyCut_MidRange {
+  .initial = "_BCDFGHJ_", .from = 9, .to = 9, .input = "aeiou", .expected = "_BCDFGHJ_aeiou"
+};
+
+constexpr TestCaseReplacement ShortString_End_EmptyCut_LongRange {
+  .initial = "_BCDFGHJ_", .from = 9, .to = 9,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "_BCDFGHJ_abcdefghijklmnopqrstuvwxyz0123456789"
+};
+
+// Long string (no SSO).
+
+// Replace at the beginning.
+
+constexpr TestCaseReplacement LongString_Begin_EmptyCut_EmptyRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 0, .to = 0, .input = "",
+  .expected = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Begin_EmptyCut_OneElementRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 0, .to = 0, .input = "a",
+  .expected = "a_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Begin_EmptyCut_MidRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 0, .to = 0, .input = "aeiou",
+  .expected = "aeiou_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Begin_EmptyCut_LongRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 0, .to = 0,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "abcdefghijklmnopqrstuvwxyz0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Begin_EmptyCut_LongerThanCapacityRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 0, .to = 0,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789_"
+           "abcdefghijklmnopqrstuvwxyz0123456789_"
+           "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "abcdefghijklmnopqrstuvwxyz0123456789_"
+              "abcdefghijklmnopqrstuvwxyz0123456789_"
+              "abcdefghijklmnopqrstuvwxyz0123456789"
+              "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Begin_OneElementCut_EmptyRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 0, .to = 1, .input = "",
+  .expected = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Begin_OneElementCut_OneElementRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 0, .to = 1, .input = "a",
+  .expected = "aABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Begin_OneElementCut_MidRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 0, .to = 1, .input = "aeiou",
+  .expected = "aeiouABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Begin_OneElementCut_LongRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 0, .to = 1,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Begin_OneElementCut_LongerThanCapacityRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 0, .to = 1,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789_"
+           "abcdefghijklmnopqrstuvwxyz0123456789_"
+           "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "abcdefghijklmnopqrstuvwxyz0123456789_"
+              "abcdefghijklmnopqrstuvwxyz0123456789_"
+              "abcdefghijklmnopqrstuvwxyz0123456789"
+              "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Begin_MidSizedCut_EmptyRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 0, .to = 3, .input = "",
+  .expected = "CDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Begin_MidSizedCut_OneElementRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 0, .to = 3, .input = "a",
+  .expected = "aCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Begin_MidSizedCut_MidRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 0, .to = 3, .input = "aeiou",
+  .expected = "aeiouCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Begin_MidSizedCut_LongRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 0, .to = 3,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "abcdefghijklmnopqrstuvwxyz0123456789CDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Begin_MidSizedCut_LongerThanCapacityRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 0, .to = 3,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789_"
+           "abcdefghijklmnopqrstuvwxyz0123456789_"
+           "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "abcdefghijklmnopqrstuvwxyz0123456789_"
+              "abcdefghijklmnopqrstuvwxyz0123456789_"
+              "abcdefghijklmnopqrstuvwxyz0123456789"
+              "CDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Begin_CutToEnd_EmptyRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 0, .to = 38, .input = "",
+  .expected = ""
+};
+
+constexpr TestCaseReplacement LongString_Begin_CutToEnd_OneElementRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 0, .to = 38, .input = "a",
+  .expected = "a"
+};
+
+constexpr TestCaseReplacement LongString_Begin_CutToEnd_MidRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 0, .to = 38, .input = "aeiou",
+  .expected = "aeiou"
+};
+
+constexpr TestCaseReplacement LongString_Begin_CutToEnd_LongRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 0, .to = 38,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "abcdefghijklmnopqrstuvwxyz0123456789"
+};
+
+constexpr TestCaseReplacement LongString_Begin_CutToEnd_LongerThanCapacityRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 0, .to = 38,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789_"
+           "abcdefghijklmnopqrstuvwxyz0123456789_"
+           "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "abcdefghijklmnopqrstuvwxyz0123456789_"
+              "abcdefghijklmnopqrstuvwxyz0123456789_"
+              "abcdefghijklmnopqrstuvwxyz0123456789"
+};
+
+// Replace in the middle.
+
+constexpr TestCaseReplacement LongString_Mid_EmptyCut_EmptyRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 18, .to = 18, .input = "",
+  .expected = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Mid_EmptyCut_OneElementRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 18, .to = 18, .input = "a",
+  .expected = "_ABCDEFGHIJKLMNOPQaRSTUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Mid_EmptyCut_MidRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 18, .to = 18, .input = "aeiou",
+  .expected = "_ABCDEFGHIJKLMNOPQaeiouRSTUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Mid_EmptyCut_LongRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 18, .to = 18,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "_ABCDEFGHIJKLMNOPQabcdefghijklmnopqrstuvwxyz0123456789RSTUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Mid_EmptyCut_LongerThanCapacityRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 18, .to = 18,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789_"
+           "abcdefghijklmnopqrstuvwxyz0123456789_"
+           "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "_ABCDEFGHIJKLMNOPQ"
+              "abcdefghijklmnopqrstuvwxyz0123456789_"
+              "abcdefghijklmnopqrstuvwxyz0123456789_"
+              "abcdefghijklmnopqrstuvwxyz0123456789"
+              "RSTUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Mid_OneElementCut_EmptyRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 18, .to = 19, .input = "",
+  .expected = "_ABCDEFGHIJKLMNOPQSTUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Mid_OneElementCut_OneElementRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 18, .to = 19, .input = "a",
+  .expected = "_ABCDEFGHIJKLMNOPQaSTUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Mid_OneElementCut_MidRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 18, .to = 19, .input = "aeiou",
+  .expected = "_ABCDEFGHIJKLMNOPQaeiouSTUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Mid_OneElementCut_LongRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 18, .to = 19,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "_ABCDEFGHIJKLMNOPQabcdefghijklmnopqrstuvwxyz0123456789STUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Mid_OneElementCut_LongerThanCapacityRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 18, .to = 19,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789_"
+           "abcdefghijklmnopqrstuvwxyz0123456789_"
+           "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "_ABCDEFGHIJKLMNOPQ"
+              "abcdefghijklmnopqrstuvwxyz0123456789_"
+              "abcdefghijklmnopqrstuvwxyz0123456789_"
+              "abcdefghijklmnopqrstuvwxyz0123456789"
+              "STUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Mid_MidSizedCut_EmptyRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 18, .to = 21, .input = "",
+  .expected = "_ABCDEFGHIJKLMNOPQUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Mid_MidSizedCut_OneElementRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 18, .to = 21, .input = "a",
+  .expected = "_ABCDEFGHIJKLMNOPQaUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Mid_MidSizedCut_MidRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 18, .to = 21, .input = "aeiou",
+  .expected = "_ABCDEFGHIJKLMNOPQaeiouUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Mid_MidSizedCut_LongRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 18, .to = 21,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "_ABCDEFGHIJKLMNOPQabcdefghijklmnopqrstuvwxyz0123456789UVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Mid_MidSizedCut_LongerThanCapacityRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 18, .to = 21,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789_"
+           "abcdefghijklmnopqrstuvwxyz0123456789_"
+           "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "_ABCDEFGHIJKLMNOPQ"
+              "abcdefghijklmnopqrstuvwxyz0123456789_"
+              "abcdefghijklmnopqrstuvwxyz0123456789_"
+              "abcdefghijklmnopqrstuvwxyz0123456789"
+              "UVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_Mid_CutToEnd_EmptyRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 18, .to = 38, .input = "",
+  .expected = "_ABCDEFGHIJKLMNOPQ"
+};
+
+constexpr TestCaseReplacement LongString_Mid_CutToEnd_OneElementRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 18, .to = 38, .input = "a",
+  .expected = "_ABCDEFGHIJKLMNOPQa"
+};
+
+constexpr TestCaseReplacement LongString_Mid_CutToEnd_MidRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 18, .to = 38, .input = "aeiou",
+  .expected = "_ABCDEFGHIJKLMNOPQaeiou"
+};
+
+constexpr TestCaseReplacement LongString_Mid_CutToEnd_LongRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 18, .to = 38,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "_ABCDEFGHIJKLMNOPQabcdefghijklmnopqrstuvwxyz0123456789"
+};
+
+constexpr TestCaseReplacement LongString_Mid_CutToEnd_LongerThanCapacityRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 18, .to = 38,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789_"
+           "abcdefghijklmnopqrstuvwxyz0123456789_"
+           "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "_ABCDEFGHIJKLMNOPQ"
+              "abcdefghijklmnopqrstuvwxyz0123456789_"
+              "abcdefghijklmnopqrstuvwxyz0123456789_"
+              "abcdefghijklmnopqrstuvwxyz0123456789"
+};
+
+// Replace at the end.
+
+constexpr TestCaseReplacement LongString_End_EmptyCut_EmptyRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 38, .to = 38, .input = "",
+  .expected = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
+};
+
+constexpr TestCaseReplacement LongString_End_EmptyCut_OneElementRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 38, .to = 38, .input = "a",
+  .expected = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_a"
+};
+
+constexpr TestCaseReplacement LongString_End_EmptyCut_MidRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 38, .to = 38, .input = "aeiou",
+  .expected = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_aeiou"
+};
+
+constexpr TestCaseReplacement LongString_End_EmptyCut_LongRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 38, .to = 38,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_abcdefghijklmnopqrstuvwxyz0123456789"
+};
+
+constexpr TestCaseReplacement LongString_End_EmptyCut_LongerThanCapacityRange {
+  .initial = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", .from = 38, .to = 38,
+  .input = "abcdefghijklmnopqrstuvwxyz0123456789_"
+           "abcdefghijklmnopqrstuvwxyz0123456789_"
+           "abcdefghijklmnopqrstuvwxyz0123456789",
+  .expected = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
+              "abcdefghijklmnopqrstuvwxyz0123456789_"
+              "abcdefghijklmnopqrstuvwxyz0123456789_"
+              "abcdefghijklmnopqrstuvwxyz0123456789"
+};
+
+template <class Iter, class Sent, class Alloc>
+constexpr void test_string_replace_with_range() {
+  auto test = [&](const TestCaseReplacement& test_case) {
+    using Container = std::basic_string<char, std::char_traits<char>, Alloc>;
+
+    auto get_pos = [](auto& c, auto index) { return std::ranges::next(c.begin(), index); };
+    Container c(test_case.initial.begin(), test_case.initial.end());
+    auto in = wrap_input<Iter, Sent>(test_case.input);
+    auto from = get_pos(c, test_case.from);
+    auto to = get_pos(c, test_case.to);
+
+    Container& result = c.replace_with_range(from, to, in);
+    assert(&result == &c);
+    LIBCPP_ASSERT(c.__invariants());
+    return std::ranges::equal(c, test_case.expected);
+  };
+
+  { // Empty string.
+    // empty_str.replace_with_range(end, end, empty_range)
+    assert(test(EmptyString_End_EmptyCut_EmptyRange));
+    // empty_str.replace_with_range(end, end, one_element_range)
+    assert(test(EmptyString_End_EmptyCut_OneElementRange));
+    // empty_str.replace_with_range(end, end, mid_range)
+    assert(test(EmptyString_End_EmptyCut_MidRange));
+    // empty_str.replace_with_range(end, end, long_range)
+    assert(test(EmptyString_End_EmptyCut_LongRange));
+  }
+
+  { // One-element string.
+    // one_element_str.replace_with_range(begin, begin, empty_range)
+    assert(test(OneElementString_Begin_EmptyCut_EmptyRange));
+    // one_element_str.replace_with_range(begin, begin, one_element_range)
+    assert(test(OneElementString_Begin_EmptyCut_OneElementRange));
+    // one_element_str.replace_with_range(begin, begin, mid_range)
+    assert(test(OneElementString_Begin_EmptyCut_MidRange));
+    // one_element_str.replace_with_range(begin, begin, long_range)
+    assert(test(OneElementString_Begin_EmptyCut_LongRange));
+    // one_element_str.replace_with_range(begin, begin + 1, empty_range)
+    assert(test(OneElementString_Begin_OneElementCut_EmptyRange));
+    // one_element_str.replace_with_range(begin, begin + 1, one_element_range)
+    assert(test(OneElementString_Begin_OneElementCut_OneElementRange));
+    // one_element_str.replace_with_range(begin, begin + 1, mid_range)
+    assert(test(OneElementString_Begin_OneElementCut_MidRange));
+    // one_element_str.replace_with_range(begin, begin + 1, long_range)
+    assert(test(OneElementString_Begin_OneElementCut_LongRange));
+    // one_element_str.replace_with_range(end, end, empty_range)
+    assert(test(OneElementString_End_EmptyCut_EmptyRange));
+    // one_element_str.replace_with_range(end, end, one_element_range)
+    assert(test(OneElementString_End_EmptyCut_OneElementRange));
+    // one_element_str.replace_with_range(end, end, mid_range)
+    assert(test(OneElementString_End_EmptyCut_MidRange));
+    // one_element_str.replace_with_range(end, end, long_range)
+    assert(test(OneElementString_End_EmptyCut_LongRange));
+  }
+
+  { // Short string.
+    // Replace at the beginning.
+
+    // short_str.replace_with_range(begin, begin, empty_range)
+    assert(test(ShortString_Begin_EmptyCut_EmptyRange));
+    // short_str.replace_with_range(begin, begin, one_element_range)
+    assert(test(ShortString_Begin_EmptyCut_OneElementRange));
+    // short_str.replace_with_range(begin, begin, mid_range)
+    assert(test(ShortString_Begin_EmptyCut_MidRange));
+    // short_str.replace_with_range(begin, begin, long_range)
+    assert(test(ShortString_Begin_EmptyCut_LongRange));
+    // short_str.replace_with_range(begin, begin + 1, empty_range)
+    assert(test(ShortString_Begin_OneElementCut_EmptyRange));
+    // short_str.replace_with_range(begin, begin + 1, one_element_range)
+    assert(test(ShortString_Begin_OneElementCut_OneElementRange));
+    // short_str.replace_with_range(begin, begin + 1, mid_range)
+    assert(test(ShortString_Begin_OneElementCut_MidRange));
+    // short_str.replace_with_range(begin, begin + 1, long_range)
+    assert(test(ShortString_Begin_OneElementCut_LongRange));
+    // short_str.replace_with_range(begin, begin + 3, empty_range)
+    assert(test(ShortString_Begin_MidSizedCut_EmptyRange));
+    // short_str.replace_with_range(begin, begin + 3, one_element_range)
+    assert(test(ShortString_Begin_MidSizedCut_OneElementRange));
+    // short_str.replace_with_range(begin, begin + 3, mid_range)
+    assert(test(ShortString_Begin_MidSizedCut_MidRange));
+    // short_str.replace_with_range(begin, begin + 3, long_range)
+    assert(test(ShortString_Begin_MidSizedCut_LongRange));
+    // short_str.replace_with_range(begin, end, empty_range)
+    assert(test(ShortString_Begin_CutToEnd_EmptyRange));
+    // short_str.replace_with_range(begin, end, one_element_range)
+    assert(test(ShortString_Begin_CutToEnd_OneElementRange));
+    // short_str.replace_with_range(begin, end, mid_range)
+    assert(test(ShortString_Begin_CutToEnd_MidRange));
+    // short_str.replace_with_range(begin, end, long_range)
+    assert(test(ShortString_Begin_CutToEnd_LongRange));
+
+    // Replace in the middle.
+
+    // short_str.replace_with_range(mid, mid, empty_range)
+    assert(test(ShortString_Mid_EmptyCut_EmptyRange));
+    // short_str.replace_with_range(mid, mid, one_element_range)
+    assert(test(ShortString_Mid_EmptyCut_OneElementRange));
+    // short_str.replace_with_range(mid, mid, mid_range)
+    assert(test(ShortString_Mid_EmptyCut_MidRange));
+    // short_str.replace_with_range(mid, mid, long_range)
+    assert(test(ShortString_Mid_EmptyCut_LongRange));
+    // short_str.replace_with_range(mid, mid + 1, empty_range)
+    assert(test(ShortString_Mid_OneElementCut_EmptyRange));
+    // short_str.replace_with_range(mid, mid + 1, one_element_range)
+    assert(test(ShortString_Mid_OneElementCut_OneElementRange));
+    // short_str.replace_with_range(mid, mid + 1, mid_range)
+    assert(test(ShortString_Mid_OneElementCut_MidRange));
+    // short_str.replace_with_range(mid, mid + 1, long_range)
+    assert(test(ShortString_Mid_OneElementCut_LongRange));
+    // short_str.replace_with_range(mid, mid + 3, empty_range)
+    assert(test(ShortString_Mid_MidSizedCut_EmptyRange));
+    // short_str.replace_with_range(mid, mid + 3, one_element_range)
+    assert(test(ShortString_Mid_MidSizedCut_OneElementRange));
+    // short_str.replace_with_range(mid, mid + 3, mid_range)
+    assert(test(ShortString_Mid_MidSizedCut_MidRange));
+    // short_str.replace_with_range(mid, mid + 3, long_range)
+    assert(test(ShortString_Mid_MidSizedCut_LongRange));
+    // short_str.replace_with_range(mid, end, empty_range)
+    assert(test(ShortString_Mid_CutToEnd_EmptyRange));
+    // short_str.replace_with_range(mid, end, one_element_range)
+    assert(test(ShortString_Mid_CutToEnd_OneElementRange));
+    // short_str.replace_with_range(mid, end, mid_range)
+    assert(test(ShortString_Mid_CutToEnd_MidRange));
+    // short_str.replace_with_range(mid, end, long_range)
+    assert(test(ShortString_Mid_CutToEnd_LongRange));
+
+    // Replace at the end.
+
+    // short_str.replace_with_range(end, end, empty_range)
+    assert(test(ShortString_End_EmptyCut_EmptyRange));
+    // short_str.replace_with_range(end, end, one_element_range)
+    assert(test(ShortString_End_EmptyCut_OneElementRange));
+    // short_str.replace_with_range(end, end, mid_range)
+    assert(test(ShortString_End_EmptyCut_MidRange));
+    // short_str.replace_with_range(end, end, long_range)
+    assert(test(ShortString_End_EmptyCut_LongRange));
+  }
+
+  { // Long string.
+    // Replace at the beginning.
+
+    // long_str.replace_with_range(begin, begin, empty_range)
+    assert(test(LongString_Begin_EmptyCut_EmptyRange));
+    // long_str.replace_with_range(begin, begin, one_element_range)
+    assert(test(LongString_Begin_EmptyCut_OneElementRange));
+    // long_str.replace_with_range(begin, begin, mid_range)
+    assert(test(LongString_Begin_EmptyCut_MidRange));
+    // long_str.replace_with_range(begin, begin, long_range)
+    assert(test(LongString_Begin_EmptyCut_LongRange));
+    // long_str.replace_with_range(begin, begin, longer_than_capacity_range)
+    assert(test(LongString_Begin_EmptyCut_LongerThanCapacityRange));
+    // long_str.replace_with_range(begin, begin + 1, empty_range)
+    assert(test(LongString_Begin_OneElementCut_EmptyRange));
+    // long_str.replace_with_range(begin, begin + 1, one_element_range)
+    assert(test(LongString_Begin_OneElementCut_OneElementRange));
+    // long_str.replace_with_range(begin, begin + 1, mid_range)
+    assert(test(LongString_Begin_OneElementCut_MidRange));
+    // long_str.replace_with_range(begin, begin + 1, long_range)
+    assert(test(LongString_Begin_OneElementCut_LongRange));
+    // long_str.replace_with_range(begin, begin + 1, longer_than_capacity_range)
+    assert(test(LongString_Begin_OneElementCut_LongerThanCapacityRange));
+    // long_str.replace_with_range(begin, begin + 3, empty_range)
+    assert(test(LongString_Begin_MidSizedCut_EmptyRange));
+    // long_str.replace_with_range(begin, begin + 3, one_element_range)
+    assert(test(LongString_Begin_MidSizedCut_OneElementRange));
+    // long_str.replace_with_range(begin, begin + 3, mid_range)
+    assert(test(LongString_Begin_MidSizedCut_MidRange));
+    // long_str.replace_with_range(begin, begin + 3, long_range)
+    assert(test(LongString_Begin_MidSizedCut_LongRange));
+    // long_str.replace_with_range(begin, begin + 3, longer_than_capacity_range)
+    assert(test(LongString_Begin_MidSizedCut_LongerThanCapacityRange));
+    // long_str.replace_with_range(begin, end, empty_range)
+    assert(test(LongString_Begin_CutToEnd_EmptyRange));
+    // long_str.replace_with_range(begin, end, one_element_range)
+    assert(test(LongString_Begin_CutToEnd_OneElementRange));
+    // long_str.replace_with_range(begin, end, mid_range)
+    assert(test(LongString_Begin_CutToEnd_MidRange));
+    // long_str.replace_with_range(begin, end, long_range)
+    assert(test(LongString_Begin_CutToEnd_LongRange));
+    // long_str.replace_with_range(begin, end, longer_than_capacity_range)
+    assert(test(LongString_Begin_CutToEnd_LongerThanCapacityRange));
+
+    // Replace in the middle.
+
+    // long_str.replace_with_range(mid, mid, empty_range)
+    assert(test(LongString_Mid_EmptyCut_EmptyRange));
+    // long_str.replace_with_range(mid, mid, one_element_range)
+    assert(test(LongString_Mid_EmptyCut_OneElementRange));
+    // long_str.replace_with_range(mid, mid, mid_range)
+    assert(test(LongString_Mid_EmptyCut_MidRange));
+    // long_str.replace_with_range(mid, mid, long_range)
+    assert(test(LongString_Mid_EmptyCut_LongRange));
+    // long_str.replace_with_range(mid, mid, longer_than_capacity_range)
+    assert(test(LongString_Mid_EmptyCut_LongerThanCapacityRange));
+    // long_str.replace_with_range(mid, mid + 1, empty_range)
+    assert(test(LongString_Mid_OneElementCut_EmptyRange));
+    // long_str.replace_with_range(mid, mid + 1, one_element_range)
+    assert(test(LongString_Mid_OneElementCut_OneElementRange));
+    // long_str.replace_with_range(mid, mid + 1, mid_range)
+    assert(test(LongString_Mid_OneElementCut_MidRange));
+    // long_str.replace_with_range(mid, mid + 1, long_range)
+    assert(test(LongString_Mid_OneElementCut_LongRange));
+    // long_str.replace_with_range(mid, mid + 1, longer_than_capacity_range)
+    assert(test(LongString_Mid_OneElementCut_LongerThanCapacityRange));
+    // long_str.replace_with_range(mid, mid + 3, empty_range)
+    assert(test(LongString_Mid_MidSizedCut_EmptyRange));
+    // long_str.replace_with_range(mid, mid + 3, one_element_range)
+    assert(test(LongString_Mid_MidSizedCut_OneElementRange));
+    // long_str.replace_with_range(mid, mid + 3, mid_range)
+    assert(test(LongString_Mid_MidSizedCut_MidRange));
+    // long_str.replace_with_range(mid, mid + 3, long_range)
+    assert(test(LongString_Mid_MidSizedCut_LongRange));
+    // long_str.replace_with_range(mid, mid + 3, longer_than_capacity_range)
+    assert(test(LongString_Mid_MidSizedCut_LongerThanCapacityRange));
+    // long_str.replace_with_range(mid, end, empty_range)
+    assert(test(LongString_Mid_CutToEnd_EmptyRange));
+    // long_str.replace_with_range(mid, end, one_element_range)
+    assert(test(LongString_Mid_CutToEnd_OneElementRange));
+    // long_str.replace_with_range(mid, end, mid_range)
+    assert(test(LongString_Mid_CutToEnd_MidRange));
+    // long_str.replace_with_range(mid, end, long_range)
+    assert(test(LongString_Mid_CutToEnd_LongRange));
+    // long_str.replace_with_range(mid, end, longer_than_capacity_range)
+    assert(test(LongString_Mid_CutToEnd_LongerThanCapacityRange));
+
+    // Replace at the end.
+
+    // long_str.replace_with_range(end, end, empty_range)
+    assert(test(LongString_End_EmptyCut_EmptyRange));
+    // long_str.replace_with_range(end, end, one_element_range)
+    assert(test(LongString_End_EmptyCut_OneElementRange));
+    // long_str.replace_with_range(end, end, mid_range)
+    assert(test(LongString_End_EmptyCut_MidRange));
+    // long_str.replace_with_range(end, end, long_range)
+    assert(test(LongString_End_EmptyCut_LongRange));
+    // long_str.replace_with_range(end, end, longer_than_capacity_range)
+    assert(test(LongString_End_EmptyCut_LongerThanCapacityRange));
+  }
+}
+
+constexpr void test_string_replace_with_range_rvalue_range() {
+  // Make sure that the input range can be passed by both an lvalue and an rvalue reference and regardless of constness.
+
+  { // Lvalue.
+    std::string in = "abc";
+    std::string c = "123";
+    c.replace_with_range(c.begin(), c.end(), in);
+  }
+
+  { // Const lvalue.
+    const std::string in = "abc";
+    std::string c = "123";
+    c.replace_with_range(c.begin(), c.end(), in);
+  }
+
+  { // Rvalue.
+    std::string in = "abc";
+    std::string c = "123";
+    c.replace_with_range(c.begin(), c.end(), std::move(in));
+  }
+
+  { // Const rvalue.
+    const std::string in = "abc";
+    std::string c = "123";
+    c.replace_with_range(c.begin(), c.end(), std::move(in));
+  }
+}
+
+constexpr bool test_constexpr() {
+  for_all_iterators_and_allocators_constexpr<char, const char*>([]<class Iter, class Sent, class Alloc>() {
+    test_string_replace_with_range<Iter, Sent, Alloc>();
+  });
+  test_string_replace_with_range_rvalue_range();
+
+  return true;
+}
+
+// Tested cases:
+// - different kinds of replacements (varying the size of the initial string, the cut point and the cut size, and the
+//   size of the input range);
+// - an exception is thrown when allocating new elements.
+int main(int, char**) {
+  static_assert(test_constraints_replace_with_range());
+
+  for_all_iterators_and_allocators<char, const char*>([]<class Iter, class Sent, class Alloc>() {
+    test_string_replace_with_range<Iter, Sent, Alloc>();
+  });
+  test_string_replace_with_range_rvalue_range();
+  static_assert(test_constexpr());
+
+  // Note: `test_string_replace_with_range_exception_safety_throwing_copy` doesn't apply because copying chars cannot
+  // throw.
+  {
+#if !defined(TEST_HAS_NO_EXCEPTIONS)
+    // Note: the input string must be long enough to prevent SSO, otherwise the allocator won't be used.
+    std::string in(64, 'a');
+
+    try {
+      ThrowingAllocator<char> alloc;
+
+      globalMemCounter.reset();
+      std::basic_string<char, std::char_traits<char>, ThrowingAllocator<char>> c(alloc);
+      c.replace_with_range(c.end(), c.end(), in);
+      assert(false); // The function call above should throw.
+
+    } catch (int) {
+      assert(globalMemCounter.new_called == globalMemCounter.delete_called);
+    }
+#endif
+  }
+
+  return 0;
+}