// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <algorithm>
#include <array>
#include <cassert>
#include <forward_list>
#include <functional>
#include <ranges>
#include <span>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>

#include <range_algorithm_support.hpp>

using namespace std;

template <class R, class F, size_t N>
concept CanViewAdjacentTransform =
    requires(R&& r, F&& f) { views::adjacent_transform<N>(forward<R>(r), forward<F>(f)); };

template <class T, class Indices>
struct repeated_tuple_impl;

template <class T, size_t... Indices>
struct repeated_tuple_impl<T, index_sequence<Indices...>> {
    template <size_t>
    using repeat_type = T;

    using type = tuple<repeat_type<Indices>...>;
};

template <class T, size_t N>
using repeated_tuple = repeated_tuple_impl<T, make_index_sequence<N>>::type;

static_assert(same_as<repeated_tuple<int, 0>, tuple<>>);
static_assert(same_as<repeated_tuple<int, 3>, tuple<int, int, int>>);
static_assert(same_as<repeated_tuple<int, 5>, tuple<int, int, int, int, int>>);

template <class Fn, class Ty, class Indices>
struct regular_invocable_with_repeated_type_impl;

template <class F, class T, size_t... Indices>
struct regular_invocable_with_repeated_type_impl<F, T, index_sequence<Indices...>> {
    template <size_t>
    using repeat_type = T;

    static constexpr bool value = regular_invocable<F, repeat_type<Indices>...>;
};

template <class Fn, class Ty, size_t Nx>
concept regular_invocable_with_repeated_type =
    regular_invocable_with_repeated_type_impl<Fn, Ty, make_index_sequence<Nx>>::value;

static_assert(regular_invocable_with_repeated_type<plus<int>, int, 2>);
static_assert(!regular_invocable_with_repeated_type<plus<int>, int, 3>);
static_assert(!regular_invocable_with_repeated_type<plus<int>, span<int>, 2>);

template <class Fn, class Ty, size_t Nx, class... Repeated>
struct invoke_result_with_repeated_type_impl : invoke_result_with_repeated_type_impl<Fn, Ty, Nx - 1, Ty, Repeated...> {
};

template <class Fn, class Ty, class... Repeated>
struct invoke_result_with_repeated_type_impl<Fn, Ty, 0, Repeated...> {
    using type = invoke_result_t<Fn, Repeated...>;
};

template <class Fn, class Ty, size_t Nx>
using invoke_result_with_repeated_type = invoke_result_with_repeated_type_impl<Fn, Ty, Nx>::type;

static_assert(same_as<invoke_result_with_repeated_type<plus<int>, int, 2>, int>);
static_assert(same_as<invoke_result_with_repeated_type<equal_to<int>, int, 2>, bool>);

