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