| 1 | //===----------------------------------------------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | // UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 |
| 10 | |
| 11 | // template<container-compatible-range<charT> R> |
| 12 | // constexpr basic_string(from_range_t, R&& rg, const Allocator& a = Allocator()); // since C++23 |
| 13 | |
| 14 | #include <algorithm> |
| 15 | #include <sstream> |
| 16 | #include <string> |
| 17 | #include <utility> |
| 18 | #include <vector> |
| 19 | |
| 20 | #include "../../../containers/from_range_helpers.h" |
| 21 | #include "../../../containers/sequences/from_range_sequence_containers.h" |
| 22 | #include "test_macros.h" |
| 23 | #include "asan_testing.h" |
| 24 | |
| 25 | template <class Container, class Range, class Alloc> |
| 26 | concept StringHasFromRangeAllocCtr = |
| 27 | requires(Range&& range, const Alloc& alloc) { Container(std::from_range, std::forward<Range>(range), alloc); }; |
| 28 | |
| 29 | constexpr bool test_constraints() { |
| 30 | // (from_range, range) |
| 31 | // |
| 32 | // Input range with the same value type. |
| 33 | static_assert(HasFromRangeCtr<std::string, InputRange<char>>); |
| 34 | // Input range with a convertible value type. |
| 35 | static_assert(HasFromRangeCtr<std::string, InputRange<int>>); |
| 36 | // Input range with a non-convertible value type. |
| 37 | static_assert(!HasFromRangeCtr<std::string, InputRange<Empty>>); |
| 38 | // Not an input range. |
| 39 | static_assert(!HasFromRangeCtr<std::string, InputRangeNotDerivedFrom>); |
| 40 | static_assert(!HasFromRangeCtr<std::string, InputRangeNotIndirectlyReadable>); |
| 41 | static_assert(!HasFromRangeCtr<std::string, InputRangeNotInputOrOutputIterator>); |
| 42 | |
| 43 | // (from_range, range, alloc) |
| 44 | // |
| 45 | // Input range with the same value type. |
| 46 | using Alloc = test_allocator<char>; |
| 47 | using StringWithAlloc = std::basic_string<char, std::char_traits<char>, Alloc>; |
| 48 | static_assert(StringHasFromRangeAllocCtr<StringWithAlloc, InputRange<char>, Alloc>); |
| 49 | // Input range with a convertible value type. |
| 50 | static_assert(StringHasFromRangeAllocCtr<StringWithAlloc, InputRange<int>, Alloc>); |
| 51 | // Input range with a non-convertible value type. |
| 52 | static_assert(!StringHasFromRangeAllocCtr<StringWithAlloc, InputRange<Empty>, Alloc>); |
| 53 | // Not an input range. |
| 54 | static_assert(!StringHasFromRangeAllocCtr<StringWithAlloc, InputRangeNotDerivedFrom, Alloc>); |
| 55 | static_assert(!StringHasFromRangeAllocCtr<StringWithAlloc, InputRangeNotIndirectlyReadable, Alloc>); |
| 56 | static_assert(!StringHasFromRangeAllocCtr<StringWithAlloc, InputRangeNotInputOrOutputIterator, Alloc>); |
| 57 | // Not an allocator. |
| 58 | static_assert(!StringHasFromRangeAllocCtr<StringWithAlloc, InputRange<char>, Empty>); |
| 59 | |
| 60 | return true; |
| 61 | } |
| 62 | |
| 63 | template <class Iter, class Sent, class Alloc> |
| 64 | constexpr void test_with_input(std::vector<char> input) { |
| 65 | { // (range) |
| 66 | std::ranges::subrange in(Iter(input.data()), Sent(Iter(input.data() + input.size()))); |
| 67 | std::string c(std::from_range, in); |
| 68 | |
| 69 | LIBCPP_ASSERT(c.__invariants()); |
| 70 | assert(c.size() == static_cast<std::size_t>(std::distance(c.begin(), c.end()))); |
| 71 | assert(std::ranges::equal(input, c)); |
| 72 | LIBCPP_ASSERT(is_string_asan_correct(c)); |
| 73 | } |
| 74 | |
| 75 | { // (range, allocator) |
| 76 | std::ranges::subrange in(Iter(input.data()), Sent(Iter(input.data() + input.size()))); |
| 77 | Alloc alloc; |
| 78 | std::basic_string<char, std::char_traits<char>, Alloc> c(std::from_range, in, alloc); |
| 79 | |
| 80 | LIBCPP_ASSERT(c.__invariants()); |
| 81 | assert(c.get_allocator() == alloc); |
| 82 | assert(c.size() == static_cast<std::size_t>(std::distance(c.begin(), c.end()))); |
| 83 | assert(std::ranges::equal(input, c)); |
| 84 | LIBCPP_ASSERT(is_string_asan_correct(c)); |
| 85 | } |
| 86 | |
| 87 | { // Ensure input-only sized ranges are accepted. |
| 88 | using input_iter = cpp20_input_iterator<const char*>; |
| 89 | const char in[]{'q', 'w', 'e', 'r'}; |
| 90 | std::string s(std::from_range, std::views::counted(input_iter{std::ranges::begin(in)}, std::ranges::ssize(in))); |
| 91 | assert(s == "qwer" ); |
| 92 | } |
| 93 | } |
| 94 | |
| 95 | void test_string_exception_safety_throwing_allocator() { |
| 96 | #if !defined(TEST_HAS_NO_EXCEPTIONS) |
| 97 | try { |
| 98 | ThrowingAllocator<char> alloc; |
| 99 | |
| 100 | globalMemCounter.reset(); |
| 101 | // Note: the input string must be long enough to prevent SSO, otherwise the allocator won't be used. |
| 102 | std::basic_string<char, std::char_traits<char>, ThrowingAllocator<char>> c( |
| 103 | std::from_range, std::vector<char>(64, 'A'), alloc); |
| 104 | assert(false); // The constructor call should throw. |
| 105 | |
| 106 | } catch (int) { |
| 107 | assert(globalMemCounter.new_called == globalMemCounter.delete_called); |
| 108 | } |
| 109 | #endif |
| 110 | } |
| 111 | |
| 112 | constexpr bool test_inputs() { |
| 113 | for_all_iterators_and_allocators<char>(f: []<class Iter, class Sent, class Alloc>() { |
| 114 | // Shorter input -- SSO. |
| 115 | test_with_input<Iter, Sent, Alloc>({'a', 'b', 'c', 'd', 'e'}); |
| 116 | // Longer input -- no SSO. |
| 117 | test_with_input<Iter, Sent, Alloc>(std::vector<char>(64, 'A')); |
| 118 | // Empty input. |
| 119 | test_with_input<Iter, Sent, Alloc>({}); |
| 120 | // Single-element input. |
| 121 | test_with_input<Iter, Sent, Alloc>({'a'}); |
| 122 | }); |
| 123 | |
| 124 | return true; |
| 125 | } |
| 126 | |
| 127 | #ifndef TEST_HAS_NO_LOCALIZATION |
| 128 | void test_counted_istream_view() { |
| 129 | std::istringstream is{"qwert" }; |
| 130 | auto vals = std::views::istream<char>(is); |
| 131 | std::string s(std::from_range, std::views::counted(vals.begin(), 3)); |
| 132 | assert(s == "qwe" ); |
| 133 | } |
| 134 | #endif |
| 135 | |
| 136 | int main(int, char**) { |
| 137 | test_inputs(); |
| 138 | static_assert(test_inputs()); |
| 139 | |
| 140 | static_assert(test_constraints()); |
| 141 | |
| 142 | // Note: `test_exception_safety_throwing_copy` doesn't apply because copying a `char` cannot throw. |
| 143 | test_string_exception_safety_throwing_allocator(); |
| 144 | |
| 145 | #ifndef TEST_HAS_NO_LOCALIZATION |
| 146 | test_counted_istream_view(); |
| 147 | #endif |
| 148 | |
| 149 | return 0; |
| 150 | } |
| 151 | |