568 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8 9// MSVC warning C4244: 'argument': conversion from '_Ty' to 'int', possible loss of data10// ADDITIONAL_COMPILE_FLAGS(cl-style-warnings): /wd424411 12// UNSUPPORTED: c++03, c++11, c++14, c++17, c++2013 14// template<class C, input_range R, class... Args> requires (!view<C>)15// constexpr C to(R&& r, Args&&... args); // Since C++2316 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 28template <class Container, class Range, class... Args>29concept HasTo = requires (Range&& range, Args ...args) {30 std::ranges::to<Container>(std::forward<Range>(range), std::forward<Args>(args)...);31};32 33struct 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};42static_assert(std::ranges::input_range<InputRange>);43 44struct 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 but49 // 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};59static_assert(std::input_iterator<common_cpp20_input_iterator>);60static_assert(std::sentinel_for<common_cpp20_input_iterator, common_cpp20_input_iterator>);61template <class T>62concept HasIteratorCategory = requires {63 typename std::iterator_traits<T>::iterator_category;64};65static_assert(!HasIteratorCategory<common_cpp20_input_iterator>);66 67struct CommonInputRange {68 int x = 0;69 constexpr common_cpp20_input_iterator begin() { return {}; }70 constexpr common_cpp20_input_iterator end() { return begin(); }71};72static_assert(std::ranges::input_range<CommonInputRange>);73static_assert(std::ranges::common_range<CommonInputRange>);74 75struct 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};84static_assert(std::ranges::input_range<CommonRange>);85static_assert(std::ranges::common_range<CommonRange>);86 87struct 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};96static_assert(std::ranges::input_range<NonCommonRange>);97static_assert(!std::ranges::common_range<NonCommonRange>);98static_assert(std::derived_from<99 typename std::iterator_traits<std::ranges::iterator_t<NonCommonRange>>::iterator_category,100 std::input_iterator_tag>);101 102using ContainerT = int;103static_assert(!std::ranges::view<ContainerT>);104static_assert(HasTo<ContainerT, InputRange>);105static_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 adaptor108// 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 112struct Empty {};113 114struct 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 129struct CtrDirectOrFallback : Fallback {130 using Fallback::Fallback;131 constexpr CtrDirectOrFallback(InputRange&&, int = 0) { ctr_choice = CtrChoice::DirectCtr; }132};133 134struct CtrFromRangeTOrFallback : Fallback {135 using Fallback::Fallback;136 constexpr CtrFromRangeTOrFallback(std::from_range_t, InputRange&&, int = 0) { ctr_choice = CtrChoice::FromRangeT; }137};138 139struct CtrBeginEndPairOrFallback : Fallback {140 using Fallback::Fallback;141 template <class Iter>142 constexpr CtrBeginEndPairOrFallback(Iter, Iter, int = 0) { ctr_choice = CtrChoice::BeginEndPair; }143};144 145template <bool HasSize>146struct 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() const152 requires HasSize {153 return 0;154 }155};156static_assert(std::ranges::sized_range<MaybeSizedRange<true>>);157static_assert(!std::ranges::sized_range<MaybeSizedRange<false>>);158 159template <bool HasCapacity = true, bool CapacityReturnsSizeT = true,160 bool HasMaxSize = true, bool MaxSizeReturnsSizeT = true>161struct Reservable : Fallback {162 bool reserve_called = false;163 164 using Fallback::Fallback;165 166 constexpr std::size_t capacity() const167 requires (HasCapacity && CapacityReturnsSizeT) {168 return 0;169 }170 constexpr int capacity() const171 requires (HasCapacity && !CapacityReturnsSizeT) {172 return 0;173 }174 175 constexpr std::size_t max_size() const176 requires (HasMaxSize && MaxSizeReturnsSizeT) {177 return 0;178 }179 constexpr int max_size() const180 requires (HasMaxSize && !MaxSizeReturnsSizeT) {181 return 0;182 }183 184 constexpr void reserve(std::size_t) {185 reserve_called = true;186 }187};188LIBCPP_STATIC_ASSERT(std::ranges::__reservable_container<Reservable<>>);189 190constexpr 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 is269 // 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_range279 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 311constexpr 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 arguments416 [[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 447template <CtrChoice Rank>448struct 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 471static_assert(!std::ranges::range<NotARange<CtrChoice::DirectCtr>>);472 473constexpr void test_lwg_3785() {474 // Test LWG 3785 ("`ranges::to` is over-constrained on the destination type being a range") -- make sure it's possible475 // 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 499constexpr 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-formed548 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 553constexpr bool test() {554 test_constraints();555 test_ctr_choice_order();556 test_lwg_3785();557 test_recursive();558 559 return true;560}561 562int main(int, char**) {563 test();564 static_assert(test());565 566 return 0;567}568