xref: /llvm-project/libcxx/test/std/ranges/range.utility/range.utility.conv/to.pass.cpp (revision 7c721999ca81f22a12ff671291ec0b51397d8ba9)
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     auto case_4 = [in, arg1, arg2]<auto InserterChoice, bool CanReserve>() {
396       using C = Container<int, CtrChoice::DefaultCtrAndInsert, InserterChoice, CanReserve>;
397       {
398         [[maybe_unused]] std::same_as<C> decltype(auto) result = std::ranges::to<C>(in);
399 
400         assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert);
401         assert(result.inserter_choice == InserterChoice);
402         assert(std::ranges::equal(result, in));
403 
404         if constexpr (CanReserve) {
405           assert(result.called_reserve);
406         } else {
407           assert(!result.called_reserve);
408         }
409 
410         assert((in | std::ranges::to<C>()) == result);
411         [[maybe_unused]] auto closure = std::ranges::to<C>();
412         assert((in | closure) == result);
413       }
414 
415       { // Extra arguments
416         [[maybe_unused]] std::same_as<C> decltype(auto) result = std::ranges::to<C>(in, arg1, arg2);
417 
418         assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert);
419         assert(result.inserter_choice == InserterChoice);
420         assert(std::ranges::equal(result, in));
421         assert(result.extra_arg1 == arg1);
422         assert(result.extra_arg2 == arg2);
423 
424         if constexpr (CanReserve) {
425           assert(result.called_reserve);
426         } else {
427           assert(!result.called_reserve);
428         }
429 
430         assert((in | std::ranges::to<C>(arg1, arg2)) == result);
431         [[maybe_unused]] auto closure = std::ranges::to<C>(arg1, arg2);
432         assert((in | closure) == result);
433       }
434     };
435 
436     case_4.operator()<InserterChoice::Insert, false>();
437     case_4.operator()<InserterChoice::Insert, true>();
438     case_4.operator()<InserterChoice::Emplace, false>();
439     case_4.operator()<InserterChoice::Emplace, true>();
440     case_4.operator()<InserterChoice::PushBack, false>();
441     case_4.operator()<InserterChoice::PushBack, true>();
442     case_4.operator()<InserterChoice::EmplaceBack, false>();
443     case_4.operator()<InserterChoice::EmplaceBack, true>();
444   }
445 }
446 
447 template <CtrChoice Rank>
448 struct NotARange {
449   using value_type = int;
450 
451   constexpr NotARange(std::ranges::input_range auto&&)
452   requires (Rank >= CtrChoice::DirectCtr)
453   {}
454 
455   constexpr NotARange(std::from_range_t, std::ranges::input_range auto&&)
456   requires (Rank >= CtrChoice::FromRangeT)
457   {}
458 
459   template <class Iter>
460   constexpr NotARange(Iter, Iter)
461   requires (Rank >= CtrChoice::BeginEndPair)
462   {}
463 
464   constexpr NotARange()
465   requires (Rank >= CtrChoice::DefaultCtrAndInsert)
466   = default;
467 
468   constexpr void push_back(int) {}
469 };
470 
471 static_assert(!std::ranges::range<NotARange<CtrChoice::DirectCtr>>);
472 
473 constexpr void test_lwg_3785() {
474   // Test LWG 3785 ("`ranges::to` is over-constrained on the destination type being a range") -- make sure it's possible
475   // to convert the given input range to a non-range type.
476   std::array in = {1, 2, 3, 4, 5};
477 
478   {
479     using C = NotARange<CtrChoice::DirectCtr>;
480     [[maybe_unused]] std::same_as<C> decltype(auto) result = std::ranges::to<C>(in);
481   }
482 
483   {
484     using C = NotARange<CtrChoice::FromRangeT>;
485     [[maybe_unused]] std::same_as<C> decltype(auto) result = std::ranges::to<C>(in);
486   }
487 
488   {
489     using C = NotARange<CtrChoice::BeginEndPair>;
490     [[maybe_unused]] std::same_as<C> decltype(auto) result = std::ranges::to<C>(in);
491   }
492 
493   {
494     using C = NotARange<CtrChoice::DefaultCtrAndInsert>;
495     [[maybe_unused]] std::same_as<C> decltype(auto) result = std::ranges::to<C>(in);
496   }
497 }
498 
499 constexpr void test_recursive() {
500   using C1 = Container<int, CtrChoice::DirectCtr>;
501   using C2 = Container<C1, CtrChoice::FromRangeT>;
502   using C3 = Container<C2, CtrChoice::BeginEndPair>;
503   using C4 = Container<C3, CtrChoice::DefaultCtrAndInsert, InserterChoice::PushBack>;
504   using A1 = std::array<int, 4>;
505   using A2 = std::array<A1, 3>;
506   using A3 = std::array<A2, 2>;
507   using A4 = std::array<A3, 2>;
508 
509   A4 in = {};
510   { // Fill the nested array with incremental values.
511     int x = 0;
512     for (auto& a3 : in) {
513       for (auto& a2 : a3) {
514         for (auto& a1 : a2) {
515           for (int& el : a1) {
516             el = x++;
517           }
518         }
519       }
520     }
521   }
522 
523   std::same_as<C4> decltype(auto) result = std::ranges::to<C4>(in);
524 
525   assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert);
526 
527   int expected_value = 0;
528   for (auto& c3 : result) {
529     assert(c3.ctr_choice == CtrChoice::BeginEndPair);
530 
531     for (auto& c2 : c3) {
532       assert(c2.ctr_choice == CtrChoice::FromRangeT);
533 
534       for (auto& c1 : c2) {
535         assert(c1.ctr_choice == CtrChoice::DirectCtr);
536 
537         for (int el : c1) {
538           assert(el == expected_value);
539           ++expected_value;
540         }
541       }
542     }
543   }
544 
545   assert((in | std::ranges::to<C4>()) == result);
546 
547   // LWG3984: ranges::to's recursion branch may be ill-formed
548   auto in_owning_view = std::views::all(std::move(in));
549   static_assert(!std::ranges::viewable_range<decltype((in_owning_view))>);
550   assert(std::ranges::to<C4>(in_owning_view) == result);
551 }
552 
553 constexpr bool test() {
554   test_constraints();
555   test_ctr_choice_order();
556   test_lwg_3785();
557   test_recursive();
558 
559   return true;
560 }
561 
562 int main(int, char**) {
563   test();
564   static_assert(test());
565 
566   return 0;
567 }
568