template <size_t N, ranges::forward_range Rng, class Expected, class Fn>
constexpr bool test_one(Rng&& rng, Fn func, Expected&& expected_rng) {
    using ranges::adjacent_transform_view, ranges::adjacent_view, ranges::forward_range, ranges::bidirectional_range,
        ranges::random_access_range, ranges::sized_range, ranges::common_range, ranges::iterator_t,
        ranges::const_iterator_t, ranges::sentinel_t, ranges::const_sentinel_t, ranges::range_reference_t,
        ranges::range_difference_t, ranges::begin, ranges::end;

    constexpr bool is_view = ranges::view<remove_cvref_t<Rng>>;

    using V = views::all_t<Rng>;
    using R = adjacent_transform_view<V, decay_t<Fn>, N>;

    static_assert(ranges::view<R>);
    static_assert(forward_range<R>);
    static_assert(bidirectional_range<R> == bidirectional_range<Rng>);
    static_assert(random_access_range<R> == random_access_range<Rng>);
    static_assert(!ranges::contiguous_range<R>);

    // Check default-initializability
    static_assert(default_initializable<R> == default_initializable<V>);

    // Check borrowed_range
    static_assert(!ranges::borrowed_range<R>);

    // Check range closure object
    const auto closure = views::adjacent_transform<N>(func);

    // ... with lvalue argument
    static_assert(CanViewAdjacentTransform<Rng&, Fn, N> == (!is_view || copy_constructible<V>) );
    if constexpr (CanViewAdjacentTransform<Rng&, Fn, N>) {
        constexpr bool is_noexcept =
            !is_view || (is_nothrow_copy_constructible_v<V> && is_nothrow_copy_constructible_v<Fn>);

        static_assert(same_as<decltype(views::adjacent_transform<N>(rng, func)), R>);
        static_assert(noexcept(views::adjacent_transform<N>(rng, func)) == is_noexcept);

        static_assert(same_as<decltype(closure(rng)), R>);
        static_assert(noexcept(closure(rng)) == is_noexcept);

        static_assert(same_as<decltype(rng | closure), R>);
        static_assert(noexcept(rng | closure) == is_noexcept);
    }

    // ... with const lvalue argument
    static_assert(
        CanViewAdjacentTransform<const remove_reference_t<Rng>&, Fn, N> == (!is_view || copy_constructible<V>) );
    if constexpr (CanViewAdjacentTransform<const remove_reference_t<Rng>&, Fn, N>) {
        using RC = adjacent_transform_view<views::all_t<const remove_reference_t<Rng>&>, Fn, N>;
        constexpr bool is_noexcept =
            !is_view || (is_nothrow_copy_constructible_v<V> && is_nothrow_copy_constructible_v<Fn>);

        static_assert(same_as<decltype(views::adjacent_transform<N>(as_const(rng), func)), RC>);
        static_assert(noexcept(views::adjacent_transform<N>(as_const(rng), func)) == is_noexcept);

        static_assert(same_as<decltype(closure(as_const(rng))), RC>);
        static_assert(noexcept(closure(as_const(rng))) == is_noexcept);

        static_assert(same_as<decltype(as_const(rng) | closure), RC>);
        static_assert(noexcept(as_const(rng) | closure) == is_noexcept);
    }

    // ... with rvalue argument
    static_assert(
        CanViewAdjacentTransform<remove_reference_t<Rng>, Fn, N> == (is_view || movable<remove_reference_t<Rng>>) );
    if constexpr (CanViewAdjacentTransform<remove_reference_t<Rng>, Fn, N>) {
        using RS                   = adjacent_transform_view<views::all_t<remove_reference_t<Rng>>, Fn, N>;
        constexpr bool is_noexcept = is_nothrow_move_constructible_v<V> && is_nothrow_copy_constructible_v<Fn>;

        static_assert(same_as<decltype(views::adjacent_transform<N>(move(rng), func)), RS>);
        static_assert(noexcept(views::adjacent_transform<N>(move(rng), func)) == is_noexcept);

        static_assert(same_as<decltype(closure(move(rng))), RS>);
        static_assert(noexcept(closure(move(rng))) == is_noexcept);

        static_assert(same_as<decltype(move(rng) | closure), RS>);
        static_assert(noexcept(move(rng) | closure) == is_noexcept);
    }

    // ... with const rvalue argument
    static_assert(
        CanViewAdjacentTransform<const remove_reference_t<Rng>, Fn, N> == (is_view && copy_constructible<V>) );
    if constexpr (CanViewAdjacentTransform<const remove_reference_t<Rng>, Fn, N>) {
        constexpr bool is_noexcept = is_nothrow_copy_constructible_v<V> && is_nothrow_copy_constructible_v<Fn>;

        static_assert(same_as<decltype(views::adjacent_transform<N>(move(as_const(rng)), func)), R>);
        static_assert(noexcept(views::adjacent_transform<N>(move(as_const(rng)), func)) == is_noexcept);

        static_assert(same_as<decltype(closure(move(as_const(rng)))), R>);
        static_assert(noexcept(closure(move(as_const(rng)))) == is_noexcept);

        static_assert(same_as<decltype(move(as_const(rng)) | closure), R>);
        static_assert(noexcept(move(as_const(rng)) | closure) == is_noexcept);
    }

    // Check views::pairwise_transform
    if constexpr (N == 2) {
        using Pairwised  = decltype(views::pairwise_transform(forward<Rng>(rng), func));
        using Adjacented = decltype(views::adjacent_transform<2>(forward<Rng>(rng), func));
        static_assert(same_as<Pairwised, Adjacented>);
    }

    R r{forward<Rng>(rng), func};

    // Check adjacent_transform_view::size
    static_assert(CanMemberSize<R> == sized_range<V>);
    if constexpr (CanMemberSize<R>) {
        same_as<ranges::range_size_t<V>> auto s = r.size();
        assert(_To_unsigned_like(s) == ranges::size(expected_rng));
        static_assert(noexcept(r.size()) == noexcept(ranges::size(rng))); // strengthened
    }

    // Check adjacent_transform_view::size (const)
    static_assert(CanMemberSize<const R> == sized_range<const V>);
    if constexpr (CanMemberSize<const R>) {
        same_as<ranges::range_size_t<const V>> auto s = as_const(r).size();
        assert(_To_unsigned_like(s) == ranges::size(expected_rng));
        static_assert(noexcept(as_const(r).size()) == noexcept(ranges::size(as_const(rng)))); // strengthened
    }

    const bool is_empty = ranges::empty(expected_rng);

    // Check view_interface::empty and operator bool
    static_assert(CanMemberEmpty<R>);
    static_assert(CanBool<R>);
    assert(r.empty() == is_empty);
    assert(static_cast<bool>(r) == !is_empty);

    // Check view_interface::empty and operator bool (const)
    static_assert(CanMemberEmpty<const R>);
    static_assert(CanBool<const R>);
    assert(as_const(r).empty() == is_empty);
    assert(static_cast<bool>(as_const(r)) == !is_empty);

    // Check content
    assert(ranges::equal(r, expected_rng));

    // Check adjacent_transform_view::begin
    static_assert(CanMemberBegin<R>);
    {
        const same_as<iterator_t<R>> auto i = r.begin();
        if (!is_empty) {
            assert(*i == *begin(expected_rng));
        }

        if constexpr (copy_constructible<V>) {
            auto r2                              = r;
            const same_as<iterator_t<R>> auto i2 = r2.begin();
            if (!is_empty) {
                assert(*i2 == *i);
            }
        }
    }

    // Check adjacent_transform_view::begin (const)
    static_assert(
        CanMemberBegin<const R>
        == (ranges::range<const V> && regular_invocable_with_repeated_type<const Fn&, range_reference_t<const V>, N>) );
    if constexpr (CanMemberBegin<const R>) {
        const same_as<iterator_t<const R>> auto ci = as_const(r).begin();
        if (!is_empty) {
            assert(*ci == *begin(expected_rng));
        }

        if constexpr (copy_constructible<V>) {
            const auto cr2                              = r;
            const same_as<iterator_t<const R>> auto ci2 = cr2.begin();
            if (!is_empty) {
                assert(*ci2 == *ci);
            }
        }
    }

    // Check adjacent_transform_view::end
    static_assert(CanMemberEnd<R>);
    {
        const same_as<sentinel_t<R>> auto s = r.end();
        assert((r.begin() == s) == is_empty);

        if constexpr (sentinel_for<sentinel_t<R>, iterator_t<const R>>) {
            assert((as_const(r).begin() == s) == is_empty);
        }

        static_assert(common_range<R> == common_range<V>);
        if constexpr (common_range<V> && bidirectional_range<V>) {
            if (!is_empty) {
                assert(*ranges::prev(s) == *ranges::prev(end(expected_rng)));
            }

            if constexpr (copy_constructible<V>) {
                auto r2 = r;
                if (!is_empty) {
                    assert(*ranges::prev(r2.end()) == *ranges::prev(end(expected_rng)));
                }
            }
        }
    }

    // Check adjacent_transform_view::end (const)
    static_assert(
        CanMemberEnd<const R>
        == (ranges::range<const V> && regular_invocable_with_repeated_type<const Fn&, range_reference_t<const V>, N>) );
    if constexpr (CanMemberEnd<const R>) {
        const same_as<sentinel_t<const R>> auto cs = as_const(r).end();
        assert((as_const(r).begin() == cs) == is_empty);

        if constexpr (sentinel_for<sentinel_t<const R>, iterator_t<R>>) {
            assert((r.begin() == cs) == is_empty);
        }

        static_assert(common_range<const R> == common_range<const V>);
        if constexpr (common_range<const V> && bidirectional_range<const V>) {
            if (!is_empty) {
                assert(*ranges::prev(cs) == *ranges::prev(end(expected_rng)));
            }

            if constexpr (copy_constructible<V>) {
                const auto r2 = r;
                if (!is_empty) {
                    assert(*ranges::prev(r2.end()) == *ranges::prev(end(expected_rng)));
                }
            }
        }
    }

    // Check view_interface::cbegin
    static_assert(CanMemberCBegin<R>);
    static_assert(
        CanMemberCBegin<const R&>
        == (ranges::range<const V> && regular_invocable_with_repeated_type<const Fn&, range_reference_t<const V>, N>) );
    {
        const same_as<const_iterator_t<R>> auto i = r.cbegin();
        if (!is_empty) {
            assert(*i == *cbegin(expected_rng));
        }

        if constexpr (copyable<V>) {
            auto r2                                    = r;
            const same_as<const_iterator_t<R>> auto i2 = r2.cbegin();
            if (!is_empty) {
                assert(*i2 == *i);
            }
        }

        if constexpr (CanCBegin<const R&>) {
            const same_as<const_iterator_t<const R>> auto i3 = as_const(r).cbegin();
            if (!is_empty) {
                assert(*i3 == *i);
            }
        }
    }

    // Check view_interface::cend
    static_assert(CanMemberCEnd<R>);
    static_assert(
        CanMemberCEnd<const R&>
        == (ranges::range<const V> && regular_invocable_with_repeated_type<const Fn&, range_reference_t<const V>, N>) );
    if (!is_empty) {
        same_as<const_sentinel_t<R>> auto i = r.cend();
        if constexpr (common_range<V> && sized_range<V> && bidirectional_range<V>) {
            assert(*ranges::prev(i) == *ranges::prev(cend(expected_rng)));
        }

        if constexpr (CanCEnd<const R&>) {
            same_as<const_sentinel_t<const R>> auto i2 = as_const(r).cend();
            if constexpr (common_range<const V> && sized_range<const V> && bidirectional_range<const V>) {
                assert(*ranges::prev(i2) == *ranges::prev(cend(expected_rng)));
            }
        }
    }

    // Check view_interface::data
    static_assert(!CanData<R>);
    static_assert(!CanData<const R>);

    if (is_empty) {
        return true;
    }

    // Check view_interface::operator[]
    static_assert(CanIndex<R> == random_access_range<V>);
    if constexpr (CanIndex<R>) {
        assert(r[0] == expected_rng[0]);
    }

    // Check view_interface::operator[] (const)
    static_assert(CanIndex<const R> == random_access_range<const V>);
    if constexpr (CanIndex<const R>) {
        assert(as_const(r)[0] == expected_rng[0]);
    }

    // Check view_interface::front
    static_assert(CanMemberFront<R>);
    assert(r.front() == *begin(expected_rng));

    // Check view_interface::front (const)
    static_assert(CanMemberFront<const R>);
    assert(as_const(r).front() == *begin(expected_rng));

    // Check view_interface::back
    static_assert(CanMemberBack<R> == (bidirectional_range<V> && common_range<V>) );
    if constexpr (CanMemberBack<R>) {
        assert(r.back() == *prev(end(expected_rng)));
    }

    // Check view_interface::back (const)
    static_assert(CanMemberBack<const R> == (bidirectional_range<const V> && common_range<const V>) );
    if constexpr (CanMemberBack<const R>) {
        assert(as_const(r).back() == *prev(end(expected_rng)));
    }

    { // Check adjacent_transform_view::iterator<not const>
        using BI = iterator_t<V>;
        using I  = iterator_t<R>;
        static_assert(forward_iterator<I>);

        // Check iterator_category
        using IterCat = I::iterator_category;
        if constexpr (!is_reference_v<invoke_result_with_repeated_type<Fn, range_reference_t<V>, N>>) {
            static_assert(same_as<IterCat, input_iterator_tag>);
        } else {
            using BaseCat = iterator_traits<BI>::iterator_category;
            if constexpr (derived_from<BaseCat, random_access_iterator_tag>) {
                static_assert(same_as<IterCat, random_access_iterator_tag>);
            } else if constexpr (derived_from<BaseCat, bidirectional_iterator_tag>) {
                static_assert(same_as<IterCat, bidirectional_iterator_tag>);
            } else if constexpr (derived_from<BaseCat, forward_iterator_tag>) {
                static_assert(same_as<IterCat, forward_iterator_tag>);
            } else {
                static_assert(same_as<IterCat, input_iterator_tag>);
            }
        }

        // Check iterator_concept
        using IterConcept = I::iterator_concept;
        static_assert(same_as<IterConcept, typename iterator_t<adjacent_view<V, N>>::iterator_concept>);

        // Check value_type
        static_assert(same_as<typename I::value_type,
            remove_cvref_t<invoke_result_with_repeated_type<Fn&, range_reference_t<V>, N>>>);

        // Check default-initializability
        static_assert(default_initializable<I>);

        auto i = r.begin();

        { // Check dereference
            same_as<invoke_result_with_repeated_type<Fn&, range_reference_t<V>, N>> decltype(auto) v = *as_const(i);
            assert(v == expected_rng[0]);
        }

        { // Check pre-incrementation
            same_as<I&> decltype(auto) i2 = ++i;
            assert(&i2 == &i);
            if (i != r.end()) {
#pragma warning(push)
#pragma warning(disable : 28020) // The expression '_Param_(1)<1' is not true at this call.
                assert(*i == expected_rng[1]);
#pragma warning(pop)
            }
            i = r.begin();
        }

        { // Check post-incrementation
            same_as<I> decltype(auto) i2 = i++;
            assert(*i2 == expected_rng[0]);
            if (i != r.end()) {
#pragma warning(push)
#pragma warning(disable : 28020) // The expression '_Param_(1)<1' is not true at this call.
                assert(*i == expected_rng[1]);
#pragma warning(pop)
            }
            i = r.begin();
        }

        { // Check equality comparisons
            auto i2               = i;
            same_as<bool> auto b1 = i == i2;
            assert(b1);
            ++i2;
            same_as<bool> auto b2 = i != i2;
            assert(b2);
        }

        if constexpr (bidirectional_range<V>) {
            { // Check pre-decrementation
                i = ranges::next(r.begin());

                same_as<I&> decltype(auto) i2 = --i;
                assert(&i2 == &i);
                assert(*i2 == expected_rng[0]);
            }

            { // Check post-decrementation
                i = ranges::next(r.begin());

                same_as<I> decltype(auto) i2 = i--;
                if (i2 != r.end()) {
#pragma warning(push)
#pragma warning(disable : 28020) // The expression '_Param_(1)<1' is not true at this call.
                    assert(*i2 == expected_rng[1]);
#pragma warning(pop)
                }
                assert(*i == expected_rng[0]);
            }
        }

        if constexpr (random_access_range<V>) {
            { // Check advancement operators
                same_as<I&> decltype(auto) i2 = (i += 1);
                assert(&i2 == &i);
                if (i != r.end()) {
#pragma warning(push)
#pragma warning(disable : 28020) // The expression '_Param_(1)<1' is not true at this call.
                    assert(*i == expected_rng[1]);
#pragma warning(pop)
                }

                same_as<I&> decltype(auto) i3 = (i -= 1);
                assert(&i3 == &i);
                assert(*i == expected_rng[0]);
            }

            { // Check subscript operator
                same_as<invoke_result_with_repeated_type<Fn&, range_reference_t<V>, N>> decltype(auto) v = i[0];
                assert(v == expected_rng[0]);
            }

            { // Check other comparisons
                auto i2               = ranges::next(i);
                same_as<bool> auto b1 = i < i2;
                assert(b1);
                same_as<bool> auto b2 = i2 > i;
                assert(b2);
                same_as<bool> auto b3 = i <= i2;
                assert(b3);
                same_as<bool> auto b4 = i2 >= i;
                assert(b4);
            }

            if constexpr (three_way_comparable<BI>) { // Check 3way comparisons
                using Cat              = compare_three_way_result_t<BI>;
                auto i2                = i;
                same_as<Cat> auto cmp1 = i <=> i2;
                assert(cmp1 == Cat::equivalent);
                ++i2;
                assert((i <=> i2) == Cat::less);
                assert((i2 <=> i) == Cat::greater);
            }

            { // Check operator+
                same_as<I> auto i2 = i + 1;
                if (i2 != r.end()) {
#pragma warning(push)
#pragma warning(disable : 28020) // The expression '_Param_(1)<1' is not true at this call.
                    assert(*i2 == expected_rng[1]);
#pragma warning(pop)
                }

                same_as<I> auto i3 = 1 + i;
                if (i3 != r.end()) {
#pragma warning(push)
#pragma warning(disable : 28020) // The expression '_Param_(1)<1' is not true at this call.
                    assert(*i3 == expected_rng[1]);
#pragma warning(pop)
                }
            }

            { // Check operator-(Iter, Diff)
                same_as<I> auto i2 = ranges::next(i) - 1;
                assert(*i2 == expected_rng[0]);
            }
        }

        if constexpr (sized_sentinel_for<BI, BI>) { // Check differencing
            same_as<range_difference_t<V>> auto diff = i - i;
            assert(diff == 0);
            assert((i - ranges::next(i)) == -1);
            assert((ranges::next(i) - i) == 1);
        }

        if constexpr (sized_sentinel_for<sentinel_t<R>, I>) { // Check differencing with sentinel<not const>
            const auto s                                    = r.end();
            const auto size                                 = ranges::ssize(expected_rng);
            const same_as<range_difference_t<V>> auto diff1 = i - s;
            assert(diff1 == -size);
            const same_as<range_difference_t<V>> auto diff2 = s - i;
            assert(diff2 == size);
        }

        if constexpr (sized_sentinel_for<sentinel_t<const R>, I>) { // Check differencing with sentinel<const>
            const auto s                                    = as_const(r).end();
            const auto size                                 = ranges::ssize(expected_rng);
            const same_as<range_difference_t<V>> auto diff1 = i - s;
            assert(diff1 == -size);
            const same_as<range_difference_t<V>> auto diff2 = s - i;
            assert(diff2 == size);
        }
    }

    // Check adjacent_transform_view::iterator<const>
    if constexpr (CanMemberBegin<const R>) {
        using CBI = iterator_t<const V>;
        using CI  = iterator_t<const R>;
        static_assert(forward_iterator<CI>);

        // Check iterator_category
        using IterCat = CI::iterator_category;
        if constexpr (!is_reference_v<invoke_result_with_repeated_type<Fn, range_reference_t<const V>, N>>) {
            static_assert(same_as<IterCat, input_iterator_tag>);
        } else {
            using BaseCat = iterator_traits<CBI>::iterator_category;
            if constexpr (derived_from<BaseCat, random_access_iterator_tag>) {
                static_assert(same_as<IterCat, random_access_iterator_tag>);
            } else if constexpr (derived_from<BaseCat, bidirectional_iterator_tag>) {
                static_assert(same_as<IterCat, bidirectional_iterator_tag>);
            } else if constexpr (derived_from<BaseCat, forward_iterator_tag>) {
                static_assert(same_as<IterCat, forward_iterator_tag>);
            } else {
                static_assert(same_as<IterCat, input_iterator_tag>);
            }
        }

        // Check iterator_concept
        using IterConcept = CI::iterator_concept;
        static_assert(same_as<IterConcept, typename iterator_t<const adjacent_view<V, N>>::iterator_concept>);

        // Check value_type
        static_assert(same_as<typename CI::value_type,
            remove_cvref_t<invoke_result_with_repeated_type<const Fn&, range_reference_t<const V>, N>>>);

        // Check default-initializability
        static_assert(default_initializable<CI>);

        iterator_t<R> i = r.begin();

        // Check construction from non-const iterator
        CI ci = i;

        { // Check dereference
            same_as<invoke_result_with_repeated_type<const Fn&, range_reference_t<const V>, N>> decltype(auto) v =
                *as_const(ci);
            assert(v == expected_rng[0]);
        }

        { // Check pre-incrementation
            same_as<CI&> decltype(auto) ci2 = ++ci;
            assert(&ci2 == &ci);
            if (ci != r.end()) {
#pragma warning(push)
#pragma warning(disable : 28020) // The expression '_Param_(1)<1' is not true at this call.
                assert(*ci == expected_rng[1]);
#pragma warning(pop)
            }
            ci = r.begin();
        }

        { // Check post-incrementation
            same_as<CI> decltype(auto) ci2 = ci++;
            assert(*ci2 == expected_rng[0]);
            if (ci != r.end()) {
#pragma warning(push)
#pragma warning(disable : 28020) // The expression '_Param_(1)<1' is not true at this call.
                assert(*ci == expected_rng[1]);
#pragma warning(pop)
            }
            ci = r.begin();
        }

        { // Check equality comparisons
            auto ci2              = ci;
            same_as<bool> auto b1 = ci == ci2;
            assert(b1);
            ++ci2;
            same_as<bool> auto b2 = ci != ci2;
            assert(b2);
        }

        { // Check equality comparisons with non-const iterators
            same_as<bool> auto b1 = ci == i;
            assert(b1);
            ++i;
            same_as<bool> auto b2 = ci != i;
            assert(b2);
            i = r.begin();
        }

        if constexpr (bidirectional_range<const V>) {
            { // Check pre-decrementation
                ci = ranges::next(r.begin());

                same_as<CI&> decltype(auto) ci2 = --ci;
                assert(&ci2 == &ci);
                assert(*ci2 == expected_rng[0]);
            }

            { // Check post-decrementation
                ci = ranges::next(r.begin());

                same_as<CI> decltype(auto) ci2 = ci--;
                if (ci2 != r.end()) {
#pragma warning(push)
#pragma warning(disable : 28020) // The expression '_Param_(1)<1' is not true at this call.
                    assert(*ci2 == expected_rng[1]);
#pragma warning(pop)
                }
                assert(*ci == expected_rng[0]);
            }
        }

        if constexpr (random_access_range<const V>) {
            { // Check advancement operators
                same_as<CI&> decltype(auto) ci2 = (ci += 1);
                assert(&ci2 == &ci);
                if (ci != r.end()) {
#pragma warning(push)
#pragma warning(disable : 28020) // The expression '_Param_(1)<1' is not true at this call.
                    assert(*ci == expected_rng[1]);
#pragma warning(pop)
                }

                same_as<CI&> decltype(auto) ci3 = (ci -= 1);
                assert(&ci3 == &ci);
                assert(*ci == expected_rng[0]);
            }

            { // Check subscript operator
                same_as<invoke_result_with_repeated_type<const Fn&, range_reference_t<const V>, N>> decltype(auto) v =
                    ci[0];
                assert(v == expected_rng[0]);
            }

            { // Check comparisons
                auto ci2              = ranges::next(ci);
                same_as<bool> auto b1 = ci < ci2;
                assert(b1);
                same_as<bool> auto b2 = ci2 > ci;
                assert(b2);
                same_as<bool> auto b3 = ci <= ci2;
                assert(b3);
                same_as<bool> auto b4 = ci2 >= ci;
                assert(b4);
            }

            { // Check comparisons with non-const iterators
                ++i;
                same_as<bool> auto b1 = ci < i;
                assert(b1);
                same_as<bool> auto b2 = i > ci;
                assert(b2);
                same_as<bool> auto b3 = ci <= i;
                assert(b3);
                same_as<bool> auto b4 = i >= ci;
                assert(b4);
                --i;
            }

            if constexpr (three_way_comparable<CBI>) { // Check 3way comparisons
                using Cat              = compare_three_way_result_t<CBI>;
                auto ci2               = ci;
                same_as<Cat> auto cmp1 = ci <=> ci2;
                assert(cmp1 == Cat::equivalent);
                ++ci2;
                assert((ci <=> ci2) == Cat::less);
                assert((ci2 <=> ci) == Cat::greater);
            }

            if constexpr (three_way_comparable<CBI>) { // Check 3way comparisons with non-const iterators
                using Cat              = compare_three_way_result_t<CBI>;
                same_as<Cat> auto cmp1 = ci <=> i;
                assert(cmp1 == Cat::equivalent);
                ++i;
                assert((ci <=> i) == Cat::less);
                assert((i <=> ci) == Cat::greater);
                --i;
            }

            { // Check operator+
                same_as<CI> auto ci2 = ci + 1;
                if (ci2 != r.end()) {
#pragma warning(push)
#pragma warning(disable : 28020) // The expression '_Param_(1)<1' is not true at this call.
                    assert(*ci2 == expected_rng[1]);
#pragma warning(pop)
                }

                same_as<CI> auto ci3 = 1 + ci;
                if (ci3 != r.end()) {
#pragma warning(push)
#pragma warning(disable : 28020) // The expression '_Param_(1)<1' is not true at this call.
                    assert(*ci3 == expected_rng[1]);
#pragma warning(pop)
                }
            }

            { // Check operator-(Iter, Diff)
                same_as<CI> auto ci2 = ranges::next(ci) - 1;
                assert(*ci2 == expected_rng[0]);
            }
        }

        if constexpr (sized_sentinel_for<CBI, CBI>) { // Check differencing
            same_as<range_difference_t<const V>> auto diff = ci - ci;
            assert(diff == 0);
            assert((ci - ranges::next(ci)) == -1);
            assert((ranges::next(ci) - ci) == 1);
        }

        if constexpr (sized_sentinel_for<sentinel_t<R>, CI>) { // Check differencing with sentinel<not const>
            const auto s                                          = r.end();
            const auto size                                       = ranges::ssize(expected_rng);
            const same_as<range_difference_t<const V>> auto diff1 = ci - s;
            assert(diff1 == -size);
            const same_as<range_difference_t<const V>> auto diff2 = s - ci;
            assert(diff2 == size);
        }

        if constexpr (sized_sentinel_for<sentinel_t<const R>, CI>) { // Check differencing with sentinel<const>
            const auto s                                          = as_const(r).end();
            const auto size                                       = ranges::ssize(expected_rng);
            const same_as<range_difference_t<const V>> auto diff1 = ci - s;
            assert(diff1 == -size);
            const same_as<range_difference_t<const V>> auto diff2 = s - ci;
            assert(diff2 == size);
        }
    }

    // Check adjacent_transform_view::base() const&
    static_assert(CanMemberBase<const R&> == copy_constructible<V>);
    if constexpr (copy_constructible<V>) {
        [[maybe_unused]] same_as<V> auto b1 = as_const(r).base();
        static_assert(noexcept(as_const(r).base()) == is_nothrow_copy_constructible_v<V>); // strengthened
    }

    // Check adjacent_transform_view::base() &&
    [[maybe_unused]] same_as<V> auto b2 = move(r).base();
    static_assert(noexcept(move(r).base()) == is_nothrow_move_constructible_v<V>); // strengthened

    return true;
}

