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 | #ifndef SUPPORT_FROM_RANGE_CONTAINER_ADAPTORS_H |
10 | #define SUPPORT_FROM_RANGE_CONTAINER_ADAPTORS_H |
11 | |
12 | #include <algorithm> |
13 | #include <cassert> |
14 | #include <cstddef> |
15 | #include <queue> |
16 | #include <ranges> |
17 | #include <utility> |
18 | #include <vector> |
19 | |
20 | #include "../exception_safety_helpers.h" |
21 | #include "../from_range_helpers.h" |
22 | #include "MoveOnly.h" |
23 | #include "almost_satisfies_types.h" |
24 | #include "count_new.h" |
25 | #include "test_macros.h" |
26 | #include "unwrap_container_adaptor.h" |
27 | |
28 | template <class Container, class Range> |
29 | concept HasFromRangeCtr = requires(Range&& range) { |
30 | Container(std::from_range, std::forward<Range>(range)); |
31 | Container(std::from_range, std::forward<Range>(range), std::allocator<typename Container::value_type>()); |
32 | }; |
33 | |
34 | template <template <class...> class Container, class T, class U> |
35 | constexpr bool test_constraints() { |
36 | // Input range with the same value type. |
37 | static_assert(HasFromRangeCtr<Container<T>, InputRange<T>>); |
38 | // Input range with a convertible value type. |
39 | static_assert(HasFromRangeCtr<Container<T>, InputRange<U>>); |
40 | // Input range with a non-convertible value type. |
41 | static_assert(!HasFromRangeCtr<Container<T>, InputRange<Empty>>); |
42 | // Not an input range. |
43 | static_assert(!HasFromRangeCtr<Container<T>, InputRangeNotDerivedFrom>); |
44 | static_assert(!HasFromRangeCtr<Container<T>, InputRangeNotIndirectlyReadable>); |
45 | static_assert(!HasFromRangeCtr<Container<T>, InputRangeNotInputOrOutputIterator>); |
46 | |
47 | return true; |
48 | } |
49 | |
50 | template <template <class...> class Adaptor, |
51 | template <class...> class UnderlyingContainer, |
52 | class T, |
53 | class Iter, |
54 | class Sent, |
55 | class Alloc> |
56 | constexpr void test_container_adaptor_with_input(std::vector<T>&& input) { |
57 | { // (range) |
58 | std::ranges::subrange in(Iter(input.data()), Sent(Iter(input.data() + input.size()))); |
59 | Adaptor<T> adaptor(std::from_range, in); |
60 | UnwrapAdaptor<Adaptor<T>> unwrap_adaptor(std::move(adaptor)); |
61 | auto& c = unwrap_adaptor.get_container(); |
62 | |
63 | assert(c.size() == static_cast<std::size_t>(std::distance(c.begin(), c.end()))); |
64 | assert(std::ranges::equal(input, c)); |
65 | LIBCPP_ASSERT(c.__invariants()); |
66 | } |
67 | |
68 | { // (range, allocator) |
69 | std::ranges::subrange in(Iter(input.data()), Sent(Iter(input.data() + input.size()))); |
70 | using C = UnderlyingContainer<T, Alloc>; |
71 | Alloc alloc; |
72 | Adaptor<T, C> adaptor(std::from_range, in, alloc); |
73 | UnwrapAdaptor<Adaptor<T, C>> unwrap_adaptor(std::move(adaptor)); |
74 | auto& c = unwrap_adaptor.get_container(); |
75 | |
76 | assert(c.get_allocator() == alloc); |
77 | assert(c.size() == static_cast<std::size_t>(std::distance(c.begin(), c.end()))); |
78 | assert(std::ranges::equal(input, c)); |
79 | LIBCPP_ASSERT(c.__invariants()); |
80 | } |
81 | } |
82 | |
83 | template <template <class...> class UnderlyingContainer, class T, class Iter, class Sent, class Comp, class Alloc> |
84 | constexpr void test_priority_queue_with_input(std::vector<T>&& input) { |
85 | { // (range) |
86 | std::ranges::subrange in(Iter(input.data()), Sent(Iter(input.data() + input.size()))); |
87 | std::priority_queue<T> adaptor(std::from_range, in); |
88 | UnwrapAdaptor<std::priority_queue<T>> unwrap_adaptor(std::move(adaptor)); |
89 | auto& c = unwrap_adaptor.get_container(); |
90 | |
91 | assert(c.size() == static_cast<std::size_t>(std::distance(c.begin(), c.end()))); |
92 | assert(std::ranges::is_permutation(input, c)); |
93 | LIBCPP_ASSERT(c.__invariants()); |
94 | } |
95 | |
96 | { // (range, comp) |
97 | std::ranges::subrange in(Iter(input.data()), Sent(Iter(input.data() + input.size()))); |
98 | using C = UnderlyingContainer<T>; |
99 | Comp comp; |
100 | |
101 | std::priority_queue<T, C, Comp> adaptor(std::from_range, in, comp); |
102 | UnwrapAdaptor<std::priority_queue<T, C, Comp>> unwrap_adaptor(std::move(adaptor)); |
103 | auto& c = unwrap_adaptor.get_container(); |
104 | |
105 | assert(c.size() == static_cast<std::size_t>(std::distance(c.begin(), c.end()))); |
106 | assert(std::ranges::is_permutation(input, c)); |
107 | LIBCPP_ASSERT(c.__invariants()); |
108 | assert(unwrap_adaptor.get_comparator() == comp); |
109 | } |
110 | |
111 | { // (range, allocator) |
112 | std::ranges::subrange in(Iter(input.data()), Sent(Iter(input.data() + input.size()))); |
113 | using C = UnderlyingContainer<T, Alloc>; |
114 | Alloc alloc; |
115 | |
116 | std::priority_queue<T, C> adaptor(std::from_range, in, alloc); |
117 | UnwrapAdaptor<std::priority_queue<T, C>> unwrap_adaptor(std::move(adaptor)); |
118 | auto& c = unwrap_adaptor.get_container(); |
119 | |
120 | assert(c.get_allocator() == alloc); |
121 | assert(c.size() == static_cast<std::size_t>(std::distance(c.begin(), c.end()))); |
122 | assert(std::ranges::is_permutation(input, c)); |
123 | LIBCPP_ASSERT(c.__invariants()); |
124 | } |
125 | |
126 | { // (range, comp, alloc) |
127 | std::ranges::subrange in(Iter(input.data()), Sent(Iter(input.data() + input.size()))); |
128 | using C = UnderlyingContainer<T, Alloc>; |
129 | Comp comp; |
130 | Alloc alloc; |
131 | |
132 | std::priority_queue<T, C, Comp> adaptor(std::from_range, in, comp, alloc); |
133 | UnwrapAdaptor<std::priority_queue<T, C, Comp>> unwrap_adaptor(std::move(adaptor)); |
134 | auto& c = unwrap_adaptor.get_container(); |
135 | |
136 | assert(c.get_allocator() == alloc); |
137 | assert(c.size() == static_cast<std::size_t>(std::distance(c.begin(), c.end()))); |
138 | assert(std::ranges::is_permutation(input, c)); |
139 | LIBCPP_ASSERT(c.__invariants()); |
140 | assert(unwrap_adaptor.get_comparator() == comp); |
141 | } |
142 | } |
143 | |
144 | template <template <class...> class Adaptor, |
145 | template <class...> class UnderlyingContainer, |
146 | class T, |
147 | class Iter, |
148 | class Sent, |
149 | class Alloc> |
150 | constexpr void test_container_adaptor() { |
151 | auto test_with_input = &test_container_adaptor_with_input<Adaptor, UnderlyingContainer, T, Iter, Sent, Alloc>; |
152 | |
153 | // Normal input. |
154 | test_with_input({0, 5, 12, 7, -1, 8, 26}); |
155 | // Empty input. |
156 | test_with_input({}); |
157 | // Single-element input. |
158 | test_with_input({5}); |
159 | } |
160 | |
161 | template <template <class...> class UnderlyingContainer, class T, class Iter, class Sent, class Comp, class Alloc> |
162 | constexpr void test_priority_queue() { |
163 | auto test_with_input = &test_priority_queue_with_input<UnderlyingContainer, T, Iter, Sent, Comp, Alloc>; |
164 | |
165 | // Normal input. |
166 | test_with_input({0, 5, 12, 7, -1, 8, 26}); |
167 | // Empty input. |
168 | test_with_input({}); |
169 | // Single-element input. |
170 | test_with_input({5}); |
171 | } |
172 | |
173 | template <template <class...> class Container> |
174 | constexpr void test_container_adaptor_move_only() { |
175 | MoveOnly input[5]; |
176 | std::ranges::subrange in(std::move_iterator{input}, std::move_iterator{input + 5}); |
177 | |
178 | [[maybe_unused]] Container<MoveOnly> c(std::from_range, in); |
179 | } |
180 | |
181 | template <template <class...> class Adaptor> |
182 | void test_exception_safety_throwing_copy() { |
183 | #if !defined(TEST_HAS_NO_EXCEPTIONS) |
184 | constexpr int ThrowOn = 3; |
185 | using T = ThrowingCopy<ThrowOn>; |
186 | test_exception_safety_throwing_copy<ThrowOn, /*Size=*/5>([](T* from, T* to) { |
187 | [[maybe_unused]] Adaptor<T, std::vector<T>> c(std::from_range, std::ranges::subrange(from, to)); |
188 | }); |
189 | #endif |
190 | } |
191 | |
192 | template <template <class...> class Adaptor, class T> |
193 | void test_exception_safety_throwing_allocator() { |
194 | #if !defined(TEST_HAS_NO_EXCEPTIONS) |
195 | T in[] = {0, 1}; |
196 | |
197 | try { |
198 | using C = std::vector<T, ThrowingAllocator<T>>; |
199 | ThrowingAllocator<T> alloc; |
200 | |
201 | globalMemCounter.reset(); |
202 | Adaptor<T, C> c(std::from_range, in, alloc); |
203 | assert(false); // The constructor call should throw. |
204 | |
205 | } catch (int) { |
206 | assert(globalMemCounter.new_called == globalMemCounter.delete_called); |
207 | } |
208 | #endif |
209 | } |
210 | |
211 | #endif // SUPPORT_FROM_RANGE_CONTAINER_ADAPTORS_H |
212 | |