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 | // MSVC warning C4244: 'argument': conversion from '_Ty' to 'int', possible loss of data |
10 | // ADDITIONAL_COMPILE_FLAGS(cl-style-warnings): /wd4244 |
11 | |
12 | // UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 |
13 | |
14 | // template<class C, input_range R, class... Args> requires (!view<C>) |
15 | // constexpr C to(R&& r, Args&&... args); // Since C++23 |
16 | |
17 | #include <ranges> |
18 | |
19 | #include <algorithm> |
20 | #include <array> |
21 | #include <cassert> |
22 | #include <vector> |
23 | #include "container.h" |
24 | #include "test_iterators.h" |
25 | #include "test_macros.h" |
26 | #include "test_range.h" |
27 | |
28 | template <class Container, class Range, class... Args> |
29 | concept HasTo = requires (Range&& range, Args ...args) { |
30 | std::ranges::to<Container>(std::forward<Range>(range), std::forward<Args>(args)...); |
31 | }; |
32 | |
33 | struct InputRange { |
34 | int x = 0; |
35 | constexpr cpp20_input_iterator<int*> begin() { |
36 | return cpp20_input_iterator<int*>(&x); |
37 | } |
38 | constexpr sentinel_wrapper<cpp20_input_iterator<int*>> end() { |
39 | return sentinel_wrapper<cpp20_input_iterator<int*>>(begin()); |
40 | } |
41 | }; |
42 | static_assert(std::ranges::input_range<InputRange>); |
43 | |
44 | struct common_cpp20_input_iterator { |
45 | using value_type = int; |
46 | using difference_type = long long; |
47 | using iterator_concept = std::input_iterator_tag; |
48 | // Deliberately not defining `iterator_category` to make sure this class satisfies the `input_iterator` concept but |
49 | // would fail `derived_from<iterator_category, input_iterator_tag>`. |
50 | |
51 | int x = 0; |
52 | |
53 | // Copyable so that it can be used as a sentinel against itself. |
54 | constexpr decltype(auto) operator*() const { return x; } |
55 | constexpr common_cpp20_input_iterator& operator++() { return *this; } |
56 | constexpr void operator++(int) {} |
57 | constexpr friend bool operator==(common_cpp20_input_iterator, common_cpp20_input_iterator) { return true; } |
58 | }; |
59 | static_assert(std::input_iterator<common_cpp20_input_iterator>); |
60 | static_assert(std::sentinel_for<common_cpp20_input_iterator, common_cpp20_input_iterator>); |
61 | template <class T> |
62 | concept HasIteratorCategory = requires { |
63 | typename std::iterator_traits<T>::iterator_category; |
64 | }; |
65 | static_assert(!HasIteratorCategory<common_cpp20_input_iterator>); |
66 | |
67 | struct CommonInputRange { |
68 | int x = 0; |
69 | constexpr common_cpp20_input_iterator begin() { return {}; } |
70 | constexpr common_cpp20_input_iterator end() { return begin(); } |
71 | }; |
72 | static_assert(std::ranges::input_range<CommonInputRange>); |
73 | static_assert(std::ranges::common_range<CommonInputRange>); |
74 | |
75 | struct CommonRange { |
76 | int x = 0; |
77 | constexpr forward_iterator<int*> begin() { |
78 | return forward_iterator<int*>(&x); |
79 | } |
80 | constexpr forward_iterator<int*> end() { |
81 | return begin(); |
82 | } |
83 | }; |
84 | static_assert(std::ranges::input_range<CommonRange>); |
85 | static_assert(std::ranges::common_range<CommonRange>); |
86 | |
87 | struct NonCommonRange { |
88 | int x = 0; |
89 | constexpr forward_iterator<int*> begin() { |
90 | return forward_iterator<int*>(&x); |
91 | } |
92 | constexpr sentinel_wrapper<forward_iterator<int*>> end() { |
93 | return sentinel_wrapper<forward_iterator<int*>>(begin()); |
94 | } |
95 | }; |
96 | static_assert(std::ranges::input_range<NonCommonRange>); |
97 | static_assert(!std::ranges::common_range<NonCommonRange>); |
98 | static_assert(std::derived_from< |
99 | typename std::iterator_traits<std::ranges::iterator_t<NonCommonRange>>::iterator_category, |
100 | std::input_iterator_tag>); |
101 | |
102 | using ContainerT = int; |
103 | static_assert(!std::ranges::view<ContainerT>); |
104 | static_assert(HasTo<ContainerT, InputRange>); |
105 | static_assert(!HasTo<test_view<forward_iterator>, InputRange>); |
106 | |
107 | // Note: it's not possible to check the `input_range` constraint because if it's not satisfied, the pipe adaptor |
108 | // overload hijacks the call (it takes unconstrained variadic arguments). |
109 | |
110 | // Check the exact constraints for each one of the cases inside `ranges::to`. |
111 | |
112 | struct Empty {}; |
113 | |
114 | struct Fallback { |
115 | using value_type = int; |
116 | |
117 | CtrChoice ctr_choice = CtrChoice::Invalid; |
118 | int x = 0; |
119 | |
120 | constexpr Fallback() : ctr_choice(CtrChoice::DefaultCtrAndInsert) {} |
121 | constexpr Fallback(Empty) : ctr_choice(CtrChoice::DefaultCtrAndInsert) {} |
122 | |
123 | constexpr void push_back(value_type) {} |
124 | constexpr value_type* begin() { return &x; } |
125 | constexpr value_type* end() { return &x; } |
126 | std::size_t size() const { return 0; } |
127 | }; |
128 | |
129 | struct CtrDirectOrFallback : Fallback { |
130 | using Fallback::Fallback; |
131 | constexpr CtrDirectOrFallback(InputRange&&, int = 0) { ctr_choice = CtrChoice::DirectCtr; } |
132 | }; |
133 | |
134 | struct CtrFromRangeTOrFallback : Fallback { |
135 | using Fallback::Fallback; |
136 | constexpr CtrFromRangeTOrFallback(std::from_range_t, InputRange&&, int = 0) { ctr_choice = CtrChoice::FromRangeT; } |
137 | }; |
138 | |
139 | struct CtrBeginEndPairOrFallback : Fallback { |
140 | using Fallback::Fallback; |
141 | template <class Iter> |
142 | constexpr CtrBeginEndPairOrFallback(Iter, Iter, int = 0) { ctr_choice = CtrChoice::BeginEndPair; } |
143 | }; |
144 | |
145 | template <bool HasSize> |
146 | struct MaybeSizedRange { |
147 | int x = 0; |
148 | constexpr forward_iterator<int*> begin() { return forward_iterator<int*>(&x); } |
149 | constexpr forward_iterator<int*> end() { return begin(); } |
150 | |
151 | constexpr std::size_t size() const |
152 | requires HasSize { |
153 | return 0; |
154 | } |
155 | }; |
156 | static_assert(std::ranges::sized_range<MaybeSizedRange<true>>); |
157 | static_assert(!std::ranges::sized_range<MaybeSizedRange<false>>); |
158 | |
159 | template <bool HasCapacity = true, bool CapacityReturnsSizeT = true, |
160 | bool HasMaxSize = true, bool MaxSizeReturnsSizeT = true> |
161 | struct Reservable : Fallback { |
162 | bool reserve_called = false; |
163 | |
164 | using Fallback::Fallback; |
165 | |
166 | constexpr std::size_t capacity() const |
167 | requires (HasCapacity && CapacityReturnsSizeT) { |
168 | return 0; |
169 | } |
170 | constexpr int capacity() const |
171 | requires (HasCapacity && !CapacityReturnsSizeT) { |
172 | return 0; |
173 | } |
174 | |
175 | constexpr std::size_t max_size() const |
176 | requires (HasMaxSize && MaxSizeReturnsSizeT) { |
177 | return 0; |
178 | } |
179 | constexpr int max_size() const |
180 | requires (HasMaxSize && !MaxSizeReturnsSizeT) { |
181 | return 0; |
182 | } |
183 | |
184 | constexpr void reserve(std::size_t) { |
185 | reserve_called = true; |
186 | } |
187 | }; |
188 | LIBCPP_STATIC_ASSERT(std::ranges::__reservable_container<Reservable<>>); |
189 | |
190 | constexpr void test_constraints() { |
191 | { // Case 1 -- construct directly from the range. |
192 | { // (range) |
193 | auto result = std::ranges::to<CtrDirectOrFallback>(InputRange()); |
194 | assert(result.ctr_choice == CtrChoice::DirectCtr); |
195 | } |
196 | |
197 | { // (range, arg) |
198 | auto result = std::ranges::to<CtrDirectOrFallback>(InputRange(), 1); |
199 | assert(result.ctr_choice == CtrChoice::DirectCtr); |
200 | } |
201 | |
202 | { // (range, convertible-to-arg) |
203 | auto result = std::ranges::to<CtrDirectOrFallback>(InputRange(), 1.0); |
204 | assert(result.ctr_choice == CtrChoice::DirectCtr); |
205 | } |
206 | |
207 | { // (range, BAD_arg) |
208 | auto result = std::ranges::to<CtrDirectOrFallback>(InputRange(), Empty()); |
209 | assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert); |
210 | } |
211 | } |
212 | |
213 | { // Case 2 -- construct using the `from_range_t` tagged constructor. |
214 | { // (range) |
215 | auto result = std::ranges::to<CtrFromRangeTOrFallback>(InputRange()); |
216 | assert(result.ctr_choice == CtrChoice::FromRangeT); |
217 | } |
218 | |
219 | { // (range, arg) |
220 | auto result = std::ranges::to<CtrFromRangeTOrFallback>(InputRange(), 1); |
221 | assert(result.ctr_choice == CtrChoice::FromRangeT); |
222 | } |
223 | |
224 | { // (range, convertible-to-arg) |
225 | auto result = std::ranges::to<CtrFromRangeTOrFallback>(InputRange(), 1.0); |
226 | assert(result.ctr_choice == CtrChoice::FromRangeT); |
227 | } |
228 | |
229 | { // (range, BAD_arg) |
230 | auto result = std::ranges::to<CtrFromRangeTOrFallback>(InputRange(), Empty()); |
231 | assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert); |
232 | } |
233 | } |
234 | |
235 | { // Case 3 -- construct from a begin-end iterator pair. |
236 | { // (range) |
237 | auto result = std::ranges::to<CtrBeginEndPairOrFallback>(CommonRange()); |
238 | assert(result.ctr_choice == CtrChoice::BeginEndPair); |
239 | } |
240 | |
241 | { // (range, arg) |
242 | auto result = std::ranges::to<CtrBeginEndPairOrFallback>(CommonRange(), 1); |
243 | assert(result.ctr_choice == CtrChoice::BeginEndPair); |
244 | } |
245 | |
246 | { // (range, convertible-to-arg) |
247 | auto result = std::ranges::to<CtrBeginEndPairOrFallback>(CommonRange(), 1.0); |
248 | assert(result.ctr_choice == CtrChoice::BeginEndPair); |
249 | } |
250 | |
251 | { // (BAD_range) -- not a common range. |
252 | auto result = std::ranges::to<CtrBeginEndPairOrFallback>(NonCommonRange()); |
253 | assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert); |
254 | } |
255 | |
256 | { // (BAD_range) -- iterator type not derived from `input_iterator_tag`. |
257 | auto result = std::ranges::to<CtrBeginEndPairOrFallback>(CommonInputRange()); |
258 | assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert); |
259 | } |
260 | |
261 | { // (range, BAD_arg) |
262 | auto result = std::ranges::to<CtrBeginEndPairOrFallback>(CommonRange(), Empty()); |
263 | assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert); |
264 | } |
265 | } |
266 | |
267 | { // Case 4 -- default-construct (or construct from the extra arguments) and insert, reserving the size if possible. |
268 | // Note: it's not possible to check the constraints on the default constructor using this approach because there is |
269 | // nothing to fall back to -- the call will result in a hard error. |
270 | // However, it's possible to check the constraints on reserving the capacity. |
271 | |
272 | { // All constraints satisfied. |
273 | using C = Reservable<>; |
274 | auto result = std::ranges::to<C>(MaybeSizedRange<true>()); |
275 | assert(result.reserve_called); |
276 | } |
277 | |
278 | { // !sized_range |
279 | using C = Reservable<>; |
280 | auto result = std::ranges::to<C>(MaybeSizedRange<false>()); |
281 | assert(!result.reserve_called); |
282 | } |
283 | |
284 | { // Missing `capacity`. |
285 | using C = Reservable</*HasCapacity=*/false>; |
286 | auto result = std::ranges::to<C>(MaybeSizedRange<true>()); |
287 | assert(!result.reserve_called); |
288 | } |
289 | |
290 | { // `capacity` doesn't return `size_type`. |
291 | using C = Reservable</*HasCapacity=*/true, /*CapacityReturnsSizeT=*/false>; |
292 | auto result = std::ranges::to<C>(MaybeSizedRange<true>()); |
293 | assert(!result.reserve_called); |
294 | } |
295 | |
296 | { // Missing `max_size`. |
297 | using C = Reservable</*HasCapacity=*/true, /*CapacityReturnsSizeT=*/true, /*HasMaxSize=*/false>; |
298 | auto result = std::ranges::to<C>(MaybeSizedRange<true>()); |
299 | assert(!result.reserve_called); |
300 | } |
301 | |
302 | { // `max_size` doesn't return `size_type`. |
303 | using C = Reservable< |
304 | /*HasCapacity=*/true, /*CapacityReturnsSizeT=*/true, /*HasMaxSize=*/true, /*MaxSizeReturnsSizeT=*/false>; |
305 | auto result = std::ranges::to<C>(MaybeSizedRange<true>()); |
306 | assert(!result.reserve_called); |
307 | } |
308 | } |
309 | } |
310 | |
311 | constexpr void test_ctr_choice_order() { |
312 | std::array in = {1, 2, 3, 4, 5}; |
313 | int arg1 = 42; |
314 | char arg2 = 'a'; |
315 | |
316 | { // Case 1 -- construct directly from the given range. |
317 | { |
318 | using C = Container<int, CtrChoice::DirectCtr>; |
319 | std::same_as<C> decltype(auto) result = std::ranges::to<C>(in); |
320 | |
321 | assert(result.ctr_choice == CtrChoice::DirectCtr); |
322 | assert(std::ranges::equal(result, in)); |
323 | assert((in | std::ranges::to<C>()) == result); |
324 | auto closure = std::ranges::to<C>(); |
325 | assert((in | closure) == result); |
326 | } |
327 | |
328 | { // Extra arguments. |
329 | using C = Container<int, CtrChoice::DirectCtr>; |
330 | std::same_as<C> decltype(auto) result = std::ranges::to<C>(in, arg1, arg2); |
331 | |
332 | assert(result.ctr_choice == CtrChoice::DirectCtr); |
333 | assert(std::ranges::equal(result, in)); |
334 | assert(result.extra_arg1 == arg1); |
335 | assert(result.extra_arg2 == arg2); |
336 | assert((in | std::ranges::to<C>(arg1, arg2)) == result); |
337 | auto closure = std::ranges::to<C>(arg1, arg2); |
338 | assert((in | closure) == result); |
339 | } |
340 | } |
341 | |
342 | { // Case 2 -- construct using the `from_range_t` tag. |
343 | { |
344 | using C = Container<int, CtrChoice::FromRangeT>; |
345 | std::same_as<C> decltype(auto) result = std::ranges::to<C>(in); |
346 | |
347 | assert(result.ctr_choice == CtrChoice::FromRangeT); |
348 | assert(std::ranges::equal(result, in)); |
349 | assert((in | std::ranges::to<C>()) == result); |
350 | auto closure = std::ranges::to<C>(); |
351 | assert((in | closure) == result); |
352 | } |
353 | |
354 | { // Extra arguments. |
355 | using C = Container<int, CtrChoice::FromRangeT>; |
356 | std::same_as<C> decltype(auto) result = std::ranges::to<C>(in, arg1, arg2); |
357 | |
358 | assert(result.ctr_choice == CtrChoice::FromRangeT); |
359 | assert(std::ranges::equal(result, in)); |
360 | assert(result.extra_arg1 == arg1); |
361 | assert(result.extra_arg2 == arg2); |
362 | assert((in | std::ranges::to<C>(arg1, arg2)) == result); |
363 | auto closure = std::ranges::to<C>(arg1, arg2); |
364 | assert((in | closure) == result); |
365 | } |
366 | } |
367 | |
368 | { // Case 3 -- construct from a begin-end pair. |
369 | { |
370 | using C = Container<int, CtrChoice::BeginEndPair>; |
371 | std::same_as<C> decltype(auto) result = std::ranges::to<C>(in); |
372 | |
373 | assert(result.ctr_choice == CtrChoice::BeginEndPair); |
374 | assert(std::ranges::equal(result, in)); |
375 | assert((in | std::ranges::to<C>()) == result); |
376 | auto closure = std::ranges::to<C>(); |
377 | assert((in | closure) == result); |
378 | } |
379 | |
380 | { // Extra arguments. |
381 | using C = Container<int, CtrChoice::BeginEndPair>; |
382 | std::same_as<C> decltype(auto) result = std::ranges::to<C>(in, arg1, arg2); |
383 | |
384 | assert(result.ctr_choice == CtrChoice::BeginEndPair); |
385 | assert(std::ranges::equal(result, in)); |
386 | assert(result.extra_arg1 == arg1); |
387 | assert(result.extra_arg2 == arg2); |
388 | assert((in | std::ranges::to<C>(arg1, arg2)) == result); |
389 | auto closure = std::ranges::to<C>(arg1, arg2); |
390 | assert((in | closure) == result); |
391 | } |
392 | } |
393 | |
394 | { // Case 4 -- default-construct then insert elements. |
395 | { |
396 | using C = Container<int, CtrChoice::DefaultCtrAndInsert, InserterChoice::Insert, /*CanReserve=*/false>; |
397 | std::same_as<C> decltype(auto) result = std::ranges::to<C>(in); |
398 | |
399 | assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert); |
400 | assert(result.inserter_choice == InserterChoice::Insert); |
401 | assert(std::ranges::equal(result, in)); |
402 | assert(!result.called_reserve); |
403 | assert((in | std::ranges::to<C>()) == result); |
404 | auto closure = std::ranges::to<C>(); |
405 | assert((in | closure) == result); |
406 | } |
407 | |
408 | { |
409 | using C = Container<int, CtrChoice::DefaultCtrAndInsert, InserterChoice::Insert, /*CanReserve=*/true>; |
410 | std::same_as<C> decltype(auto) result = std::ranges::to<C>(in); |
411 | |
412 | assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert); |
413 | assert(result.inserter_choice == InserterChoice::Insert); |
414 | assert(std::ranges::equal(result, in)); |
415 | assert(result.called_reserve); |
416 | assert((in | std::ranges::to<C>()) == result); |
417 | auto closure = std::ranges::to<C>(); |
418 | assert((in | closure) == result); |
419 | } |
420 | |
421 | { |
422 | using C = Container<int, CtrChoice::DefaultCtrAndInsert, InserterChoice::PushBack, /*CanReserve=*/false>; |
423 | std::same_as<C> decltype(auto) result = std::ranges::to<C>(in); |
424 | |
425 | assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert); |
426 | assert(result.inserter_choice == InserterChoice::PushBack); |
427 | assert(std::ranges::equal(result, in)); |
428 | assert(!result.called_reserve); |
429 | assert((in | std::ranges::to<C>()) == result); |
430 | auto closure = std::ranges::to<C>(); |
431 | assert((in | closure) == result); |
432 | } |
433 | |
434 | { |
435 | using C = Container<int, CtrChoice::DefaultCtrAndInsert, InserterChoice::PushBack, /*CanReserve=*/true>; |
436 | std::same_as<C> decltype(auto) result = std::ranges::to<C>(in); |
437 | |
438 | assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert); |
439 | assert(result.inserter_choice == InserterChoice::PushBack); |
440 | assert(std::ranges::equal(result, in)); |
441 | assert(result.called_reserve); |
442 | assert((in | std::ranges::to<C>()) == result); |
443 | auto closure = std::ranges::to<C>(); |
444 | assert((in | closure) == result); |
445 | } |
446 | |
447 | { // Extra arguments. |
448 | using C = Container<int, CtrChoice::DefaultCtrAndInsert, InserterChoice::Insert, /*CanReserve=*/false>; |
449 | std::same_as<C> decltype(auto) result = std::ranges::to<C>(in, arg1, arg2); |
450 | |
451 | assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert); |
452 | assert(result.inserter_choice == InserterChoice::Insert); |
453 | assert(std::ranges::equal(result, in)); |
454 | assert(!result.called_reserve); |
455 | assert(result.extra_arg1 == arg1); |
456 | assert(result.extra_arg2 == arg2); |
457 | assert((in | std::ranges::to<C>(arg1, arg2)) == result); |
458 | auto closure = std::ranges::to<C>(arg1, arg2); |
459 | assert((in | closure) == result); |
460 | } |
461 | } |
462 | } |
463 | |
464 | template <CtrChoice Rank> |
465 | struct NotARange { |
466 | using value_type = int; |
467 | |
468 | constexpr NotARange(std::ranges::input_range auto&&) |
469 | requires (Rank >= CtrChoice::DirectCtr) |
470 | {} |
471 | |
472 | constexpr NotARange(std::from_range_t, std::ranges::input_range auto&&) |
473 | requires (Rank >= CtrChoice::FromRangeT) |
474 | {} |
475 | |
476 | template <class Iter> |
477 | constexpr NotARange(Iter, Iter) |
478 | requires (Rank >= CtrChoice::BeginEndPair) |
479 | {} |
480 | |
481 | constexpr NotARange() |
482 | requires (Rank >= CtrChoice::DefaultCtrAndInsert) |
483 | = default; |
484 | |
485 | constexpr void push_back(int) {} |
486 | }; |
487 | |
488 | static_assert(!std::ranges::range<NotARange<CtrChoice::DirectCtr>>); |
489 | |
490 | constexpr void test_lwg_3785() { |
491 | // Test LWG 3785 ("`ranges::to` is over-constrained on the destination type being a range") -- make sure it's possible |
492 | // to convert the given input range to a non-range type. |
493 | std::array in = {1, 2, 3, 4, 5}; |
494 | |
495 | { |
496 | using C = NotARange<CtrChoice::DirectCtr>; |
497 | [[maybe_unused]] std::same_as<C> decltype(auto) result = std::ranges::to<C>(in); |
498 | } |
499 | |
500 | { |
501 | using C = NotARange<CtrChoice::FromRangeT>; |
502 | [[maybe_unused]] std::same_as<C> decltype(auto) result = std::ranges::to<C>(in); |
503 | } |
504 | |
505 | { |
506 | using C = NotARange<CtrChoice::BeginEndPair>; |
507 | [[maybe_unused]] std::same_as<C> decltype(auto) result = std::ranges::to<C>(in); |
508 | } |
509 | |
510 | { |
511 | using C = NotARange<CtrChoice::DefaultCtrAndInsert>; |
512 | [[maybe_unused]] std::same_as<C> decltype(auto) result = std::ranges::to<C>(in); |
513 | } |
514 | } |
515 | |
516 | constexpr void test_recursive() { |
517 | using C1 = Container<int, CtrChoice::DirectCtr>; |
518 | using C2 = Container<C1, CtrChoice::FromRangeT>; |
519 | using C3 = Container<C2, CtrChoice::BeginEndPair>; |
520 | using C4 = Container<C3, CtrChoice::DefaultCtrAndInsert, InserterChoice::PushBack>; |
521 | using A1 = std::array<int, 4>; |
522 | using A2 = std::array<A1, 3>; |
523 | using A3 = std::array<A2, 2>; |
524 | using A4 = std::array<A3, 2>; |
525 | |
526 | A4 in = {}; |
527 | { // Fill the nested array with incremental values. |
528 | int x = 0; |
529 | for (auto& a3 : in) { |
530 | for (auto& a2 : a3) { |
531 | for (auto& a1 : a2) { |
532 | for (int& el : a1) { |
533 | el = x++; |
534 | } |
535 | } |
536 | } |
537 | } |
538 | } |
539 | |
540 | std::same_as<C4> decltype(auto) result = std::ranges::to<C4>(in); |
541 | |
542 | assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert); |
543 | |
544 | int expected_value = 0; |
545 | for (auto& c3 : result) { |
546 | assert(c3.ctr_choice == CtrChoice::BeginEndPair); |
547 | |
548 | for (auto& c2 : c3) { |
549 | assert(c2.ctr_choice == CtrChoice::FromRangeT); |
550 | |
551 | for (auto& c1 : c2) { |
552 | assert(c1.ctr_choice == CtrChoice::DirectCtr); |
553 | |
554 | for (int el : c1) { |
555 | assert(el == expected_value); |
556 | ++expected_value; |
557 | } |
558 | } |
559 | } |
560 | } |
561 | |
562 | assert((in | std::ranges::to<C4>()) == result); |
563 | } |
564 | |
565 | constexpr bool test() { |
566 | test_constraints(); |
567 | test_ctr_choice_order(); |
568 | test_lwg_3785(); |
569 | test_recursive(); |
570 | |
571 | return true; |
572 | } |
573 | |
574 | int main(int, char**) { |
575 | test(); |
576 | static_assert(test()); |
577 | |
578 | return 0; |
579 | } |
580 | |