template <ranges::forward_range Rng>
constexpr void test_adjacent0(Rng&& rng) {
    auto func = [] { return 602; };
    using Fn  = decltype(func);

    using V = views::all_t<Rng>;
    using E = ranges::empty_view<int>;

    // Check range closure object
    constexpr bool is_view = ranges::view<remove_cvref_t<Rng>>;
    const auto closure     = views::adjacent_transform<0>(func);

    // ... with lvalue argument
    static_assert(CanViewAdjacentTransform<Rng&, Fn, 0> == (!is_view || copy_constructible<V>) );
    if constexpr (CanViewAdjacentTransform<Rng&, Fn, 0>) {
        static_assert(same_as<decltype(views::adjacent_transform<0>(rng, func)), E>);
        static_assert(noexcept(views::adjacent_transform<0>(rng, func)) == is_nothrow_copy_constructible_v<Fn>);

        static_assert(same_as<decltype(closure(rng)), E>);
        static_assert(noexcept(closure(rng)) == is_nothrow_copy_constructible_v<Fn>);

        static_assert(same_as<decltype(rng | closure), E>);
        static_assert(noexcept(rng | closure) == is_nothrow_copy_constructible_v<Fn>);
    }

    // ... with const lvalue argument
    static_assert(
        CanViewAdjacentTransform<const remove_reference_t<Rng>&, Fn, 0> == (!is_view || copy_constructible<V>) );
    if constexpr (CanViewAdjacentTransform<const remove_reference_t<Rng>&, Fn, 0>) {
        static_assert(same_as<decltype(views::adjacent_transform<0>(as_const(rng), func)), E>);
        static_assert(
            noexcept(views::adjacent_transform<0>(as_const(rng), func)) == is_nothrow_copy_constructible_v<Fn>);

        static_assert(same_as<decltype(closure(as_const(rng))), E>);
        static_assert(noexcept(closure(as_const(rng))) == is_nothrow_copy_constructible_v<Fn>);

        static_assert(same_as<decltype(as_const(rng) | closure), E>);
        static_assert(noexcept(as_const(rng) | closure) == is_nothrow_copy_constructible_v<Fn>);
    }

    // ... with rvalue argument
    static_assert(
        CanViewAdjacentTransform<remove_reference_t<Rng>, Fn, 0> == (is_view || movable<remove_reference_t<Rng>>) );
    if constexpr (CanViewAdjacentTransform<remove_reference_t<Rng>, Fn, 0>) {
        static_assert(same_as<decltype(views::adjacent_transform<0>(move(rng), func)), E>);
        static_assert(noexcept(views::adjacent_transform<0>(move(rng), func)) == is_nothrow_copy_constructible_v<Fn>);

        static_assert(same_as<decltype(closure(move(rng))), E>);
        static_assert(noexcept(closure(move(rng))) == is_nothrow_copy_constructible_v<Fn>);

        static_assert(same_as<decltype(move(rng) | closure), E>);
        static_assert(noexcept(move(rng) | closure) == is_nothrow_copy_constructible_v<Fn>);
    }

    // ... with const rvalue argument
    static_assert(
        CanViewAdjacentTransform<const remove_reference_t<Rng>, Fn, 0> == (is_view && copy_constructible<V>) );
    if constexpr (CanViewAdjacentTransform<const remove_reference_t<Rng>, Fn, 0>) {
        static_assert(same_as<decltype(views::adjacent_transform<0>(move(as_const(rng)), func)), E>);
        static_assert(
            noexcept(views::adjacent_transform<0>(move(as_const(rng)), func)) == is_nothrow_copy_constructible_v<Fn>);

        static_assert(same_as<decltype(closure(move(as_const(rng)))), E>);
        static_assert(noexcept(closure(move(as_const(rng)))) == is_nothrow_copy_constructible_v<Fn>);

        static_assert(same_as<decltype(move(as_const(rng)) | closure), E>);
        static_assert(noexcept(move(as_const(rng)) | closure) == is_nothrow_copy_constructible_v<Fn>);
    }
}

