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