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_ASSOCIATIVE_CONTAINERS_H |
10 | #define SUPPORT_FROM_RANGE_ASSOCIATIVE_CONTAINERS_H |
11 | |
12 | #include <algorithm> |
13 | #include <cassert> |
14 | #include <cstddef> |
15 | #include <ranges> |
16 | #include <vector> |
17 | #include <utility> |
18 | |
19 | #include "../exception_safety_helpers.h" |
20 | #include "../from_range_helpers.h" |
21 | #include "../test_compare.h" |
22 | #include "MoveOnly.h" |
23 | #include "almost_satisfies_types.h" |
24 | #include "count_new.h" |
25 | #include "test_macros.h" |
26 | |
27 | template <class Container, class Range> |
28 | concept HasFromRangeCtr = requires (Range&& range) { |
29 | Container(std::from_range, std::forward<Range>(range)); |
30 | Container(std::from_range, std::forward<Range>(range), std::less<typename Container::key_type>()); |
31 | Container(std::from_range, std::forward<Range>(range), std::less<typename Container::key_type>(), |
32 | std::allocator<typename Container::value_type>()); |
33 | Container(std::from_range, std::forward<Range>(range), std::allocator<typename Container::value_type>()); |
34 | }; |
35 | |
36 | template <template <class...> class Container, class K, class V, class K2, class V2> |
37 | constexpr bool test_map_constraints() { |
38 | using ValueType = std::pair<const K, V>; |
39 | |
40 | // Input range with the same value type. |
41 | static_assert(HasFromRangeCtr<Container<K, V>, InputRange<ValueType>>); |
42 | // Input range with a convertible value type. |
43 | static_assert(HasFromRangeCtr<Container<K, V>, InputRange<std::pair<const K2, V2>>>); |
44 | // Input range with a non-convertible value type. |
45 | static_assert(!HasFromRangeCtr<Container<K, V>, InputRange<std::pair<const Empty, V>>>); |
46 | static_assert(!HasFromRangeCtr<Container<K, V>, InputRange<std::pair<const K, Empty>>>); |
47 | // Not an input range. |
48 | static_assert(!HasFromRangeCtr<Container<K, V>, InputRangeNotDerivedFromGeneric<ValueType>>); |
49 | |
50 | return true; |
51 | } |
52 | |
53 | template <template <class ...> class Container, |
54 | class K, |
55 | class V, |
56 | class Iter, |
57 | class Sent, |
58 | class Comp, |
59 | class Alloc, |
60 | class ValueType = std::pair<const K, V>> |
61 | void test_associative_map_with_input(std::vector<ValueType>&& input) { |
62 | auto in = wrap_input<Iter, Sent>(input); |
63 | |
64 | { // (range) |
65 | Container<K, V> c(std::from_range, in); |
66 | |
67 | assert(c.size() == static_cast<std::size_t>(std::distance(c.begin(), c.end()))); |
68 | assert(std::ranges::is_permutation(input, c)); |
69 | } |
70 | |
71 | { // (range, comp) |
72 | Comp comp; |
73 | Container<K, V, Comp> c(std::from_range, in, comp); |
74 | |
75 | assert(c.key_comp() == comp); |
76 | assert(c.size() == static_cast<std::size_t>(std::distance(c.begin(), c.end()))); |
77 | assert(std::ranges::is_permutation(input, c)); |
78 | } |
79 | |
80 | { // (range, allocator) |
81 | Alloc alloc; |
82 | Container<K, V, std::less<K>, Alloc> c(std::from_range, in, alloc); |
83 | |
84 | assert(c.get_allocator() == alloc); |
85 | assert(c.size() == static_cast<std::size_t>(std::distance(c.begin(), c.end()))); |
86 | assert(std::ranges::is_permutation(input, c)); |
87 | } |
88 | |
89 | { // (range, comp, allocator) |
90 | Comp comp; |
91 | Alloc alloc; |
92 | Container<K, V, Comp, Alloc> c(std::from_range, in, comp, alloc); |
93 | |
94 | assert(c.key_comp() == comp); |
95 | assert(c.get_allocator() == alloc); |
96 | assert(c.size() == static_cast<std::size_t>(std::distance(c.begin(), c.end()))); |
97 | assert(std::ranges::is_permutation(input, c)); |
98 | } |
99 | } |
100 | |
101 | template <template <class ...> class Container, |
102 | class K, |
103 | class V, |
104 | class Iter, |
105 | class Sent, |
106 | class Comp, |
107 | class Alloc> |
108 | void test_associative_map() { |
109 | auto test_with_input = &test_associative_map_with_input<Container, K, V, Iter, Sent, Comp, Alloc>; |
110 | |
111 | // Normal input. |
112 | test_with_input({{1, 2}, {3, 4}, {5, 6}, {7, 8}, {9, 10}}); |
113 | // Empty input. |
114 | test_with_input({}); |
115 | // Single-element input. |
116 | test_with_input({{1, 2}}); |
117 | } |
118 | |
119 | template <template <class ...> class Container> |
120 | void test_associative_map_move_only() { |
121 | std::pair<const int, MoveOnly> input[5]; |
122 | std::ranges::subrange in(std::move_iterator{input}, std::move_iterator{input + 5}); |
123 | |
124 | [[maybe_unused]] Container<int, MoveOnly> c(std::from_range, in); |
125 | } |
126 | |
127 | template <template <class ...> class Container> |
128 | void test_map_exception_safety_throwing_copy() { |
129 | #if !defined(TEST_HAS_NO_EXCEPTIONS) |
130 | using K = int; |
131 | using V = ThrowingCopy<3>; |
132 | |
133 | V::throwing_enabled = false; |
134 | std::pair<const K, V> in[5] = { |
135 | {1, {}}, {2, {}}, {3, {}}, {4, {}}, {5, {}} |
136 | }; |
137 | V::throwing_enabled = true; |
138 | V::reset(); |
139 | |
140 | try { |
141 | Container<K, V> c(std::from_range, in); |
142 | assert(false); // The constructor call above should throw. |
143 | |
144 | } catch (int) { |
145 | assert(V::created_by_copying == 3); |
146 | assert(V::destroyed == 2); // No destructor call for the partially-constructed element. |
147 | } |
148 | #endif |
149 | } |
150 | |
151 | template <template <class ...> class Container, class K, class V> |
152 | void test_map_exception_safety_throwing_allocator() { |
153 | #if !defined(TEST_HAS_NO_EXCEPTIONS) |
154 | using ValueType = std::pair<const K, V>; |
155 | ValueType in[] = { |
156 | ValueType{K{1}, V{1}} |
157 | }; |
158 | |
159 | try { |
160 | ThrowingAllocator<ValueType> alloc; |
161 | |
162 | globalMemCounter.reset(); |
163 | Container<K, V, test_less<K>, ThrowingAllocator<ValueType>> c(std::from_range, in, alloc); |
164 | assert(false); // The constructor call above should throw. |
165 | |
166 | } catch (int) { |
167 | assert(globalMemCounter.new_called == globalMemCounter.delete_called); |
168 | } |
169 | #endif |
170 | } |
171 | |
172 | template <class Container, class Range> |
173 | concept SetHasFromRangeCtr = requires (Range&& range) { |
174 | Container(std::from_range, std::forward<Range>(range)); |
175 | Container(std::from_range, std::forward<Range>(range), std::less<typename Container::value_type>()); |
176 | Container(std::from_range, std::forward<Range>(range), std::less<typename Container::value_type>(), |
177 | std::allocator<typename Container::value_type>()); |
178 | Container(std::from_range, std::forward<Range>(range), std::allocator<typename Container::value_type>()); |
179 | }; |
180 | |
181 | template <template <class...> class Container, class T, class U> |
182 | constexpr bool test_set_constraints() { |
183 | // Input range with the same value type. |
184 | static_assert(SetHasFromRangeCtr<Container<T>, InputRange<T>>); |
185 | // Input range with a convertible value type. |
186 | static_assert(SetHasFromRangeCtr<Container<T>, InputRange<U>>); |
187 | // Input range with a non-convertible value type. |
188 | static_assert(!SetHasFromRangeCtr<Container<T>, InputRange<Empty>>); |
189 | // Not an input range. |
190 | static_assert(!SetHasFromRangeCtr<Container<T>, InputRangeNotDerivedFromGeneric<T>>); |
191 | |
192 | return true; |
193 | } |
194 | |
195 | template <template <class ...> class Container, |
196 | class T, |
197 | class Iter, |
198 | class Sent, |
199 | class Comp, |
200 | class Alloc> |
201 | void test_associative_set_with_input(std::vector<T>&& input) { |
202 | auto b = Iter(input.data()); |
203 | auto e = Iter(input.data() + input.size()); |
204 | std::ranges::subrange in(std::move(b), Sent(std::move(e))); |
205 | |
206 | { // (range) |
207 | Container<T> c(std::from_range, in); |
208 | |
209 | assert(c.size() == static_cast<std::size_t>(std::distance(c.begin(), c.end()))); |
210 | assert(std::ranges::is_permutation(input, c)); |
211 | } |
212 | |
213 | { // (range, comp) |
214 | Comp comp; |
215 | Container<T, Comp> c(std::from_range, in, comp); |
216 | |
217 | assert(c.key_comp() == comp); |
218 | assert(c.size() == static_cast<std::size_t>(std::distance(c.begin(), c.end()))); |
219 | assert(std::ranges::is_permutation(input, c)); |
220 | } |
221 | |
222 | { // (range, allocator) |
223 | Alloc alloc; |
224 | Container<T, std::less<T>, Alloc> c(std::from_range, in, alloc); |
225 | |
226 | assert(c.get_allocator() == alloc); |
227 | assert(c.size() == static_cast<std::size_t>(std::distance(c.begin(), c.end()))); |
228 | assert(std::ranges::is_permutation(input, c)); |
229 | } |
230 | |
231 | { // (range, comp, allocator) |
232 | Comp comp; |
233 | Alloc alloc; |
234 | Container<T, Comp, Alloc> c(std::from_range, in, comp, alloc); |
235 | |
236 | assert(c.key_comp() == comp); |
237 | assert(c.get_allocator() == alloc); |
238 | assert(c.size() == static_cast<std::size_t>(std::distance(c.begin(), c.end()))); |
239 | assert(std::ranges::is_permutation(input, c)); |
240 | } |
241 | } |
242 | |
243 | template <template <class ...> class Container, |
244 | class T, |
245 | class Iter, |
246 | class Sent, |
247 | class Comp, |
248 | class Alloc> |
249 | void test_associative_set() { |
250 | auto test_with_input = &test_associative_set_with_input<Container, T, Iter, Sent, Comp, Alloc>; |
251 | |
252 | // Normal input. |
253 | test_with_input({0, 5, 12, 7, -1, 8, 26}); |
254 | // Empty input. |
255 | test_with_input({}); |
256 | // Single-element input. |
257 | test_with_input({5}); |
258 | } |
259 | |
260 | template <template <class ...> class Container> |
261 | void test_associative_set_move_only() { |
262 | MoveOnly input[5]; |
263 | std::ranges::subrange in(std::move_iterator{input}, std::move_iterator{input + 5}); |
264 | |
265 | [[maybe_unused]] Container<MoveOnly> c(std::from_range, in); |
266 | } |
267 | |
268 | template <template <class ...> class Container> |
269 | void test_set_exception_safety_throwing_copy() { |
270 | #if !defined(TEST_HAS_NO_EXCEPTIONS) |
271 | using T = ThrowingCopy<3>; |
272 | T::reset(); |
273 | T in[5] = {{1}, {2}, {3}, {4}, {5}}; |
274 | |
275 | try { |
276 | Container<T> c(std::from_range, in); |
277 | assert(false); // The constructor call above should throw. |
278 | |
279 | } catch (int) { |
280 | assert(T::created_by_copying == 3); |
281 | assert(T::destroyed == 2); // No destructor call for the partially-constructed element. |
282 | } |
283 | #endif |
284 | } |
285 | |
286 | template <template <class ...> class Container, class T> |
287 | void test_set_exception_safety_throwing_allocator() { |
288 | #if !defined(TEST_HAS_NO_EXCEPTIONS) |
289 | T in[] = {1, 2}; |
290 | |
291 | try { |
292 | ThrowingAllocator<T> alloc; |
293 | |
294 | globalMemCounter.reset(); |
295 | Container<T, test_less<T>, ThrowingAllocator<T>> c(std::from_range, in, alloc); |
296 | assert(false); // The constructor call above should throw. |
297 | |
298 | } catch (int) { |
299 | assert(globalMemCounter.new_called == globalMemCounter.delete_called); |
300 | } |
301 | #endif |
302 | } |
303 | |
304 | #endif // SUPPORT_FROM_RANGE_ASSOCIATIVE_CONTAINERS_H |
305 | |