diff --git a/stl/inc/xstring b/stl/inc/xstring index 715b2437e94..506bf63ced7 100644 --- a/stl/inc/xstring +++ b/stl/inc/xstring @@ -376,7 +376,24 @@ public: _In_reads_(_Count) const _Elem* const _First2, const size_t _Count) noexcept /* strengthened */ { // compare [_First1, _First1 + _Count) with [_First2, ...) #if _HAS_CXX17 +#if _HAS_CXX20 && defined(__EDG__) // TRANSITION, VSO-1601168 + if (_STD is_constant_evaluated()) { + for (size_t _Idx = 0; _Idx != _Count; ++_Idx) { + if (static_cast(_First1[_Idx]) < static_cast(_First2[_Idx])) { + return -1; + } + + if (static_cast(_First2[_Idx]) < static_cast(_First1[_Idx])) { + return 1; + } + } + return 0; + } else { + return _CSTD memcmp(_First1, _First2, _Count); + } +#else // ^^^ workaround for EDG / no workaround needed for MSVC and Clang vvv return __builtin_memcmp(_First1, _First2, _Count); +#endif // ^^^ no workaround needed for MSVC and Clang ^^^ #else // _HAS_CXX17 return _CSTD memcmp(_First1, _First2, _Count); #endif // _HAS_CXX17 @@ -606,7 +623,7 @@ constexpr size_t _Traits_find(_In_reads_(_Hay_size) const _Traits_ptr_t<_Traits> // search [_Haystack, _Haystack + _Hay_size) for [_Needle, _Needle + _Needle_size), at/after _Start_at if (_Needle_size > _Hay_size || _Start_at > _Hay_size - _Needle_size) { // xpos cannot exist, report failure - // N4659 24.3.2.7.2 [string.find]/1 says: + // N4910 23.3.3.8 [string.view.find]/3 says: // 1. _Start_at <= xpos // 2. xpos + _Needle_size <= _Hay_size; // therefore: @@ -1200,12 +1217,12 @@ template class basic_string_view { // wrapper for any kind of contiguous character buffer public: static_assert(is_same_v<_Elem, typename _Traits::char_type>, - "Bad char_traits for basic_string_view; " - "N4659 24.4.2 [string.view.template]/1 \"the type traits::char_type shall name the same type as charT.\""); + "Bad char_traits for basic_string_view; N4910 23.3.3.1 [string.view.template.general]/1 " + "\"The program is ill-formed if traits::char_type is not the same type as charT.\""); static_assert(!is_array_v<_Elem> && is_trivial_v<_Elem> && is_standard_layout_v<_Elem>, - "The character type of basic_string_view must be a non-array trivial standard-layout type. See N4861 " - "[strings.general]/1."); + "The character type of basic_string_view must be a non-array trivial standard-layout type. See N4910 " + "23.1 [strings.general]/1."); using traits_type = _Traits; using value_type = _Elem; @@ -1806,7 +1823,7 @@ struct _Get_comparison_category<_Traits, void_t, - "N4878 [string.view.comparison]/4: Mandates: R denotes a comparison category type."); + "N4910 23.3.5 [string.view.comparison]/4: Mandates: R denotes a comparison category type."); }; template @@ -2440,12 +2457,12 @@ private: _MISMATCHED_ALLOCATOR_MESSAGE("basic_string", "T")); static_assert(is_same_v<_Elem, typename _Traits::char_type>, - "N4659 24.3.2.1 [string.require]/3 requires that the supplied " + "N4910 23.4.3.2 [string.require]/3 requires that the supplied " "char_traits character type match the string's character type."); static_assert(!is_array_v<_Elem> && is_trivial_v<_Elem> && is_standard_layout_v<_Elem>, - "The character type of basic_string must be a non-array trivial standard-layout type. See N4861 " - "[strings.general]/1."); + "The character type of basic_string must be a non-array trivial standard-layout type. See N4910 " + "23.1 [strings.general]/1."); public: using traits_type = _Traits; @@ -2474,7 +2491,7 @@ private: // _String_val::_Bx::_Ptr (type is pointer) // _String_val::_Mysize (type is size_type) // _String_val::_Myres (type is size_type) - // N4810 21.1 [strings.general]/1 says _Elem must be trivial standard-layout, so memcpy is safe. + // N4910 23.1 [strings.general]/1 says _Elem must be trivial standard-layout, so memcpy is safe. // We need to ask if pointer is safe to memcpy. // size_type must be an unsigned integral type so memcpy is safe. // We also need to disable memcpy if the user has supplied _Traits, since @@ -2629,6 +2646,19 @@ public: _Right._Mypair._Myval2._Myptr() + _Roff, _Right._Mypair._Myval2._Clamp_suffix_size(_Roff, _Count)); } +#if _HAS_CXX23 + constexpr basic_string(basic_string&& _Right, const size_type _Roff, const _Alloc& _Al = _Alloc()) + : _Mypair(_One_then_variadic_args_t{}, _Al) { // construct from _Right [_Roff, ), potentially move + _Move_construct_from_substr(_Right, _Roff, npos, _Al); + } + + constexpr basic_string( + basic_string&& _Right, const size_type _Roff, const size_type _Count, const _Alloc& _Al = _Alloc()) + : _Mypair(_One_then_variadic_args_t{}, _Al) { // construct from _Right [_Roff, _Roff + _Count), potentially move + _Move_construct_from_substr(_Right, _Roff, _Count, _Al); + } +#endif // _HAS_CXX23 + _CONSTEXPR20 basic_string(_In_reads_(_Count) const _Elem* const _Ptr, _CRT_GUARDOVERFLOW const size_type _Count) : _Mypair(_Zero_then_variadic_args_t{}) { _Construct<_Construct_strategy::_From_ptr>(_Ptr, _Count); @@ -3165,6 +3195,28 @@ private: _Right._Tidy_init(); } +#if _HAS_CXX23 + constexpr void _Move_construct_from_substr( + basic_string& _Right, const size_type _Roff, const size_type _Size_max, const _Alloc& _Al) { + auto& _Right_data = _Right._Mypair._Myval2; + _Right_data._Check_offset(_Roff); + + const auto _Result_size = _Right_data._Clamp_suffix_size(_Roff, _Size_max); + const auto _Right_ptr = _Right_data._Myptr(); + if (_Allocators_equal(_Al, _Right._Getal()) && _Result_size >= _BUF_SIZE) { + if (_Roff != 0) { + _Traits::move(_Right_ptr, _Right_ptr + _Roff, _Result_size); + } + _Right._Eos(_Result_size); + + _Mypair._Myval2._Alloc_proxy(_GET_PROXY_ALLOCATOR(_Alty, _Getal())); + _Take_contents(_Right); + } else { + _Construct<_Construct_strategy::_From_ptr>(_Right_ptr + _Roff, _Result_size); + } + } +#endif // _HAS_CXX23 + public: _CONSTEXPR20 basic_string(initializer_list<_Elem> _Ilist, const _Alloc& _Al = allocator_type()) : _Mypair(_One_then_variadic_args_t{}, _Al) { @@ -4675,10 +4727,23 @@ public: } #endif // _HAS_CXX17 - _NODISCARD _CONSTEXPR20 basic_string substr(const size_type _Off = 0, const size_type _Count = npos) const { - // return [_Off, _Off + _Count) as new string - return basic_string{*this, _Off, _Count, get_allocator()}; + _NODISCARD _CONSTEXPR20 basic_string substr(const size_type _Off = 0, const size_type _Count = npos) +#if _HAS_CXX23 + const& +#else // _HAS_CXX23 + const +#endif // _HAS_CXX23 + { + // return [_Off, _Off + _Count) as new string, default-constructing its allocator + return basic_string{*this, _Off, _Count}; + } + +#if _HAS_CXX23 + _NODISCARD constexpr basic_string substr(const size_type _Off = 0, const size_type _Count = npos) && { + // return [_Off, _Off + _Count) as new string, potentially moving, default-constructing its allocator + return basic_string{_STD move(*this), _Off, _Count}; } +#endif // _HAS_CXX23 _CONSTEXPR20 bool _Equal(const basic_string& _Right) const noexcept { // compare [0, size()) with _Right for equality @@ -5082,10 +5147,10 @@ _NODISCARD _CONSTEXPR20 basic_string<_Elem, _Traits, _Alloc> operator+( basic_string<_Elem, _Traits, _Alloc>&& _Left, basic_string<_Elem, _Traits, _Alloc>&& _Right) { #if _ITERATOR_DEBUG_LEVEL == 2 _STL_VERIFY(_STD addressof(_Left) != _STD addressof(_Right), - "You cannot concatenate the same moved string to itself. See " - "N4849 [res.on.arguments]/1.3: If a function argument binds to an rvalue reference " - "parameter, the implementation may assume that this parameter is a unique reference " - "to this argument"); + "You cannot concatenate the same moved string to itself. See N4910 16.4.5.9 [res.on.arguments]/1.3: " + "If a function argument is bound to an rvalue reference parameter, the implementation may assume that " + "this parameter is a unique reference to this argument, except that the argument passed to " + "a move-assignment operator may be a reference to *this (16.4.6.15 [lib.types.movedfrom])."); #endif // _ITERATOR_DEBUG_LEVEL == 2 return {_String_constructor_concat_tag{}, _Left, _Right}; } diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index d854c5002c6..e418abc221c 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -327,6 +327,7 @@ // (changes to pair, tuple, and vector::reference only) // P2387R3 Pipe Support For User-Defined Range Adaptors // P2417R2 More constexpr bitset +// P2438R2 string::substr() && // P2440R1 ranges::iota, ranges::shift_left, ranges::shift_right // P2441R2 views::join_with // P2442R1 Windowing Range Adaptors: views::chunk, views::slide diff --git a/tests/std/test.lst b/tests/std/test.lst index e1880a3b6ef..f63c48e1b1e 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -213,6 +213,7 @@ tests\GH_002769_handle_deque_block_pointers tests\GH_002789_Hash_vec_Tidy tests\GH_002989_nothrow_unwrappable tests\GH_002992_unwrappable_iter_sent_pairs +tests\GH_003022_substr_allocator tests\LWG2597_complex_branch_cut tests\LWG3018_shared_ptr_function tests\LWG3121_constrained_tuple_forwarding_ctor @@ -551,6 +552,7 @@ tests\P2401R0_conditional_noexcept_for_exchange tests\P2408R5_ranges_iterators_to_classic_algorithms tests\P2415R2_owning_view tests\P2417R2_constexpr_bitset +tests\P2438R2_substr_rvalue tests\P2440R1_ranges_alg_shift_left tests\P2440R1_ranges_alg_shift_right tests\P2440R1_ranges_numeric_iota diff --git a/tests/std/tests/GH_003022_substr_allocator/env.lst b/tests/std/tests/GH_003022_substr_allocator/env.lst new file mode 100644 index 00000000000..19f025bd0e6 --- /dev/null +++ b/tests/std/tests/GH_003022_substr_allocator/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_matrix.lst diff --git a/tests/std/tests/GH_003022_substr_allocator/test.cpp b/tests/std/tests/GH_003022_substr_allocator/test.cpp new file mode 100644 index 00000000000..d6d26b36370 --- /dev/null +++ b/tests/std/tests/GH_003022_substr_allocator/test.cpp @@ -0,0 +1,204 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include + +using namespace std; + +#if _HAS_CXX20 +#define CONSTEXPR20 constexpr +#else // ^^^ _HAS_CXX20 / !_HAS_CXX20 vvv +#define CONSTEXPR20 inline +#endif // ^^^ !_HAS_CXX20 ^^^ + +#define TEST_ASSERT(...) assert((__VA_ARGS__)) + +#ifdef __cpp_char8_t +using char8_type = char8_t; +#else // ^^^ defined(__cpp_char8_t) / !defined(__cpp_char8_t) vvv +using char8_type = unsigned char; +#endif // ^^^ !defined(__cpp_char8_t) ^^^ + +template , int> = 0> +constexpr auto statically_widen_impl( + const char* s, const char8_type*, const char16_t*, const char32_t*, const wchar_t*) noexcept { + return s; +} + +template , int> = 0> +constexpr auto statically_widen_impl( + const char*, const char8_type* s8, const char16_t*, const char32_t*, const wchar_t*) noexcept { + return s8; +} + +template , int> = 0> +constexpr auto statically_widen_impl( + const char*, const char8_type*, const char16_t* s16, const char32_t*, const wchar_t*) noexcept { + return s16; +} + +template , int> = 0> +constexpr auto statically_widen_impl( + const char*, const char8_type*, const char16_t*, const char32_t* s32, const wchar_t*) noexcept { + return s32; +} + +template , int> = 0> +constexpr auto statically_widen_impl( + const char*, const char8_type*, const char16_t*, const char32_t*, const wchar_t* sw) noexcept { + return sw; +} + +#ifdef __cpp_char8_t +#define STATICALLY_WIDEN(CT, S) (statically_widen_impl(S, u8##S, u##S, U##S, L##S)) +#else // ^^^ defined(__cpp_char8_t) / !defined(__cpp_char8_t) vvv +#define STATICALLY_WIDEN(CT, S) (statically_widen_impl(S, nullptr, u##S, U##S, L##S)) +#endif // ^^^ !defined(__cpp_char8_t) ^^^ + +template +class payloaded_allocator { +private: + int payload_ = 0; + +public: + using value_type = T; + using size_type = size_t; + using difference_type = ptrdiff_t; + using propagate_on_container_move_assignment = true_type; + using propagate_on_container_swap = true_type; + + payloaded_allocator() = default; + constexpr explicit payloaded_allocator(int payload) noexcept : payload_{payload} {} + + template + constexpr payloaded_allocator(const payloaded_allocator& other) noexcept : payload_{other.get_payload()} {} + + template + friend constexpr bool operator==(const payloaded_allocator& lhs, const payloaded_allocator& rhs) noexcept { + return lhs.get_payload() == rhs.get_payload(); + } + +#if !_HAS_CXX20 + template + friend constexpr bool operator!=(const payloaded_allocator& lhs, const payloaded_allocator& rhs) noexcept { + return !(lhs == rhs); + } +#endif // !_HAS_CXX20 + + CONSTEXPR20 T* allocate(const size_t n) { + return allocator{}.allocate(n); + } + + CONSTEXPR20 void deallocate(T* const p, const size_t n) { + return allocator{}.deallocate(p, n); + } + + constexpr int get_payload() const noexcept { + return payload_; + } +}; + +template +CONSTEXPR20 bool test_substr_allocator() { + struct str_test_case { + const CharT* ntcts; + size_t offset; + size_t count; + }; + + constexpr str_test_case substring_test_cases[]{ + {STATICALLY_WIDEN(CharT, "nul"), 1, 0}, + {STATICALLY_WIDEN(CharT, "str"), 1, 2}, + {STATICALLY_WIDEN(CharT, "ful"), 0, 3}, + {STATICALLY_WIDEN(CharT, "Empty"), 4, 0}, + {STATICALLY_WIDEN(CharT, "Shorter"), 5, 2}, + {STATICALLY_WIDEN(CharT, "Longer"), 1, 5}, + {STATICALLY_WIDEN(CharT, "FullStr"), 0, 7}, + {STATICALLY_WIDEN(CharT, "Empty substring"), 12, 0}, + {STATICALLY_WIDEN(CharT, "Short substring"), 9, 3}, + {STATICALLY_WIDEN(CharT, "Mid substring"), 7, 5}, + {STATICALLY_WIDEN(CharT, "Longer string"), 3, 9}, + {STATICALLY_WIDEN(CharT, "Maximal SSO len"), 0, 15}, + {STATICALLY_WIDEN(CharT, "Take an empty substring from a long string"), 35, 0}, + {STATICALLY_WIDEN(CharT, "Take a short substring from a long string"), 21, 2}, + {STATICALLY_WIDEN(CharT, "Take some substring from a long string"), 15, 6}, + {STATICALLY_WIDEN(CharT, "Take a longer substring from a long string"), 20, 13}, + {STATICALLY_WIDEN(CharT, "Take a quite longer substring..."), 10, 20}, + {STATICALLY_WIDEN(CharT, "Take the whole long string as a substring"), 0, 41}, + }; + + using string_type = basic_string, payloaded_allocator>; + + payloaded_allocator myator{42}; + + for (const auto& test_case : substring_test_cases) { + string_type full_str{test_case.ntcts, myator}; + + const auto offset = test_case.offset; + const auto count = test_case.count; + + // Test const lvalue overloads + TEST_ASSERT(string_type{full_str, offset, count}.get_allocator().get_payload() == 0); + TEST_ASSERT(string_type{full_str, offset}.get_allocator().get_payload() == 0); + + TEST_ASSERT(full_str.substr(offset, count).get_allocator().get_payload() == 0); + + TEST_ASSERT(string_type{full_str, offset, count, myator}.get_allocator().get_payload() == 42); + TEST_ASSERT(string_type{full_str, offset, myator}.get_allocator().get_payload() == 42); + + // Test non-const rvalue overloads + TEST_ASSERT(string_type{string_type{full_str}, offset, count}.get_allocator().get_payload() == 0); + TEST_ASSERT(string_type{string_type{full_str}, offset}.get_allocator().get_payload() == 0); + + TEST_ASSERT(string_type{full_str}.substr(offset, count).get_allocator().get_payload() == 0); + + TEST_ASSERT(string_type{string_type{full_str}, offset, count, myator}.get_allocator().get_payload() == 42); + TEST_ASSERT(string_type{string_type{full_str}, offset, myator}.get_allocator().get_payload() == 42); + } + + // Also test well-formedness change before/after P2438R2 + struct ambiguator { + string_type str; + + operator const string_type&() { + return str; + } + + operator string_type&&() { + return move(str); + } + }; + +#if _HAS_CXX23 && !defined(__EDG__) // TRANSITION, VSO-1601179 + static_assert(!is_constructible_v, "This should be ambiguous"); +#else // _HAS_CXX23 && !defined(__EDG__) + static_assert(is_constructible_v, "This should be unambiguous"); +#endif // _HAS_CXX23 && !defined(__EDG__) + + return true; +} + +#if _HAS_CXX20 +static_assert(test_substr_allocator()); +#ifdef __cpp_char8_t +static_assert(test_substr_allocator()); +#endif // __cpp_char8_t +static_assert(test_substr_allocator()); +static_assert(test_substr_allocator()); +static_assert(test_substr_allocator()); +#endif // _HAS_CXX20 + +int main() { + (void) test_substr_allocator(); +#ifdef __cpp_char8_t + (void) test_substr_allocator(); +#endif // __cpp_char8_t + (void) test_substr_allocator(); + (void) test_substr_allocator(); + (void) test_substr_allocator(); +} diff --git a/tests/std/tests/P2438R2_substr_rvalue/env.lst b/tests/std/tests/P2438R2_substr_rvalue/env.lst new file mode 100644 index 00000000000..642f530ffad --- /dev/null +++ b/tests/std/tests/P2438R2_substr_rvalue/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_latest_matrix.lst diff --git a/tests/std/tests/P2438R2_substr_rvalue/test.cpp b/tests/std/tests/P2438R2_substr_rvalue/test.cpp new file mode 100644 index 00000000000..772c3e5701d --- /dev/null +++ b/tests/std/tests/P2438R2_substr_rvalue/test.cpp @@ -0,0 +1,235 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include + +#define TEST_ASSERT(...) assert((__VA_ARGS__)) + +using namespace std; + +template +class payloaded_allocator { +private: + int payload_ = 0; + +public: + using value_type = T; + using size_type = size_t; + using difference_type = ptrdiff_t; + using propagate_on_container_move_assignment = true_type; + using propagate_on_container_swap = true_type; + + payloaded_allocator() = default; + constexpr explicit payloaded_allocator(int payload) noexcept : payload_{payload} {} + + template + constexpr payloaded_allocator(const payloaded_allocator& other) noexcept : payload_{other.get_payload()} {} + + template + friend constexpr bool operator==(const payloaded_allocator& lhs, const payloaded_allocator& rhs) noexcept { + return lhs.get_payload() == rhs.get_payload(); + } + + constexpr T* allocate(const size_t n) { + return allocator{}.allocate(n); + } + + constexpr void deallocate(T* const p, const size_t n) { + return allocator{}.deallocate(p, n); + } + + constexpr int get_payload() const noexcept { + return payload_; + } +}; + +template +consteval auto statically_widen_impl(string_view sv, +#ifdef __cpp_char8_t + u8string_view svu8, +#endif // __cpp_char8_t + u16string_view svu16, u32string_view svu32, wstring_view svw) noexcept { + if constexpr (is_same_v) { + return sv; + } +#ifdef __cpp_char8_t + else if constexpr (is_same_v) { + return svu8; + } +#endif // __cpp_char8_t + else if constexpr (is_same_v) { + return svu16; + } else if constexpr (is_same_v) { + return svu32; + } else if constexpr (is_same_v) { + return svw; + } else { + static_assert(!is_same_v, "Invalid character type"); + } +} + +#ifdef __cpp_char8_t +#define STATICALLY_WIDEN(CT, S) (statically_widen_impl(S##sv, u8##S##sv, u##S##sv, U##S##sv, L##S##sv)) +#else // ^^^ defined(__cpp_char8_t) / !defined(__cpp_char8_t) vvv +#define STATICALLY_WIDEN(CT, S) (statically_widen_impl(S##sv, u##S##sv, U##S##sv, L##S##sv)) +#endif // ^^^ !defined(__cpp_char8_t) ^^^ + +template +constexpr bool test_rvalue_substr() { + struct str_test_case { + basic_string_view str_view; + size_t offset; + size_t count; + }; + + constexpr str_test_case substring_test_cases[]{ + {STATICALLY_WIDEN(CharT, "nul"), 1, 0}, + {STATICALLY_WIDEN(CharT, "str"), 1, 2}, + {STATICALLY_WIDEN(CharT, "ful"), 0, 3}, + {STATICALLY_WIDEN(CharT, "Empty"), 4, 0}, + {STATICALLY_WIDEN(CharT, "Shorter"), 5, 2}, + {STATICALLY_WIDEN(CharT, "Longer"), 1, 5}, + {STATICALLY_WIDEN(CharT, "FullStr"), 0, 7}, + {STATICALLY_WIDEN(CharT, "Empty substring"), 12, 0}, + {STATICALLY_WIDEN(CharT, "Short substring"), 9, 3}, + {STATICALLY_WIDEN(CharT, "Mid substring"), 7, 5}, + {STATICALLY_WIDEN(CharT, "Longer string"), 3, 9}, + {STATICALLY_WIDEN(CharT, "Maximal SSO len"), 0, 15}, + {STATICALLY_WIDEN(CharT, "Take an empty substring from a long string"), 35, 0}, + {STATICALLY_WIDEN(CharT, "Take a short substring from a long string"), 21, 2}, + {STATICALLY_WIDEN(CharT, "Take some substring from a long string"), 15, 6}, + {STATICALLY_WIDEN(CharT, "Take a longer substring from a long string"), 20, 13}, + {STATICALLY_WIDEN(CharT, "Take a quite longer substring..."), 10, 20}, + {STATICALLY_WIDEN(CharT, "Take the whole long string as a substring"), 0, 41}, + }; + + for (const auto& [str_view, offset, count] : substring_test_cases) { + using string_type = basic_string; + + const auto substring_view = str_view.substr(offset, count); + const auto suffix_view = str_view.substr(offset); + + // Non-portable implementation details + constexpr size_t sso_buffer_capacity = basic_string{}.capacity(); + + TEST_ASSERT(string_type{string_type{str_view}, offset, count} == substring_view); + TEST_ASSERT(string_type{str_view}.substr(offset, count) == substring_view); + TEST_ASSERT(string_type{string_type{str_view}, offset} == suffix_view); + TEST_ASSERT(string_type{str_view}.substr(offset) == suffix_view); + + // Test non-portable implementation details + { + string_type source_str{str_view}; + const auto old_data = source_str.data(); + string_type dest_str{move(source_str), offset, count}; + + TEST_ASSERT((dest_str.data() == old_data) == (dest_str.size() > sso_buffer_capacity)); + } + + { + string_type source_str{str_view}; + const auto old_data = source_str.data(); + string_type dest_str{move(source_str), offset}; + + TEST_ASSERT((dest_str.data() == old_data) == (dest_str.size() > sso_buffer_capacity)); + } + + { + string_type source_str{str_view}; + const auto old_data = source_str.data(); + auto dest_str = move(source_str).substr(offset, count); + + TEST_ASSERT((dest_str.data() == old_data) == (dest_str.size() > sso_buffer_capacity)); + } + + // Test non-portable implementation details for stateful allocators + + using payloaded_string_type = basic_string, payloaded_allocator>; + + payloaded_allocator non_default_allocator{42}; + { + payloaded_string_type source_str{str_view}; + const auto old_data = source_str.data(); + payloaded_string_type dest_str{move(source_str), offset, count}; + + TEST_ASSERT((dest_str.data() == old_data) == (dest_str.size() > sso_buffer_capacity)); + } + { + payloaded_string_type source_str{str_view, non_default_allocator}; + const auto old_data = source_str.data(); + payloaded_string_type dest_str{move(source_str), offset, count}; + + TEST_ASSERT(dest_str.data() != old_data); + } + { + payloaded_string_type source_str{str_view, non_default_allocator}; + const auto old_data = source_str.data(); + payloaded_string_type dest_str{move(source_str), offset, count, non_default_allocator}; + + TEST_ASSERT((dest_str.data() == old_data) == (dest_str.size() > sso_buffer_capacity)); + } + + { + payloaded_string_type source_str{str_view}; + const auto old_data = source_str.data(); + payloaded_string_type dest_str{move(source_str), offset}; + + TEST_ASSERT((dest_str.data() == old_data) == (dest_str.size() > sso_buffer_capacity)); + } + { + payloaded_string_type source_str{str_view, non_default_allocator}; + const auto old_data = source_str.data(); + payloaded_string_type dest_str{move(source_str), offset}; + + TEST_ASSERT(dest_str.data() != old_data); + } + { + payloaded_string_type source_str{str_view, non_default_allocator}; + const auto old_data = source_str.data(); + payloaded_string_type dest_str{move(source_str), offset, non_default_allocator}; + + TEST_ASSERT((dest_str.data() == old_data) == (dest_str.size() > sso_buffer_capacity)); + } + + { + payloaded_string_type source_str{str_view}; + const auto old_data = source_str.data(); + auto dest_str = move(source_str).substr(offset, count); + + TEST_ASSERT((dest_str.data() == old_data) == (dest_str.size() > sso_buffer_capacity)); + } + { + payloaded_string_type source_str{str_view, non_default_allocator}; + const auto old_data = source_str.data(); + auto dest_str = move(source_str).substr(offset, count); + + TEST_ASSERT(dest_str.data() != old_data); + } + } + + return true; +} + +static_assert(test_rvalue_substr()); +#ifdef __cpp_char8_t +static_assert(test_rvalue_substr()); +#endif // __cpp_char8_t +static_assert(test_rvalue_substr()); +static_assert(test_rvalue_substr()); +static_assert(test_rvalue_substr()); + +int main() { + (void) test_rvalue_substr(); +#ifdef __cpp_char8_t + (void) test_rvalue_substr(); +#endif // __cpp_char8_t + (void) test_rvalue_substr(); + (void) test_rvalue_substr(); + (void) test_rvalue_substr(); +}