// We have to use std::array instead of C-array, see LLVM-61025
constexpr auto some_ints        = array{1, 3, 5, 7, 11, 13};
constexpr auto adjacent1_fn     = [](auto&& val) -> const int& { return val; };
constexpr auto adjacent1_result = array{1, 3, 5, 7, 11, 13};
constexpr auto pairwise_fn      = plus<int>{};
constexpr auto pairwise_result  = array{4, 8, 12, 18, 24};
constexpr auto adjacent3_fn     = [](auto... vals) { return (vals * ...); };
constexpr auto adjacent3_result = array{15, 105, 385, 1001};
constexpr auto adjacent4_fn     = [](auto... vals) { return (vals | ...); };
constexpr auto adjacent4_result = array{7, 15, 15};
constexpr auto adjacent5_fn     = [](auto... vals) { return (vals - ...); };
constexpr auto adjacent5_result = array{7, 7};
constexpr auto adjacent6_fn     = [](auto... vals) { return (... - vals); };
constexpr auto adjacent6_result = array{-38};
constexpr auto adjacent7_fn     = [](auto...) { return 0; };
constexpr array<int, 0> adjacent7_result{};

template <class Category, test::Common IsCommon, test::Sized IsSized>
using test_range =
    test::range<Category, const int, IsSized, test::CanDifference{derived_from<Category, random_access_iterator_tag>},
        IsCommon, test::CanCompare{derived_from<Category, forward_iterator_tag> || IsCommon == test::Common::yes},
        test::ProxyRef{!derived_from<Category, contiguous_iterator_tag>}>;

