579 lines
19 KiB
C++
579 lines
19 KiB
C++
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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<class C, input_range R, class... Args> requires (!view<C>)
|
|
// constexpr C to(R&& r, Args&&... args); // Since C++23
|
|
|
|
#include <ranges>
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cassert>
|
|
#include <vector>
|
|
#include "container.h"
|
|
#include "test_iterators.h"
|
|
#include "test_macros.h"
|
|
#include "test_range.h"
|
|
|
|
template <class Container, class Range, class... Args>
|
|
concept HasTo = requires (Range&& range, Args ...args) {
|
|
std::ranges::to<Container>(std::forward<Range>(range), std::forward<Args>(args)...);
|
|
};
|
|
|
|
struct InputRange {
|
|
int x = 0;
|
|
constexpr cpp20_input_iterator<int*> begin() {
|
|
return cpp20_input_iterator<int*>(&x);
|
|
}
|
|
constexpr sentinel_wrapper<cpp20_input_iterator<int*>> end() {
|
|
return sentinel_wrapper<cpp20_input_iterator<int*>>(begin());
|
|
}
|
|
};
|
|
static_assert(std::ranges::input_range<InputRange>);
|
|
|
|
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<iterator_category, input_iterator_tag>`.
|
|
|
|
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<common_cpp20_input_iterator>);
|
|
static_assert(std::sentinel_for<common_cpp20_input_iterator, common_cpp20_input_iterator>);
|
|
template <class T>
|
|
concept HasIteratorCategory = requires {
|
|
typename std::iterator_traits<T>::iterator_category;
|
|
};
|
|
static_assert(!HasIteratorCategory<common_cpp20_input_iterator>);
|
|
|
|
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<CommonInputRange>);
|
|
static_assert(std::ranges::common_range<CommonInputRange>);
|
|
|
|
struct CommonRange {
|
|
int x = 0;
|
|
constexpr forward_iterator<int*> begin() {
|
|
return forward_iterator<int*>(&x);
|
|
}
|
|
constexpr forward_iterator<int*> end() {
|
|
return begin();
|
|
}
|
|
};
|
|
static_assert(std::ranges::input_range<CommonRange>);
|
|
static_assert(std::ranges::common_range<CommonRange>);
|
|
|
|
struct NonCommonRange {
|
|
int x = 0;
|
|
constexpr forward_iterator<int*> begin() {
|
|
return forward_iterator<int*>(&x);
|
|
}
|
|
constexpr sentinel_wrapper<forward_iterator<int*>> end() {
|
|
return sentinel_wrapper<forward_iterator<int*>>(begin());
|
|
}
|
|
};
|
|
static_assert(std::ranges::input_range<NonCommonRange>);
|
|
static_assert(!std::ranges::common_range<NonCommonRange>);
|
|
static_assert(std::derived_from<
|
|
typename std::iterator_traits<std::ranges::iterator_t<NonCommonRange>>::iterator_category,
|
|
std::input_iterator_tag>);
|
|
|
|
using ContainerT = int;
|
|
static_assert(!std::ranges::view<ContainerT>);
|
|
static_assert(HasTo<ContainerT, InputRange>);
|
|
static_assert(!HasTo<test_view<forward_iterator>, 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 <class Iter>
|
|
constexpr CtrBeginEndPairOrFallback(Iter, Iter, int = 0) { ctr_choice = CtrChoice::BeginEndPair; }
|
|
};
|
|
|
|
template <bool HasSize>
|
|
struct MaybeSizedRange {
|
|
int x = 0;
|
|
constexpr forward_iterator<int*> begin() { return forward_iterator<int*>(&x); }
|
|
constexpr forward_iterator<int*> end() { return begin(); }
|
|
|
|
constexpr std::size_t size() const
|
|
requires HasSize {
|
|
return 0;
|
|
}
|
|
};
|
|
static_assert(std::ranges::sized_range<MaybeSizedRange<true>>);
|
|
static_assert(!std::ranges::sized_range<MaybeSizedRange<false>>);
|
|
|
|
template <bool HasCapacity = true, bool CapacityReturnsSizeT = true,
|
|
bool HasMaxSize = true, bool MaxSizeReturnsSizeT = true>
|
|
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<Reservable<>>);
|
|
|
|
constexpr void test_constraints() {
|
|
{ // Case 1 -- construct directly from the range.
|
|
{ // (range)
|
|
auto result = std::ranges::to<CtrDirectOrFallback>(InputRange());
|
|
assert(result.ctr_choice == CtrChoice::DirectCtr);
|
|
}
|
|
|
|
{ // (range, arg)
|
|
auto result = std::ranges::to<CtrDirectOrFallback>(InputRange(), 1);
|
|
assert(result.ctr_choice == CtrChoice::DirectCtr);
|
|
}
|
|
|
|
{ // (range, convertible-to-arg)
|
|
auto result = std::ranges::to<CtrDirectOrFallback>(InputRange(), 1.0);
|
|
assert(result.ctr_choice == CtrChoice::DirectCtr);
|
|
}
|
|
|
|
{ // (range, BAD_arg)
|
|
auto result = std::ranges::to<CtrDirectOrFallback>(InputRange(), Empty());
|
|
assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert);
|
|
}
|
|
}
|
|
|
|
{ // Case 2 -- construct using the `from_range_t` tagged constructor.
|
|
{ // (range)
|
|
auto result = std::ranges::to<CtrFromRangeTOrFallback>(InputRange());
|
|
assert(result.ctr_choice == CtrChoice::FromRangeT);
|
|
}
|
|
|
|
{ // (range, arg)
|
|
auto result = std::ranges::to<CtrFromRangeTOrFallback>(InputRange(), 1);
|
|
assert(result.ctr_choice == CtrChoice::FromRangeT);
|
|
}
|
|
|
|
{ // (range, convertible-to-arg)
|
|
auto result = std::ranges::to<CtrFromRangeTOrFallback>(InputRange(), 1.0);
|
|
assert(result.ctr_choice == CtrChoice::FromRangeT);
|
|
}
|
|
|
|
{ // (range, BAD_arg)
|
|
auto result = std::ranges::to<CtrFromRangeTOrFallback>(InputRange(), Empty());
|
|
assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert);
|
|
}
|
|
}
|
|
|
|
{ // Case 3 -- construct from a begin-end iterator pair.
|
|
{ // (range)
|
|
auto result = std::ranges::to<CtrBeginEndPairOrFallback>(CommonRange());
|
|
assert(result.ctr_choice == CtrChoice::BeginEndPair);
|
|
}
|
|
|
|
{ // (range, arg)
|
|
auto result = std::ranges::to<CtrBeginEndPairOrFallback>(CommonRange(), 1);
|
|
assert(result.ctr_choice == CtrChoice::BeginEndPair);
|
|
}
|
|
|
|
{ // (range, convertible-to-arg)
|
|
auto result = std::ranges::to<CtrBeginEndPairOrFallback>(CommonRange(), 1.0);
|
|
assert(result.ctr_choice == CtrChoice::BeginEndPair);
|
|
}
|
|
|
|
{ // (BAD_range) -- not a common range.
|
|
auto result = std::ranges::to<CtrBeginEndPairOrFallback>(NonCommonRange());
|
|
assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert);
|
|
}
|
|
|
|
{ // (BAD_range) -- iterator type not derived from `input_iterator_tag`.
|
|
auto result = std::ranges::to<CtrBeginEndPairOrFallback>(CommonInputRange());
|
|
assert(result.ctr_choice == CtrChoice::DefaultCtrAndInsert);
|
|
}
|
|
|
|
{ // (range, BAD_arg)
|
|
auto result = std::ranges::to<CtrBeginEndPairOrFallback>(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<C>(MaybeSizedRange<true>());
|
|
assert(result.reserve_called);
|
|
}
|
|
|
|
{ // !sized_range
|
|
using C = Reservable<>;
|
|
auto result = std::ranges::to<C>(MaybeSizedRange<false>());
|
|
assert(!result.reserve_called);
|
|
}
|
|
|
|
{ // Missing `capacity`.
|
|
using C = Reservable</*HasCapacity=*/false>;
|
|
auto result = std::ranges::to<C>(MaybeSizedRange<true>());
|
|
assert(!result.reserve_called);
|
|
}
|
|
|
|
{ // `capacity` doesn't return `size_type`.
|
|
using C = Reservable</*HasCapacity=*/true, /*CapacityReturnsSizeT=*/false>;
|
|
auto result = std::ranges::to<C>(MaybeSizedRange<true>());
|
|
assert(!result.reserve_called);
|
|
}
|
|
|
|
{ // Missing `max_size`.
|
|
using C = Reservable</*HasCapacity=*/true, /*CapacityReturnsSizeT=*/true, /*HasMaxSize=*/false>;
|
|
auto result = std::ranges::to<C>(MaybeSizedRange<true>());
|
|
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<C>(MaybeSizedRange<true>());
|
|
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<int, CtrChoice::DirectCtr>;
|
|
std::same_as<C> decltype(auto) result = std::ranges::to<C>(in);
|
|
|
|
assert(result.ctr_choice == CtrChoice::DirectCtr);
|
|
assert(std::ranges::equal(result, in));
|
|
assert((in | std::ranges::to<C>()) == result);
|
|
auto closure = std::ranges::to<C>();
|
|
assert((in | closure) == result);
|
|
}
|
|
|
|
{ // Extra arguments.
|
|
using C = Container<int, CtrChoice::DirectCtr>;
|
|
std::same_as<C> decltype(auto) result = std::ranges::to<C>(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<C>(arg1, arg2)) == result);
|
|
auto closure = std::ranges::to<C>(arg1, arg2);
|
|
assert((in | closure) == result);
|
|
}
|
|
}
|
|
|
|
{ // Case 2 -- construct using the `from_range_t` tag.
|
|
{
|
|
using C = Container<int, CtrChoice::FromRangeT>;
|
|
std::same_as<C> decltype(auto) result = std::ranges::to<C>(in);
|
|
|
|
assert(result.ctr_choice == CtrChoice::FromRangeT);
|
|
assert(std::ranges::equal(result, in));
|
|
assert((in | std::ranges::to<C>()) == result);
|
|
auto closure = std::ranges::to<C>();
|
|
assert((in | closure) == result);
|
|
}
|
|
|
|
{ // Extra arguments.
|
|
using C = Container<int, CtrChoice::FromRangeT>;
|
|
std::same_as<C> decltype(auto) result = std::ranges::to<C>(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<C>(arg1, arg2)) == result);
|
|
auto closure = std::ranges::to<C>(arg1, arg2);
|
|
assert((in | closure) == result);
|
|
}
|
|
}
|
|
|
|
{ // Case 3 -- construct from a begin-end pair.
|
|
{
|
|
using C = Container<int, CtrChoice::BeginEndPair>;
|
|
std::same_as<C> decltype(auto) result = std::ranges::to<C>(in);
|
|
|
|
assert(result.ctr_choice == CtrChoice::BeginEndPair);
|
|
assert(std::ranges::equal(result, in));
|
|
assert((in | std::ranges::to<C>()) == result);
|
|
auto closure = std::ranges::to<C>();
|
|
assert((in | closure) == result);
|
|
}
|
|
|
|
{ // Extra arguments.
|
|
using C = Container<int, CtrChoice::BeginEndPair>;
|
|
std::same_as<C> decltype(auto) result = std::ranges::to<C>(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<C>(arg1, arg2)) == result);
|
|
auto closure = std::ranges::to<C>(arg1, arg2);
|
|
assert((in | closure) == result);
|
|
}
|
|
}
|
|
|
|
{ // Case 4 -- default-construct then insert elements.
|
|
{
|
|
using C = Container<int, CtrChoice::DefaultCtrAndInsert, InserterChoice::Insert, /*CanReserve=*/false>;
|
|
std::same_as<C> decltype(auto) result = std::ranges::to<C>(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<C>()) == result);
|
|
auto closure = std::ranges::to<C>();
|
|
assert((in | closure) == result);
|
|
}
|
|
|
|
{
|
|
using C = Container<int, CtrChoice::DefaultCtrAndInsert, InserterChoice::Insert, /*CanReserve=*/true>;
|
|
std::same_as<C> decltype(auto) result = std::ranges::to<C>(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<C>()) == result);
|
|
auto closure = std::ranges::to<C>();
|
|
assert((in | closure) == result);
|
|
}
|
|
|
|
{
|
|
using C = Container<int, CtrChoice::DefaultCtrAndInsert, InserterChoice::PushBack, /*CanReserve=*/false>;
|
|
std::same_as<C> decltype(auto) result = std::ranges::to<C>(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<C>()) == result);
|
|
auto closure = std::ranges::to<C>();
|
|
assert((in | closure) == result);
|
|
}
|
|
|
|
{
|
|
using C = Container<int, CtrChoice::DefaultCtrAndInsert, InserterChoice::PushBack, /*CanReserve=*/true>;
|
|
std::same_as<C> decltype(auto) result = std::ranges::to<C>(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<C>()) == result);
|
|
auto closure = std::ranges::to<C>();
|
|
assert((in | closure) == result);
|
|
}
|
|
|
|
{ // Extra arguments.
|
|
using C = Container<int, CtrChoice::DefaultCtrAndInsert, InserterChoice::Insert, /*CanReserve=*/false>;
|
|
std::same_as<C> decltype(auto) result = std::ranges::to<C>(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<C>(arg1, arg2)) == result);
|
|
auto closure = std::ranges::to<C>(arg1, arg2);
|
|
assert((in | closure) == result);
|
|
}
|
|
}
|
|
}
|
|
|
|
template <CtrChoice Rank>
|
|
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 <class Iter>
|
|
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<NotARange<CtrChoice::DirectCtr>>);
|
|
|
|
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<CtrChoice::DirectCtr>;
|
|
[[maybe_unused]] std::same_as<C> decltype(auto) result = std::ranges::to<C>(in);
|
|
}
|
|
|
|
{
|
|
using C = NotARange<CtrChoice::FromRangeT>;
|
|
[[maybe_unused]] std::same_as<C> decltype(auto) result = std::ranges::to<C>(in);
|
|
}
|
|
|
|
{
|
|
using C = NotARange<CtrChoice::BeginEndPair>;
|
|
[[maybe_unused]] std::same_as<C> decltype(auto) result = std::ranges::to<C>(in);
|
|
}
|
|
|
|
{
|
|
using C = NotARange<CtrChoice::DefaultCtrAndInsert>;
|
|
[[maybe_unused]] std::same_as<C> decltype(auto) result = std::ranges::to<C>(in);
|
|
}
|
|
}
|
|
|
|
constexpr void test_recursive() {
|
|
using C1 = Container<int, CtrChoice::DirectCtr>;
|
|
using C2 = Container<C1, CtrChoice::FromRangeT>;
|
|
using C3 = Container<C2, CtrChoice::BeginEndPair>;
|
|
using C4 = Container<C3, CtrChoice::DefaultCtrAndInsert, InserterChoice::PushBack>;
|
|
using A1 = std::array<int, 4>;
|
|
using A2 = std::array<A1, 3>;
|
|
using A3 = std::array<A2, 2>;
|
|
using A4 = std::array<A3, 2>;
|
|
|
|
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<C4> decltype(auto) result = std::ranges::to<C4>(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<C4>()) == 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;
|
|
}
|