//===----------------------------------------------------------------------===//
//
// 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);
// LWG3984: ranges::to's recursion branch may be ill-formed
auto in_owning_view = std::views::all(std::move(in));
static_assert(!std::ranges::viewable_range<decltype((in_owning_view))>);
assert(std::ranges::to<C4>(in_owning_view) == 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;
}