struct instantiator {
    template <ranges::forward_range R>
    static constexpr void call() {
        R r{some_ints};
        test_one<1>(r, adjacent1_fn, adjacent1_result);
        test_one<2>(r, pairwise_fn, pairwise_result);
#ifndef _M_IX86 // fatal error C1060: compiler is out of heap space
        test_one<4>(r, adjacent4_fn, adjacent4_result);
#endif // !defined(_M_IX86)
        test_one<7>(r, adjacent7_fn, adjacent7_result);
        test_adjacent0(r);
    }
};

constexpr void instantiation_test() {
#ifdef TEST_EVERYTHING
    test_fwd<instantiator, const int>();
#else // ^^^ test all forward range permutations / test only "interesting" permutations vvv
    using test::Common, test::Sized;

    // The view is sensitive to category, commonality, and size, but oblivious to proxyness and differencing
    instantiator::call<test_range<forward_iterator_tag, Common::no, Sized::yes>>();
    instantiator::call<test_range<forward_iterator_tag, Common::no, Sized::no>>();
    instantiator::call<test_range<forward_iterator_tag, Common::yes, Sized::yes>>();
    instantiator::call<test_range<forward_iterator_tag, Common::yes, Sized::no>>();

    instantiator::call<test_range<bidirectional_iterator_tag, Common::no, Sized::yes>>();
    instantiator::call<test_range<bidirectional_iterator_tag, Common::no, Sized::no>>();
    instantiator::call<test_range<bidirectional_iterator_tag, Common::yes, Sized::yes>>();
    instantiator::call<test_range<bidirectional_iterator_tag, Common::yes, Sized::no>>();

    instantiator::call<test_range<random_access_iterator_tag, Common::no, Sized::yes>>();
    instantiator::call<test_range<random_access_iterator_tag, Common::no, Sized::no>>();
    instantiator::call<test_range<random_access_iterator_tag, Common::yes, Sized::yes>>();
    instantiator::call<test_range<random_access_iterator_tag, Common::yes, Sized::no>>();

    instantiator::call<test_range<contiguous_iterator_tag, Common::no, Sized::yes>>();
    instantiator::call<test_range<contiguous_iterator_tag, Common::no, Sized::no>>();
    instantiator::call<test_range<contiguous_iterator_tag, Common::yes, Sized::yes>>();
    instantiator::call<test_range<contiguous_iterator_tag, Common::yes, Sized::no>>();
#endif // TEST_EVERYTHING
}

