//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // MSVC warning C4244: 'argument': conversion from '_Ty' to 'int', possible loss of data // ADDITIONAL_COMPILE_FLAGS(cl-style-warnings): /wd4244 // UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 // template requires (!view) // constexpr C to(R&& r, Args&&... args); // Since C++23 #include #include #include #include #include #include "container.h" #include "test_iterators.h" #include "test_macros.h" #include "test_range.h" template concept HasTo = requires (Range&& range, Args ...args) { std::ranges::to(std::forward(range), std::forward(args)...); }; struct InputRange { int x = 0; constexpr cpp20_input_iterator begin() { return cpp20_input_iterator(&x); } constexpr sentinel_wrapper> end() { return sentinel_wrapper>(begin()); } }; static_assert(std::ranges::input_range); struct common_cpp20_input_iterator { using value_type = int; using difference_type = long long; using iterator_concept = std::input_iterator_tag; // Deliberately not defining `iterator_category` to make sure this class satisfies the `input_iterator` concept but // would fail `derived_from`. int x = 0; // Copyable so that it can be used as a sentinel against itself. constexpr decltype(auto) operator*() const { return x; } constexpr common_cpp20_input_iterator& operator++() { return *this; } constexpr void operator++(int) {} constexpr friend bool operator==(common_cpp20_input_iterator, common_cpp20_input_iterator) { return true; } }; static_assert(std::input_iterator); static_assert(std::sentinel_for); template concept HasIteratorCategory = requires { typename std::iterator_traits::iterator_category; }; static_assert(!HasIteratorCategory); struct CommonInputRange { int x = 0; constexpr common_cpp20_input_iterator begin() { return {}; } constexpr common_cpp20_input_iterator end() { return begin(); } }; static_assert(std::ranges::input_range); static_assert(std::ranges::common_range); struct CommonRange { int x = 0; constexpr forward_iterator begin() { return forward_iterator(&x); } constexpr forward_iterator end() { return begin(); } }; static_assert(std::ranges::input_range); static_assert(std::ranges::common_range); struct NonCommonRange { int x = 0; constexpr forward_iterator begin() { return forward_iterator(&x); } constexpr sentinel_wrapper> end() { return sentinel_wrapper>(begin()); } }; static_assert(std::ranges::input_range); static_assert(!std::ranges::common_range); static_assert(std::derived_from< typename std::iterator_traits>::iterator_category, std::input_iterator_tag>); using ContainerT = int; static_assert(!std::ranges::view); static_assert(HasTo); static_assert(!HasTo, InputRange>); // Note: it's not possible to check the `input_range` constraint because if it's not satisfied, the pipe adaptor // overload hijacks the call (it takes unconstrained variadic arguments). // Check the exact constraints for each one of the cases inside `ranges::to`. struct Empty {}; struct Fallback { using value_type = int; CtrChoice ctr_choice = CtrChoice::Invalid; int x = 0; constexpr Fallback() : ctr_choice(CtrChoice::DefaultCtrAndInsert) {} constexpr Fallback(Empty) : ctr_choice(CtrChoice::DefaultCtrAndInsert) {} constexpr void push_back(value_type) {} constexpr value_type* begin() { return &x; } constexpr value_type* end() { return &x; } std::size_t size() const { return 0; } }; struct CtrDirectOrFallback : Fallback { using Fallback::Fallback; constexpr CtrDirectOrFallback(InputRange&&, int = 0) { ctr_choice = CtrChoice::DirectCtr; } }; struct CtrFromRangeTOrFallback : Fallback { using Fallback::Fallback; constexpr CtrFromRangeTOrFallback(std::from_range_t, InputRange&&, int = 0) { ctr_choice = CtrChoice::FromRangeT; } }; struct CtrBeginEndPairOrFallback : Fallback { using Fallback::Fallback; template constexpr CtrBeginEndPairOrFallback(Iter, Iter, int = 0) { ctr_choice = CtrChoice::BeginEndPair; } }; template struct MaybeSizedRange { int x = 0; constexpr forward_iterator begin() { return forward_iterator(&x); } constexpr forward_iterator end() { return begin(); } constexpr std::size_t size() const requires HasSize { return 0; } }; static_assert(std::ranges::sized_range>); static_assert(!std::ranges::sized_range>); template struct Reservable : Fallback { bool reserve_called = false; using Fallback::Fallback; constexpr std::size_t capacity() const requires (HasCapacity && CapacityReturnsSizeT) { return 0; } constexpr int capacity() const requires (HasCapacity && !CapacityReturnsSizeT) { return 0; } constexpr std::size_t max_size() const requires (HasMaxSize && MaxSizeReturnsSizeT) { return 0; } constexpr int max_size() const requires (HasMaxSize && !MaxSizeReturnsSizeT) { return 0; } constexpr void reserve(std::size_t) { reserve_called = true; } }; LIBCPP_STATIC_ASSERT(std::ranges::__reservable_container>); constexpr void test_constraints() { { // Case 1 -- construct directly from the range. { // (range) auto result = std::ranges::to(InputRange()); assert(result.ctr_choice == CtrChoice::DirectCtr); } { // (range, arg) auto result = std::ranges::to(InputRange(), 1); assert(result.ctr_choice == CtrChoice::DirectCtr); } { // (range, convertible-to-arg) auto result = std::ranges::to(InputRange(), 1.0); assert(result.ctr_choice == CtrChoice::DirectCtr); } { // (range, BAD_arg) auto result = std::ranges::to(InputRange(), Empty()); assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert); } } { // Case 2 -- construct using the `from_range_t` tagged constructor. { // (range) auto result = std::ranges::to(InputRange()); assert(result.ctr_choice == CtrChoice::FromRangeT); } { // (range, arg) auto result = std::ranges::to(InputRange(), 1); assert(result.ctr_choice == CtrChoice::FromRangeT); } { // (range, convertible-to-arg) auto result = std::ranges::to(InputRange(), 1.0); assert(result.ctr_choice == CtrChoice::FromRangeT); } { // (range, BAD_arg) auto result = std::ranges::to(InputRange(), Empty()); assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert); } } { // Case 3 -- construct from a begin-end iterator pair. { // (range) auto result = std::ranges::to(CommonRange()); assert(result.ctr_choice == CtrChoice::BeginEndPair); } { // (range, arg) auto result = std::ranges::to(CommonRange(), 1); assert(result.ctr_choice == CtrChoice::BeginEndPair); } { // (range, convertible-to-arg) auto result = std::ranges::to(CommonRange(), 1.0); assert(result.ctr_choice == CtrChoice::BeginEndPair); } { // (BAD_range) -- not a common range. auto result = std::ranges::to(NonCommonRange()); assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert); } { // (BAD_range) -- iterator type not derived from `input_iterator_tag`. auto result = std::ranges::to(CommonInputRange()); assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert); } { // (range, BAD_arg) auto result = std::ranges::to(CommonRange(), Empty()); assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert); } } { // Case 4 -- default-construct (or construct from the extra arguments) and insert, reserving the size if possible. // Note: it's not possible to check the constraints on the default constructor using this approach because there is // nothing to fall back to -- the call will result in a hard error. // However, it's possible to check the constraints on reserving the capacity. { // All constraints satisfied. using C = Reservable<>; auto result = std::ranges::to(MaybeSizedRange()); assert(result.reserve_called); } { // !sized_range using C = Reservable<>; auto result = std::ranges::to(MaybeSizedRange()); assert(!result.reserve_called); } { // Missing `capacity`. using C = Reservable; auto result = std::ranges::to(MaybeSizedRange()); assert(!result.reserve_called); } { // `capacity` doesn't return `size_type`. using C = Reservable; auto result = std::ranges::to(MaybeSizedRange()); assert(!result.reserve_called); } { // Missing `max_size`. using C = Reservable; auto result = std::ranges::to(MaybeSizedRange()); assert(!result.reserve_called); } { // `max_size` doesn't return `size_type`. using C = Reservable< /*HasCapacity=*/true, /*CapacityReturnsSizeT=*/true, /*HasMaxSize=*/true, /*MaxSizeReturnsSizeT=*/false>; auto result = std::ranges::to(MaybeSizedRange()); assert(!result.reserve_called); } } } constexpr void test_ctr_choice_order() { std::array in = {1, 2, 3, 4, 5}; int arg1 = 42; char arg2 = 'a'; { // Case 1 -- construct directly from the given range. { using C = Container; std::same_as decltype(auto) result = std::ranges::to(in); assert(result.ctr_choice == CtrChoice::DirectCtr); assert(std::ranges::equal(result, in)); assert((in | std::ranges::to()) == result); auto closure = std::ranges::to(); assert((in | closure) == result); } { // Extra arguments. using C = Container; std::same_as decltype(auto) result = std::ranges::to(in, arg1, arg2); assert(result.ctr_choice == CtrChoice::DirectCtr); assert(std::ranges::equal(result, in)); assert(result.extra_arg1 == arg1); assert(result.extra_arg2 == arg2); assert((in | std::ranges::to(arg1, arg2)) == result); auto closure = std::ranges::to(arg1, arg2); assert((in | closure) == result); } } { // Case 2 -- construct using the `from_range_t` tag. { using C = Container; std::same_as decltype(auto) result = std::ranges::to(in); assert(result.ctr_choice == CtrChoice::FromRangeT); assert(std::ranges::equal(result, in)); assert((in | std::ranges::to()) == result); auto closure = std::ranges::to(); assert((in | closure) == result); } { // Extra arguments. using C = Container; std::same_as decltype(auto) result = std::ranges::to(in, arg1, arg2); assert(result.ctr_choice == CtrChoice::FromRangeT); assert(std::ranges::equal(result, in)); assert(result.extra_arg1 == arg1); assert(result.extra_arg2 == arg2); assert((in | std::ranges::to(arg1, arg2)) == result); auto closure = std::ranges::to(arg1, arg2); assert((in | closure) == result); } } { // Case 3 -- construct from a begin-end pair. { using C = Container; std::same_as decltype(auto) result = std::ranges::to(in); assert(result.ctr_choice == CtrChoice::BeginEndPair); assert(std::ranges::equal(result, in)); assert((in | std::ranges::to()) == result); auto closure = std::ranges::to(); assert((in | closure) == result); } { // Extra arguments. using C = Container; std::same_as decltype(auto) result = std::ranges::to(in, arg1, arg2); assert(result.ctr_choice == CtrChoice::BeginEndPair); assert(std::ranges::equal(result, in)); assert(result.extra_arg1 == arg1); assert(result.extra_arg2 == arg2); assert((in | std::ranges::to(arg1, arg2)) == result); auto closure = std::ranges::to(arg1, arg2); assert((in | closure) == result); } } { // Case 4 -- default-construct then insert elements. { using C = Container; std::same_as decltype(auto) result = std::ranges::to(in); assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert); assert(result.inserter_choice == InserterChoice::Insert); assert(std::ranges::equal(result, in)); assert(!result.called_reserve); assert((in | std::ranges::to()) == result); auto closure = std::ranges::to(); assert((in | closure) == result); } { using C = Container; std::same_as decltype(auto) result = std::ranges::to(in); assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert); assert(result.inserter_choice == InserterChoice::Insert); assert(std::ranges::equal(result, in)); assert(result.called_reserve); assert((in | std::ranges::to()) == result); auto closure = std::ranges::to(); assert((in | closure) == result); } { using C = Container; std::same_as decltype(auto) result = std::ranges::to(in); assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert); assert(result.inserter_choice == InserterChoice::PushBack); assert(std::ranges::equal(result, in)); assert(!result.called_reserve); assert((in | std::ranges::to()) == result); auto closure = std::ranges::to(); assert((in | closure) == result); } { using C = Container; std::same_as decltype(auto) result = std::ranges::to(in); assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert); assert(result.inserter_choice == InserterChoice::PushBack); assert(std::ranges::equal(result, in)); assert(result.called_reserve); assert((in | std::ranges::to()) == result); auto closure = std::ranges::to(); assert((in | closure) == result); } { // Extra arguments. using C = Container; std::same_as decltype(auto) result = std::ranges::to(in, arg1, arg2); assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert); assert(result.inserter_choice == InserterChoice::Insert); assert(std::ranges::equal(result, in)); assert(!result.called_reserve); assert(result.extra_arg1 == arg1); assert(result.extra_arg2 == arg2); assert((in | std::ranges::to(arg1, arg2)) == result); auto closure = std::ranges::to(arg1, arg2); assert((in | closure) == result); } } } template struct NotARange { using value_type = int; constexpr NotARange(std::ranges::input_range auto&&) requires (Rank >= CtrChoice::DirectCtr) {} constexpr NotARange(std::from_range_t, std::ranges::input_range auto&&) requires (Rank >= CtrChoice::FromRangeT) {} template constexpr NotARange(Iter, Iter) requires (Rank >= CtrChoice::BeginEndPair) {} constexpr NotARange() requires (Rank >= CtrChoice::DefaultCtrAndInsert) = default; constexpr void push_back(int) {} }; static_assert(!std::ranges::range>); constexpr void test_lwg_3785() { // Test LWG 3785 ("`ranges::to` is over-constrained on the destination type being a range") -- make sure it's possible // to convert the given input range to a non-range type. std::array in = {1, 2, 3, 4, 5}; { using C = NotARange; [[maybe_unused]] std::same_as decltype(auto) result = std::ranges::to(in); } { using C = NotARange; [[maybe_unused]] std::same_as decltype(auto) result = std::ranges::to(in); } { using C = NotARange; [[maybe_unused]] std::same_as decltype(auto) result = std::ranges::to(in); } { using C = NotARange; [[maybe_unused]] std::same_as decltype(auto) result = std::ranges::to(in); } } constexpr void test_recursive() { using C1 = Container; using C2 = Container; using C3 = Container; using C4 = Container; using A1 = std::array; using A2 = std::array; using A3 = std::array; using A4 = std::array; A4 in = {}; { // Fill the nested array with incremental values. int x = 0; for (auto& a3 : in) { for (auto& a2 : a3) { for (auto& a1 : a2) { for (int& el : a1) { el = x++; } } } } } std::same_as decltype(auto) result = std::ranges::to(in); assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert); int expected_value = 0; for (auto& c3 : result) { assert(c3.ctr_choice == CtrChoice::BeginEndPair); for (auto& c2 : c3) { assert(c2.ctr_choice == CtrChoice::FromRangeT); for (auto& c1 : c2) { assert(c1.ctr_choice == CtrChoice::DirectCtr); for (int el : c1) { assert(el == expected_value); ++expected_value; } } } } assert((in | std::ranges::to()) == result); } constexpr bool test() { test_constraints(); test_ctr_choice_order(); test_lwg_3785(); test_recursive(); return true; } int main(int, char**) { test(); static_assert(test()); return 0; }