// LWG-4098 views::adjacent<0> should reject non-forward ranges
template <size_t N, ranges::input_range Rng, class Fn>
constexpr void test_input_only(Rng&&, Fn&&) {
    if constexpr (!ranges::forward_range<Rng>) {
        static_assert(!CanViewAdjacentTransform<Rng&, Fn, N>);
        static_assert(!CanViewAdjacentTransform<Rng, Fn, N>);
    }

    if constexpr (!ranges::forward_range<const Rng>) {
        static_assert(!CanViewAdjacentTransform<const Rng&, Fn, N>);
        static_assert(!CanViewAdjacentTransform<const Rng, Fn, N>);
    }
}

struct input_only_instantiator {
#ifdef TEST_EVERYTHING
    template <ranges::input_range R>
    static constexpr void call() {
        R r{some_ints};
        auto nullary_fn = [] { return 1729; };
        test_input_only<0>(r, nullary_fn);
        test_input_only<1>(r, adjacent1_fn);
        test_input_only<2>(r, pairwise_fn);
        test_input_only<4>(r, adjacent4_fn);
        test_input_only<7>(r, adjacent7_fn);
    }
#else // ^^^ test all input range permutations / test only "interesting" permutations vvv
    template <class Tag, test::Common IsCommon, test::Sized IsSized>
    static constexpr void call() {
        test_range<Tag, IsCommon, IsSized> r{some_ints};
        auto nullary_fn = [] { return 1729; };
        test_input_only<0>(r, nullary_fn);
        test_input_only<1>(r, adjacent1_fn);
        test_input_only<2>(r, pairwise_fn);
        test_input_only<4>(r, adjacent4_fn);
        test_input_only<7>(r, adjacent7_fn);
    }
#endif // TEST_EVERYTHING
};

constexpr void instantiation_input_only_test() {
#ifdef TEST_EVERYTHING
    test_in<input_only_instantiator, const int>();
#else // ^^^ test all input range permutations / test only "interesting" permutations vvv
    using test::Common, test::Sized;

    // The view is sensitive to category, commonality, and size, but oblivious to proxyness and differencing
    input_only_instantiator::call<input_iterator_tag, Common::no, Sized::yes>();
    input_only_instantiator::call<input_iterator_tag, Common::no, Sized::no>();
    input_only_instantiator::call<input_iterator_tag, Common::yes, Sized::yes>();
    input_only_instantiator::call<input_iterator_tag, Common::yes, Sized::no>();
#endif // TEST_EVERYTHING
}

template <class Category, test::Common IsCommon, bool IsRandom = derived_from<Category, random_access_iterator_tag>>
using move_only_view = test::range<Category, const int, test::Sized{IsRandom}, test::CanDifference{IsRandom}, IsCommon,
    test::CanCompare::yes, test::ProxyRef{!derived_from<Category, contiguous_iterator_tag>}, test::CanView::yes,
    test::Copyability::move_only>;

int main() {
    { // Check views
        // ... copyable
        constexpr span<const int> s{some_ints};
        static_assert(test_one<4>(s, adjacent4_fn, adjacent4_result));
        test_one<4>(s, adjacent4_fn, adjacent4_result);
    }

    { // ... move-only
        test_one<1>(move_only_view<forward_iterator_tag, test::Common::no>{some_ints}, adjacent1_fn, adjacent1_result);
        test_one<2>(move_only_view<forward_iterator_tag, test::Common::yes>{some_ints}, pairwise_fn, pairwise_result);
        test_one<3>(
            move_only_view<bidirectional_iterator_tag, test::Common::no>{some_ints}, adjacent3_fn, adjacent3_result);
        test_one<4>(
            move_only_view<bidirectional_iterator_tag, test::Common::yes>{some_ints}, adjacent4_fn, adjacent4_result);
        test_one<5>(
            move_only_view<random_access_iterator_tag, test::Common::no>{some_ints}, adjacent5_fn, adjacent5_result);
        test_one<6>(
            move_only_view<random_access_iterator_tag, test::Common::yes>{some_ints}, adjacent6_fn, adjacent6_result);
    }

    { // Check non-views
        static_assert(test_one<2>(some_ints, pairwise_fn, pairwise_result));
        test_one<2>(some_ints, pairwise_fn, pairwise_result);

        auto vec = some_ints | ranges::to<vector>();
        test_one<3>(vec, adjacent3_fn, adjacent3_result);

        auto lst = some_ints | ranges::to<forward_list>();
        test_one<4>(lst, adjacent4_fn, adjacent4_result);
    }

    { // Check empty range
        constexpr auto to_float = [](int x, int y, int z) { return static_cast<float>(x + y + z); };
        static_assert(test_one<3>(span<const int>{}, to_float, span<float>{}));
        test_one<3>(span<const int>{}, to_float, span<float>{});
    }

    { // LWG-3947 Unexpected constraints on adjacent_transform_view::base()
        struct weird_span : span<int>, ranges::view_base {
            weird_span()                  = default;
            weird_span(const weird_span&) = default;
            weird_span(weird_span&)       = delete;

            weird_span& operator=(const weird_span&) = default;
        };
        static_assert(!copy_constructible<weird_span>);

        using weird_adjacent_transform_view = ranges::adjacent_transform_view<weird_span, ranges::equal_to, 2>;
        static_assert(!CanMemberBase<weird_adjacent_transform_view&>);
        static_assert(!CanMemberBase<const weird_adjacent_transform_view&>);
        static_assert(!CanMemberBase<const weird_adjacent_transform_view>);
        static_assert(CanMemberBase<weird_adjacent_transform_view>);
    }

#ifndef _PREFAST_ // TRANSITION, GH-1030
    static_assert((instantiation_test(), true));
#endif
    instantiation_test();

#ifndef _PREFAST_ // TRANSITION, GH-1030
    static_assert((instantiation_input_only_test(), true));
#endif
    instantiation_input_only_test();
}
