diff --git a/stl/inc/ranges b/stl/inc/ranges index edcb62f86b..cd0b98ae39 100644 --- a/stl/inc/ranges +++ b/stl/inc/ranges @@ -19,6 +19,10 @@ _EMIT_STL_WARNING(STL4038, "The contents of are available only with C++ #include #include +#if _HAS_CXX23 +#include +#endif // _HAS_CXX23 + #pragma pack(push, _CRT_PACKING) #pragma warning(push, _STL_WARNING_LEVEL) #pragma warning(disable : _STL_DISABLED_WARNINGS) @@ -8782,6 +8786,395 @@ namespace ranges { _EXPORT_STD inline constexpr _Zip_transform_fn zip_transform{}; } // namespace views + template + struct _Repeated_tuple_impl; + + template + struct _Repeated_tuple_impl<_Ty, index_sequence<_Indices...>> { + template + using _Repeat_type = _Ty; + + using type = tuple<_Repeat_type<_Indices>...>; + }; + + template + using _Repeated_tuple = typename _Repeated_tuple_impl<_Ty, make_index_sequence<_Nx>>::type; + + _EXPORT_STD template + requires view<_Vw> && (_Nx > 0) + class adjacent_view : public view_interface> { + private: + /* [[no_unique_address]] */ _Vw _Range{}; + + struct _As_sentinel { + explicit _As_sentinel() = default; + }; + + template + class _Iterator { + private: + friend adjacent_view; + + using _Base = _Maybe_const<_Const, _Vw>; + using _Base_iterator = iterator_t<_Base>; + + array<_Base_iterator, _Nx> _Current{}; + + constexpr _Iterator(_Base_iterator _First, sentinel_t<_Base> _Last) { + _Current.front() = _First; + for (size_t _Ix = 1; _Ix < _Nx; ++_Ix) { + _RANGES advance(_First, 1, _Last); + _Current[_Ix] = _First; + } + } + + constexpr _Iterator(_As_sentinel, _Base_iterator _First, _Base_iterator _Last) { + if constexpr (!bidirectional_range<_Base>) { + _Current.fill(_Last); + } else { + _Current.back() = _Last; + for (size_t _Ix = 1; _Ix < _Nx; ++_Ix) { + _RANGES advance(_Last, -1, _First); + _Current[_Nx - 1 - _Ix] = _Last; + } + } + } + + template + constexpr _Iterator(_Iterator& _Other, index_sequence<_Indices...>) noexcept( + is_nothrow_convertible_v, _Base_iterator>) + requires _Const && convertible_to, _Base_iterator> + : _Current{_STD move(_Other._Current[_Indices])...} {} + + public: + using iterator_category = input_iterator_tag; + using iterator_concept = conditional_t, random_access_iterator_tag, + conditional_t, bidirectional_iterator_tag, forward_iterator_tag>>; + using value_type = _Repeated_tuple, _Nx>; + using difference_type = range_difference_t<_Base>; + + _Iterator() = default; + + constexpr _Iterator(_Iterator _Other) noexcept( + is_nothrow_convertible_v, _Base_iterator>) // strengthened + requires _Const && convertible_to, _Base_iterator> + : _Iterator(_Other, make_index_sequence<_Nx>{}) {} + + _NODISCARD constexpr auto operator*() const { + return _RANGES _Tuple_transform([](auto& _It) -> decltype(auto) { return *_It; }, _Current); + } + + constexpr _Iterator& operator++() noexcept(noexcept(++_Current.front())) /* strengthened */ { + for (_Base_iterator& _It : _Current) { + ++_It; + } + return *this; + } + + constexpr _Iterator operator++(int) noexcept( + noexcept(++*this) && is_nothrow_copy_constructible_v<_Iterator>) /* strengthened */ { + auto _Tmp = *this; + ++*this; + return _Tmp; + } + + constexpr _Iterator& operator--() noexcept(noexcept(--_Current.back())) // strengthened + requires bidirectional_range<_Base> + { + for (_Base_iterator& _It : _Current) { + --_It; + } + return *this; + } + + constexpr _Iterator operator--(int) noexcept( + noexcept(--*this) && is_nothrow_copy_constructible_v<_Iterator>) // strengthened + requires bidirectional_range<_Base> + { + auto _Tmp = *this; + --*this; + return _Tmp; + } + + constexpr _Iterator& operator+=(const difference_type _Off) noexcept( + noexcept(_Current.back() += _Off)) // strengthened + requires random_access_range<_Base> + { + for (_Base_iterator& _It : _Current) { + _It += _Off; + } + return *this; + } + + constexpr _Iterator& operator-=(const difference_type _Off) noexcept( + noexcept(_Current.back() -= _Off)) // strengthened + requires random_access_range<_Base> + { + for (_Base_iterator& _It : _Current) { + _It -= _Off; + } + return *this; + } + + _NODISCARD constexpr auto operator[](const difference_type _Off) const + requires random_access_range<_Base> + { + return _RANGES _Tuple_transform([&](auto& _It) -> decltype(auto) { return _It[_Off]; }, _Current); + } + + _NODISCARD_FRIEND constexpr bool operator==(const _Iterator& _Left, const _Iterator& _Right) noexcept( + noexcept(_Fake_copy_init(_Left._Current.back() == _Right._Current.back()))) /* strengthened */ { + return _Left._Current.back() == _Right._Current.back(); + } + + _NODISCARD_FRIEND constexpr bool operator<(const _Iterator& _Left, const _Iterator& _Right) noexcept( + noexcept(_Fake_copy_init(_Left._Current.back() < _Right._Current.back()))) // strengthened + requires random_access_range<_Base> + { + return _Left._Current.back() < _Right._Current.back(); + } + + _NODISCARD_FRIEND constexpr bool operator>(const _Iterator& _Left, const _Iterator& _Right) noexcept( + noexcept(_Right < _Left)) // strengthened + requires random_access_range<_Base> + { + return _Right < _Left; + } + + _NODISCARD_FRIEND constexpr bool operator<=(const _Iterator& _Left, const _Iterator& _Right) noexcept( + noexcept(!(_Right < _Left))) // strengthened + requires random_access_range<_Base> + { + return !(_Right < _Left); + } + + _NODISCARD_FRIEND constexpr bool operator>=(const _Iterator& _Left, const _Iterator& _Right) noexcept( + noexcept(!(_Left < _Right))) // strengthened + requires random_access_range<_Base> + { + return !(_Left < _Right); + } + + _NODISCARD_FRIEND constexpr auto operator<=>(const _Iterator& _Left, const _Iterator& _Right) noexcept( + noexcept(_Left._Current.back() <=> _Right._Current.back())) // strengthened + requires random_access_range<_Base> && three_way_comparable<_Base_iterator> + { + return _Left._Current.back() <=> _Right._Current.back(); + } + + _NODISCARD_FRIEND constexpr _Iterator operator+(const _Iterator& _It, const difference_type _Off) noexcept( + noexcept(_STD declval<_Iterator&>() += _Off) + && is_nothrow_copy_constructible_v<_Iterator>) // strengthened + requires random_access_range<_Base> + { + auto _Tmp = _It; + _Tmp += _Off; + return _Tmp; + } + + _NODISCARD_FRIEND constexpr _Iterator operator+(const difference_type _Off, const _Iterator& _It) noexcept( + noexcept(_STD declval<_Iterator&>() += _Off) + && is_nothrow_copy_constructible_v<_Iterator>) // strengthened + requires random_access_range<_Base> + { + auto _Tmp = _It; + _Tmp += _Off; + return _Tmp; + } + + _NODISCARD_FRIEND constexpr _Iterator operator-(const _Iterator& _It, const difference_type _Off) noexcept( + noexcept(_STD declval<_Iterator&>() -= _Off) + && is_nothrow_copy_constructible_v<_Iterator>) // strengthened + requires random_access_range<_Base> + { + auto _Tmp = _It; + _Tmp -= _Off; + return _Tmp; + } + + _NODISCARD_FRIEND constexpr difference_type + operator-(const _Iterator& _Left, const _Iterator& _Right) noexcept( + noexcept(_Left._Current.back() - _Right._Current.back())) // strengthened + requires sized_sentinel_for<_Base_iterator, _Base_iterator> + { + return _Left._Current.back() - _Right._Current.back(); + } + + _NODISCARD_FRIEND constexpr auto iter_move(const _Iterator& _It) noexcept( + noexcept(_RANGES iter_move(_STD declval())) + && is_nothrow_move_constructible_v>) { + return _RANGES _Tuple_transform(_RANGES iter_move, _It._Current); + } + + friend constexpr void iter_swap(const _Iterator& _Left, const _Iterator& _Right) noexcept( + noexcept(_RANGES iter_swap(_STD declval<_Base_iterator>(), _STD declval<_Base_iterator>()))) + requires indirectly_swappable<_Base_iterator> + { + for (size_t _Ix = 0; _Ix < _Nx; ++_Ix) { + _RANGES iter_swap(_Left._Current[_Ix], _Right._Current[_Ix]); + } + } + }; + + template + class _Sentinel { + private: + friend adjacent_view; + + using _Base = _Maybe_const<_Const, _Vw>; + using _Base_sentinel = sentinel_t<_Base>; + + /* [[no_unique_address]] */ _Base_sentinel _End{}; + + constexpr explicit _Sentinel(_Base_sentinel _Last_) noexcept( + is_nothrow_move_constructible_v<_Base_sentinel>) // strengthened + : _End(_STD move(_Last_)) {} + + template + requires sentinel_for<_Base_sentinel, iterator_t<_Maybe_const<_OtherConst, _Vw>>> + _NODISCARD constexpr bool _Equal(const _Iterator<_OtherConst>& _It) const + noexcept(noexcept(_Fake_copy_init(_It._Current.back() == _End))) { + return _It._Current.back() == _End; + } + + template + requires sized_sentinel_for<_Base_sentinel, iterator_t<_Maybe_const<_OtherConst, _Vw>>> + _NODISCARD constexpr range_difference_t<_Maybe_const<_OtherConst, _Vw>> _Distance_from( + const _Iterator<_OtherConst>& _It) const noexcept(noexcept(_End - _It._Current.back())) { + return _End - _It._Current.back(); + } + + public: + _Sentinel() = default; + + constexpr _Sentinel(_Sentinel _Other) noexcept( + is_nothrow_convertible_v, _Base_sentinel>) // strengthened + requires _Const && convertible_to, _Base_sentinel> + : _End(_STD move(_Other._End)) {} + + template + requires sentinel_for<_Base_sentinel, iterator_t<_Maybe_const<_OtherConst, _Vw>>> + _NODISCARD_FRIEND constexpr bool operator==(const _Iterator<_OtherConst>& _It, + const _Sentinel& _Se) noexcept(noexcept(_Se._Equal(_It))) /* strengthened */ { + return _Se._Equal(_It); + } + + template + requires sized_sentinel_for<_Base_sentinel, iterator_t<_Maybe_const<_OtherConst, _Vw>>> + _NODISCARD_FRIEND constexpr range_difference_t<_Maybe_const<_OtherConst, _Vw>> operator-( + const _Iterator<_OtherConst>& _It, const _Sentinel& _Se) noexcept( // + noexcept(_Se._Distance_from(_It))) /* strengthened */ { + return -_Se._Distance_from(_It); + } + + template + requires sized_sentinel_for<_Base_sentinel, iterator_t<_Maybe_const<_OtherConst, _Vw>>> + _NODISCARD_FRIEND constexpr range_difference_t<_Maybe_const<_OtherConst, _Vw>> operator-( + const _Sentinel& _Se, const _Iterator<_OtherConst>& _It) noexcept( // + noexcept(_Se._Distance_from(_It))) /* strengthened */ { + return _Se._Distance_from(_It); + } + }; + + public: + // clang-format off + adjacent_view() requires default_initializable<_Vw> = default; + // clang-format on + + constexpr explicit adjacent_view(_Vw _Range_) noexcept(is_nothrow_move_constructible_v<_Vw>) // strengthened + : _Range(_STD move(_Range_)) {} + + _NODISCARD constexpr _Vw base() const& noexcept(is_nothrow_copy_constructible_v<_Vw>) // strengthened + requires copy_constructible<_Vw> + { + return _Range; + } + + _NODISCARD constexpr _Vw base() && noexcept(is_nothrow_move_constructible_v<_Vw>) /* strengthened */ { + return _STD move(_Range); + } + + _NODISCARD constexpr auto begin() + requires (!_Simple_view<_Vw>) + { + return _Iterator{_RANGES begin(_Range), _RANGES end(_Range)}; + } + + _NODISCARD constexpr auto begin() const + requires range + { + return _Iterator{_RANGES begin(_Range), _RANGES end(_Range)}; + } + + _NODISCARD constexpr auto end() + requires (!_Simple_view<_Vw>) + { + if constexpr (common_range<_Vw>) { + return _Iterator{_As_sentinel{}, _RANGES begin(_Range), _RANGES end(_Range)}; + } else { + return _Sentinel{_RANGES end(_Range)}; + } + } + + _NODISCARD constexpr auto end() const + requires range + { + if constexpr (common_range) { + return _Iterator{_As_sentinel{}, _RANGES begin(_Range), _RANGES end(_Range)}; + } else { + return _Sentinel{_RANGES end(_Range)}; + } + } + + _NODISCARD constexpr auto size() noexcept(noexcept(_RANGES size(_Range))) // strengthened + requires sized_range<_Vw> + { + using _Size_type = decltype(_RANGES size(_Range)); + using _Common_size_type = common_type_t<_Size_type, size_t>; + auto _Size = static_cast<_Common_size_type>(_RANGES size(_Range)); + _Size -= (_STD min)(_Size, static_cast<_Common_size_type>(_Nx - 1)); + return static_cast<_Size_type>(_Size); + } + + _NODISCARD constexpr auto size() const noexcept(noexcept(_RANGES size(_Range))) // strengthened + requires sized_range + { + using _Size_type = decltype(_RANGES size(_Range)); + using _Common_size_type = common_type_t<_Size_type, size_t>; + auto _Size = static_cast<_Common_size_type>(_RANGES size(_Range)); + _Size -= (_STD min)(_Size, static_cast<_Common_size_type>(_Nx - 1)); + return static_cast<_Size_type>(_Size); + } + }; + + template + inline constexpr bool enable_borrowed_range> = enable_borrowed_range<_Rng>; + + namespace views { + template + class _Adjacent_fn : public _Pipe::_Base<_Adjacent_fn<_Nx>> { + public: + template + _NODISCARD constexpr auto operator()(_Rng&& _Range) const + noexcept(noexcept(adjacent_view, _Nx>{_STD forward<_Rng>(_Range)})) + requires requires { adjacent_view, _Nx>{_STD forward<_Rng>(_Range)}; } + { + return adjacent_view, _Nx>{_STD forward<_Rng>(_Range)}; + } + + template + _NODISCARD constexpr auto operator()(_Rng&&) const noexcept + requires (_Nx == 0) + { + return empty_view>{}; + } + }; + + _EXPORT_STD template + inline constexpr _Adjacent_fn<_Nx> adjacent; + _EXPORT_STD inline constexpr _Adjacent_fn<2> pairwise; + } // namespace views + #ifdef __cpp_lib_ranges_to_container // clang-format off template diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index 9d2c2da1ff..0b6cdf1a02 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -335,7 +335,7 @@ // P2291R3 constexpr Integral // P2302R4 ranges::contains, ranges::contains_subrange // P2321R2 zip -// (missing views::adjacent and views::adjacent_transform) +// (missing views::adjacent_transform) // P2322R6 ranges::fold_left, ranges::fold_right, Etc. // P2387R3 Pipe Support For User-Defined Range Adaptors // P2404R3 Move-Only Types For Comparison Concepts diff --git a/tests/std/test.lst b/tests/std/test.lst index 157fecb668..fc8574d240 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -568,6 +568,7 @@ tests\P2278R4_views_as_const tests\P2302R4_ranges_alg_contains tests\P2302R4_ranges_alg_contains_subrange tests\P2321R2_proxy_reference +tests\P2321R2_views_adjacent tests\P2321R2_views_zip tests\P2321R2_views_zip_transform tests\P2322R6_ranges_alg_fold diff --git a/tests/std/tests/P2164R9_views_enumerate/test.cpp b/tests/std/tests/P2164R9_views_enumerate/test.cpp index 77f6c90c18..942f853dde 100644 --- a/tests/std/tests/P2164R9_views_enumerate/test.cpp +++ b/tests/std/tests/P2164R9_views_enumerate/test.cpp @@ -65,6 +65,7 @@ constexpr bool test_one(Rng&& rng, Expected&& expected) { // Validate borrowed_range STATIC_ASSERT(ranges::borrowed_range == ranges::borrowed_range); + // Validate range closure object // ... with lvalue argument STATIC_ASSERT(CanViewEnumerate == (!is_view || copy_constructible) ); if constexpr (CanViewEnumerate) { @@ -610,7 +611,7 @@ constexpr void instantiation_test() { #ifdef TEST_EVERYTHING test_in(); #else // ^^^ test all input range permutations / test only "interesting" permutations vvv - using test::Common, test::Sized, test::ProxyRef; + using test::Common, test::Sized; // When the base range is an input range, the view is sensitive to differencing instantiator::call>(); diff --git a/tests/std/tests/P2321R2_views_adjacent/env.lst b/tests/std/tests/P2321R2_views_adjacent/env.lst new file mode 100644 index 0000000000..8ac7033b20 --- /dev/null +++ b/tests/std/tests/P2321R2_views_adjacent/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\strict_concepts_latest_matrix.lst diff --git a/tests/std/tests/P2321R2_views_adjacent/test.cpp b/tests/std/tests/P2321R2_views_adjacent/test.cpp new file mode 100644 index 0000000000..bc993cf73b --- /dev/null +++ b/tests/std/tests/P2321R2_views_adjacent/test.cpp @@ -0,0 +1,950 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; + +template +concept CanViewAdjacent = requires(R&& r) { views::adjacent(forward(r)); }; + +template +concept CanConstructAdjacentView = requires(R&& r) { ranges::adjacent_view, N>{forward(r)}; }; + +template +struct repeated_tuple_impl; + +template +struct repeated_tuple_impl> { + template + using repeat_type = T; + + using type = tuple...>; +}; + +template +using repeated_tuple = typename repeated_tuple_impl>::type; + +STATIC_ASSERT(same_as, tuple<>>); +STATIC_ASSERT(same_as, tuple>); +STATIC_ASSERT(same_as, tuple>); + +// Check views::pairwise +STATIC_ASSERT(same_as)>); + +template +constexpr bool test_one(Rng&& rng, Expected&& expected) { + using ranges::adjacent_view, ranges::forward_range, ranges::bidirectional_range, ranges::random_access_range, + ranges::sized_range, ranges::common_range, ranges::iterator_t, ranges::sentinel_t, ranges::const_iterator_t, + ranges::const_sentinel_t, ranges::range_difference_t, ranges::range_value_t, ranges::range_reference_t; + + constexpr bool is_view = ranges::view>; + + using V = views::all_t; + using R = adjacent_view; + + STATIC_ASSERT(ranges::view); + STATIC_ASSERT(ranges::input_range); + STATIC_ASSERT(forward_range); + STATIC_ASSERT(bidirectional_range == bidirectional_range); + STATIC_ASSERT(random_access_range == random_access_range); + STATIC_ASSERT(!ranges::contiguous_range); + + // Check default-initializability + STATIC_ASSERT(default_initializable == default_initializable); + + // Check borrowed_range + STATIC_ASSERT(ranges::borrowed_range == ranges::borrowed_range); + + // Check range closure object + constexpr auto closure = views::adjacent; + + // ... with lvalue argument + STATIC_ASSERT(CanViewAdjacent == (!is_view || copy_constructible) ); + if constexpr (CanViewAdjacent) { + constexpr bool is_noexcept = !is_view || is_nothrow_copy_constructible_v; + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(closure(rng)) == is_noexcept); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(rng | closure) == is_noexcept); + } + + // ... with const lvalue argument + STATIC_ASSERT(CanViewAdjacent&, N> == (!is_view || copy_constructible) ); + if constexpr (CanViewAdjacent&, N>) { + using RC = adjacent_view&>, N>; + constexpr bool is_noexcept = !is_view || is_nothrow_copy_constructible_v; + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(closure(as_const(rng))) == is_noexcept); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(as_const(rng) | closure) == is_noexcept); + } + + // ... with rvalue argument + STATIC_ASSERT(CanViewAdjacent, N> == (is_view || movable>) ); + if constexpr (CanViewAdjacent, N>) { + using RS = adjacent_view>, N>; + constexpr bool is_noexcept = is_nothrow_move_constructible_v; + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(closure(move(rng))) == is_noexcept); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(move(rng) | closure) == is_noexcept); + } + + // ... with const rvalue argument + STATIC_ASSERT(CanViewAdjacent, N> == (is_view && copy_constructible) ); + if constexpr (CanViewAdjacent, N>) { + constexpr bool is_noexcept = is_nothrow_copy_constructible_v; + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(closure(move(as_const(rng)))) == is_noexcept); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(move(as_const(rng)) | closure) == is_noexcept); + } + + R r{forward(rng)}; + + // Check adjacent_view::size + STATIC_ASSERT(CanMemberSize == sized_range); + if constexpr (CanMemberSize) { + same_as> auto s = r.size(); + assert(_To_unsigned_like(s) == ranges::size(expected)); + STATIC_ASSERT(noexcept(r.size()) == noexcept(ranges::size(rng))); // strengthened + } + + // Check adjacent_view::size (const) + STATIC_ASSERT(CanMemberSize == sized_range); + if constexpr (CanMemberSize) { + same_as> auto s = as_const(r).size(); + assert(_To_unsigned_like(s) == ranges::size(expected)); + STATIC_ASSERT(noexcept(as_const(r).size()) == noexcept(ranges::size(as_const(rng)))); // strengthened + } + + const bool is_empty = ranges::empty(expected); + + // Check view_interface::empty and operator bool + STATIC_ASSERT(CanMemberEmpty); + STATIC_ASSERT(CanBool == CanEmpty); + if constexpr (CanMemberEmpty) { + assert(r.empty() == is_empty); + assert(static_cast(r) == !is_empty); + } + + // Check view_interface::empty and operator bool (const) + STATIC_ASSERT(CanMemberEmpty); + STATIC_ASSERT(CanBool == CanEmpty); + if constexpr (CanMemberEmpty) { + assert(as_const(r).empty() == is_empty); + assert(static_cast(as_const(r)) == !is_empty); + } + + // Check content + assert(ranges::equal(r, expected)); + + // Check adjacent_view::begin + STATIC_ASSERT(CanMemberBegin); + { + const same_as> auto i = r.begin(); + if (!is_empty) { + assert(*i == *begin(expected)); + } + + if constexpr (copy_constructible) { + auto r2 = r; + const same_as> auto i2 = r2.begin(); + if (!is_empty) { + assert(*i2 == *i); + } + } + } + + // Check adjacent_view::begin (const) + STATIC_ASSERT(CanMemberBegin == ranges::range); + if constexpr (CanMemberBegin) { + const same_as> auto ci = as_const(r).begin(); + if (!is_empty) { + assert(*ci == *begin(expected)); + } + + if constexpr (copy_constructible) { + const auto cr2 = r; + const same_as> auto ci2 = cr2.begin(); + if (!is_empty) { + assert(*ci2 == *ci); + } + } + } + + // Check adjacent_view::end + STATIC_ASSERT(CanMemberEnd); + { + const same_as> auto s = r.end(); + assert((r.begin() == s) == is_empty); + + if constexpr (sentinel_for, iterator_t>) { + assert((as_const(r).begin() == s) == is_empty); + } + + STATIC_ASSERT(common_range == common_range); + if constexpr (common_range && bidirectional_range) { + if (!is_empty) { + assert(*prev(s) == *prev(end(expected))); + } + + if constexpr (copy_constructible) { + auto r2 = r; + if (!is_empty) { + assert(*prev(r2.end()) == *prev(end(expected))); + } + } + } + } + + // Check adjacent_view::end (const) + STATIC_ASSERT(CanMemberEnd == ranges::range); + if constexpr (CanMemberEnd) { + const same_as> auto cs = as_const(r).end(); + assert((as_const(r).begin() == cs) == is_empty); + + if constexpr (sentinel_for, iterator_t>) { + assert((r.begin() == cs) == is_empty); + } + + STATIC_ASSERT(common_range == common_range); + if constexpr (common_range && bidirectional_range) { + if (!is_empty) { + assert(*prev(cs) == *prev(end(expected))); + } + + if constexpr (copy_constructible) { + const auto r2 = r; + if (!is_empty) { + assert(*prev(r2.end()) == *prev(end(expected))); + } + } + } + } + + // Check view_interface::cbegin + STATIC_ASSERT(CanMemberCBegin); + STATIC_ASSERT(CanMemberCBegin == ranges::range); + { + const same_as> auto i = r.cbegin(); + if (!is_empty) { + assert(*i == *cbegin(expected)); + } + + if constexpr (copyable) { + auto r2 = r; + const same_as> auto i2 = r2.cbegin(); + if (!is_empty) { + assert(*i2 == *i); + } + } + + if constexpr (CanCBegin) { + const same_as> auto i3 = as_const(r).cbegin(); + if (!is_empty) { + assert(*i3 == *i); + } + } + } + + // Check view_interface::cend + STATIC_ASSERT(CanMemberCEnd); + STATIC_ASSERT(CanMemberCEnd == ranges::range); + if (!is_empty) { + same_as> auto i = r.cend(); + if constexpr (common_range && sized_range && bidirectional_range) { + assert(*prev(i) == *prev(cend(expected))); + } + + if constexpr (CanCEnd) { + same_as> auto i2 = as_const(r).cend(); + if constexpr (common_range && sized_range && bidirectional_range) { + assert(*prev(i2) == *prev(cend(expected))); + } + } + } + + // Check view_interface::data + STATIC_ASSERT(!CanData); + STATIC_ASSERT(!CanData); + + if (is_empty) { + return true; + } + + // Check view_interface::operator[] + STATIC_ASSERT(CanIndex == random_access_range); + if constexpr (CanIndex) { + assert(r[0] == expected[0]); + } + + // Check view_interface::operator[] (const) + STATIC_ASSERT(CanIndex == random_access_range); + if constexpr (CanIndex) { + assert(as_const(r)[0] == expected[0]); + } + + // Check view_interface::front + STATIC_ASSERT(CanMemberFront == forward_range); + if constexpr (CanMemberFront) { + assert(r.front() == *begin(expected)); + } + + // Check view_interface::front (const) + STATIC_ASSERT(CanMemberFront == forward_range); + if constexpr (CanMemberFront) { + assert(as_const(r).front() == *begin(expected)); + } + + // Check view_interface::back + STATIC_ASSERT(CanMemberBack == (bidirectional_range && common_range) ); + if constexpr (CanMemberBack) { + assert(r.back() == *prev(end(expected))); + } + + // Check view_interface::back (const) + STATIC_ASSERT(CanMemberBack == (bidirectional_range && common_range) ); + if constexpr (CanMemberBack) { + assert(as_const(r).back() == *prev(end(expected))); + } + + { // Check adjacent_view::iterator + using BI = iterator_t; + using I = iterator_t; + STATIC_ASSERT(forward_iterator); + + // Check iterator_category + STATIC_ASSERT(same_as); + + // Check iterator_concept + using IterConcept = typename I::iterator_concept; + STATIC_ASSERT(random_access_range == same_as); + STATIC_ASSERT( + (bidirectional_range && !random_access_range) == same_as); + STATIC_ASSERT((forward_range && !bidirectional_range) == same_as); + + // Check value_type + STATIC_ASSERT(same_as, N>>); + + // Check default-initializability + STATIC_ASSERT(default_initializable); + + auto i = r.begin(); + + { // Check dereference + same_as, N>> decltype(auto) v = *as_const(i); + assert(v == expected[0]); + } + + { // Check pre-incrementation + same_as decltype(auto) i2 = ++i; + assert(&i2 == &i); + if (i != r.end()) { +#pragma warning(suppress : 28020) // The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call + assert(*i == expected[1]); + } + i = r.begin(); + } + + { // Check post-incrementation + same_as decltype(auto) i2 = i++; + assert(*i2 == expected[0]); + if (i != r.end()) { +#pragma warning(suppress : 28020) // The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call + assert(*i == expected[1]); + } + i = r.begin(); + } + + { // Check equality comparisons + auto i2 = i; + same_as auto b1 = i == i2; + assert(b1); + ++i2; + same_as auto b2 = i != i2; + assert(b2); + } + + if constexpr (bidirectional_range) { + { // Check pre-decrementation + i = ranges::next(r.begin()); + + same_as decltype(auto) i2 = --i; + assert(&i2 == &i); + assert(*i2 == expected[0]); + } + + { // Check post-decrementation + i = ranges::next(r.begin()); + + same_as decltype(auto) i2 = i--; + if (i2 != r.end()) { +#pragma warning(suppress : 28020) // The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call + assert(*i2 == expected[1]); + } + assert(*i == expected[0]); + } + } + + if constexpr (random_access_range) { + { // Check advancement operators + same_as decltype(auto) i2 = (i += 1); + assert(&i2 == &i); + if (i != r.end()) { +#pragma warning(suppress : 28020) // The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call + assert(*i == expected[1]); + } + + same_as decltype(auto) i3 = (i -= 1); + assert(&i3 == &i); + assert(*i == expected[0]); + } + + { // Check subscript operator + same_as, N>> decltype(auto) v = i[0]; + assert(v == expected[0]); + } + + { // Check other comparisons + auto i2 = ranges::next(i); + same_as auto b1 = i < i2; + assert(b1); + same_as auto b2 = i2 > i; + assert(b2); + same_as auto b3 = i <= i2; + assert(b3); + same_as auto b4 = i2 >= i; + assert(b4); + } + + if constexpr (three_way_comparable) { // Check 3way comparisons + using Cat = compare_three_way_result_t; + auto i2 = i; + same_as auto cmp1 = i <=> i2; + assert(cmp1 == Cat::equivalent); + ++i2; + assert((i <=> i2) == Cat::less); + assert((i2 <=> i) == Cat::greater); + } + + { // Check operator+ + same_as auto i2 = i + 1; + if (i2 != r.end()) { +#pragma warning(suppress : 28020) // The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call + assert(*i2 == expected[1]); + } + + same_as auto i3 = 1 + i; + if (i3 != r.end()) { +#pragma warning(suppress : 28020) // The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call + assert(*i3 == expected[1]); + } + } + + { // Check operator-(Iter, Diff) + same_as auto i2 = ranges::next(i) - 1; + assert(*i2 == expected[0]); + } + } + + if constexpr (sized_sentinel_for) { // Check differencing + same_as> auto diff = i - i; + assert(diff == 0); + assert((i - ranges::next(i)) == -1); + assert((ranges::next(i) - i) == 1); + } + + if constexpr (sized_sentinel_for, I>) { // Check differencing with sentinel + const auto s = r.end(); + const auto size = ranges::ssize(expected); + const same_as> auto diff1 = i - s; + assert(diff1 == -size); + const same_as> auto diff2 = s - i; + assert(diff2 == size); + } + + if constexpr (sized_sentinel_for, I>) { // Check differencing with sentinel + const auto s = as_const(r).end(); + const auto size = ranges::ssize(expected); + const same_as> auto diff1 = i - s; + assert(diff1 == -size); + const same_as> auto diff2 = s - i; + assert(diff2 == size); + } + + { // Check iter_move + same_as, N>> decltype(auto) rval = iter_move(as_const(i)); + assert(rval == expected[0]); + STATIC_ASSERT(noexcept(iter_move(i)) + == (noexcept(ranges::iter_move(declval())) + && is_nothrow_move_constructible_v>) ); + } + + if constexpr (indirectly_swappable) { // Check iter_swap + STATIC_ASSERT(is_void_v); + STATIC_ASSERT( + noexcept(iter_swap(i, i)) == noexcept(ranges::iter_swap(declval(), declval()))); + // Note: other tests are defined in 'test_iter_swap' function + } + } + + // Check adjacent_view::iterator + if constexpr (CanMemberBegin) { + using CBI = iterator_t; + using CI = iterator_t; + STATIC_ASSERT(forward_iterator); + + // Check iterator_category + STATIC_ASSERT(same_as); + + // Check iterator_concept + using IterConcept = typename CI::iterator_concept; + STATIC_ASSERT(random_access_range == same_as); + STATIC_ASSERT((bidirectional_range && !random_access_range) + == same_as); + STATIC_ASSERT( + (forward_range && !bidirectional_range) == same_as); + + // Check value_type + STATIC_ASSERT(same_as, N>>); + + // Check default-initializability + STATIC_ASSERT(default_initializable); + + iterator_t i = r.begin(); + + // Check construction from non-const iterator + CI ci = i; + + { // Check dereference + same_as, N>> decltype(auto) v = *as_const(ci); + assert(v == expected[0]); + } + + { // Check pre-incrementation + same_as decltype(auto) ci2 = ++ci; + assert(&ci2 == &ci); + if (ci != r.end()) { +#pragma warning(suppress : 28020) // The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call + assert(*ci == expected[1]); + } + ci = r.begin(); + } + + { // Check post-incrementation + same_as decltype(auto) ci2 = ci++; + assert(*ci2 == expected[0]); + if (ci != r.end()) { +#pragma warning(suppress : 28020) // The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call + assert(*ci == expected[1]); + } + ci = r.begin(); + } + + { // Check equality comparisons + auto ci2 = ci; + same_as auto b1 = ci == ci2; + assert(b1); + ++ci2; + same_as auto b2 = ci != ci2; + assert(b2); + } + + { // Check equality comparisons with non-const iterators + same_as auto b1 = ci == i; + assert(b1); + ++i; + same_as auto b2 = ci != i; + assert(b2); + i = r.begin(); + } + + if constexpr (bidirectional_range) { + { // Check pre-decrementation + ci = ranges::next(r.begin()); + + same_as decltype(auto) ci2 = --ci; + assert(&ci2 == &ci); + assert(*ci2 == expected[0]); + } + + { // Check post-decrementation + ci = ranges::next(r.begin()); + + same_as decltype(auto) ci2 = ci--; + if (ci2 != r.end()) { +#pragma warning(suppress : 28020) // The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call + assert(*ci2 == expected[1]); + } + assert(*ci == expected[0]); + } + } + + if constexpr (random_access_range) { + { // Check advancement operators + same_as decltype(auto) ci2 = (ci += 1); + assert(&ci2 == &ci); + if (ci != r.end()) { +#pragma warning(suppress : 28020) // The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call + assert(*ci == expected[1]); + } + + same_as decltype(auto) ci3 = (ci -= 1); + assert(&ci3 == &ci); + assert(*ci == expected[0]); + } + + { // Check subscript operator + same_as, N>> decltype(auto) v = ci[0]; + assert(v == expected[0]); + } + + { // Check comparisons + auto ci2 = ranges::next(ci); + same_as auto b1 = ci < ci2; + assert(b1); + same_as auto b2 = ci2 > ci; + assert(b2); + same_as auto b3 = ci <= ci2; + assert(b3); + same_as auto b4 = ci2 >= ci; + assert(b4); + } + + { // Check comparisons with non-const iterators + ++i; + same_as auto b1 = ci < i; + assert(b1); + same_as auto b2 = i > ci; + assert(b2); + same_as auto b3 = ci <= i; + assert(b3); + same_as auto b4 = i >= ci; + assert(b4); + --i; + } + + if constexpr (three_way_comparable) { // Check 3way comparisons + using Cat = compare_three_way_result_t; + auto ci2 = ci; + same_as auto cmp1 = ci <=> ci2; + assert(cmp1 == Cat::equivalent); + ++ci2; + assert((ci <=> ci2) == Cat::less); + assert((ci2 <=> ci) == Cat::greater); + } + + if constexpr (three_way_comparable) { // Check 3way comparisons with non-const iterators + using Cat = compare_three_way_result_t; + same_as auto cmp1 = ci <=> i; + assert(cmp1 == Cat::equivalent); + ++i; + assert((ci <=> i) == Cat::less); + assert((i <=> ci) == Cat::greater); + --i; + } + + { // Check operator+ + same_as auto ci2 = ci + 1; + if (ci2 != r.end()) { +#pragma warning(suppress : 28020) // The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call + assert(*ci2 == expected[1]); + } + + same_as auto ci3 = 1 + ci; + if (ci3 != r.end()) { +#pragma warning(suppress : 28020) // The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call + assert(*ci3 == expected[1]); + } + } + + { // Check operator-(Iter, Diff) + same_as auto ci2 = ranges::next(ci) - 1; + assert(*ci2 == expected[0]); + } + } + + if constexpr (sized_sentinel_for) { // Check differencing + same_as> auto diff = ci - ci; + assert(diff == 0); + assert((ci - ranges::next(ci)) == -1); + assert((ranges::next(ci) - ci) == 1); + } + + if constexpr (sized_sentinel_for, CI>) { // Check differencing with sentinel + const auto s = r.end(); + const auto size = ranges::ssize(expected); + const same_as> auto diff1 = ci - s; + assert(diff1 == -size); + const same_as> auto diff2 = s - ci; + assert(diff2 == size); + } + + if constexpr (sized_sentinel_for, CI>) { // Check differencing with sentinel + const auto s = as_const(r).end(); + const auto size = ranges::ssize(expected); + const same_as> auto diff1 = ci - s; + assert(diff1 == -size); + const same_as> auto diff2 = s - ci; + assert(diff2 == size); + } + + { // Check iter_move + same_as, N>> decltype(auto) rval = iter_move(as_const(ci)); + assert(rval == expected[0]); + STATIC_ASSERT(noexcept(iter_move(ci)) + == (noexcept(ranges::iter_move(declval())) + && is_nothrow_move_constructible_v>) ); + } + + if constexpr (indirectly_swappable) { // Check iter_swap + STATIC_ASSERT(is_void_v); + STATIC_ASSERT(noexcept(iter_swap(ci, ci)) + == noexcept(ranges::iter_swap(declval(), declval()))); + // Note: other tests are defined in 'test_iter_swap' function + } + } + + // Check adjacent_view::base() const& + STATIC_ASSERT(CanMemberBase == copy_constructible); + if constexpr (copy_constructible) { + [[maybe_unused]] same_as auto b1 = as_const(r).base(); + STATIC_ASSERT(noexcept(as_const(r).base()) == is_nothrow_copy_constructible_v); // strengthened + } + + // Check adjacent_view::base() && + [[maybe_unused]] same_as auto b2 = move(r).base(); + STATIC_ASSERT(noexcept(move(r).base()) == is_nothrow_move_constructible_v); // strengthened + + return true; +} + +template +constexpr void test_adjacent0(Rng&& rng) { + STATIC_ASSERT(!CanConstructAdjacentView); + using V = views::all_t; + using E = ranges::empty_view>; + + // Check range closure object + constexpr auto closure = views::adjacent<0>; + constexpr bool is_view = ranges::view>; + + // ... with lvalue argument + STATIC_ASSERT(CanViewAdjacent == (!is_view || copy_constructible) ); + if constexpr (CanViewAdjacent) { + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(closure(rng))); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(rng | closure)); + } + + // ... with const lvalue argument + STATIC_ASSERT(CanViewAdjacent&, 0> == (!is_view || copy_constructible) ); + if constexpr (CanViewAdjacent&, 0>) { + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(closure(as_const(rng)))); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(as_const(rng) | closure)); + } + + // ... with rvalue argument + STATIC_ASSERT(CanViewAdjacent, 0> == (is_view || movable>) ); + if constexpr (CanViewAdjacent, 0>) { + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(closure(move(rng)))); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(move(rng) | closure)); + } + + // ... with const rvalue argument + STATIC_ASSERT(CanViewAdjacent, 0> == (is_view && copy_constructible) ); + if constexpr (CanViewAdjacent, 0>) { + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(closure(move(as_const(rng))))); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(move(as_const(rng)) | closure)); + } +} + +template + requires indirectly_swappable> +constexpr void test_iter_swap(Rng& rng) { + // This test assumes that 'ranges::size(views::adjacent(rng))' is at least 2 + auto r = views::adjacent(rng); + + { // Check iter_swap for adjacent_view::iterator + auto i = r.begin(); + auto first = *i; + auto j = ranges::next(i); + auto second = *j; + + // It takes N+1 swaps to get to initial state + for ([[maybe_unused]] size_t _ : views::iota(0u, N)) { + iter_swap(as_const(i), as_const(j)); + } + ranges::iter_swap(as_const(i), as_const(j)); + + assert(*i == first); + assert(*j == second); + } + + // Check iter_swap for adjacent_view::iterator + if constexpr (CanMemberBegin && indirectly_swappable>) { + auto i = as_const(r).begin(); + auto first = *i; + auto j = ranges::next(i); + auto second = *j; + + // It takes N+1 swaps to get to initial state + for ([[maybe_unused]] size_t _ : views::iota(0u, N)) { + iter_swap(as_const(i), as_const(j)); + } + ranges::iter_swap(as_const(i), as_const(j)); + + assert(*i == first); + assert(*j == second); + } +} + +// 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_result = to_array>({{1}, {3}, {5}, {7}, {11}, {13}}); +constexpr auto pairwise_result = to_array>({{1, 3}, {3, 5}, {5, 7}, {7, 11}, {11, 13}}); +constexpr auto adjacent3_result = to_array>({{1, 3, 5}, {3, 5, 7}, {5, 7, 11}, {7, 11, 13}}); +constexpr auto adjacent4_result = to_array>({{1, 3, 5, 7}, {3, 5, 7, 11}, {5, 7, 11, 13}}); +constexpr auto adjacent5_result = to_array>({{1, 3, 5, 7, 11}, {3, 5, 7, 11, 13}}); +constexpr auto adjacent6_result = to_array>({{1, 3, 5, 7, 11, 13}}); +constexpr array, 0> adjacent7_result; + +template +using test_range = + test::range}, + IsCommon, test::CanCompare{derived_from || IsCommon == test::Common::yes}, + test::ProxyRef{!derived_from}>; + +struct instantiator { +#ifdef TEST_EVERYTHING + template + static constexpr void call() { + R r{some_ints}; + test_one<1>(r, adjacent1_result); + test_one<2>(r, pairwise_result); + test_one<4>(r, adjacent4_result); + test_one<7>(r, adjacent7_result); + test_adjacent0(r); + + // We cannot invoke 'test_iter_swap' in 'TEST_EVERYTHING' mode. + } +#else // ^^^ test all input range permutations / test only "interesting" permutations vvv + template + static constexpr void call() { + test_range r{some_ints}; + test_one<1>(r, adjacent1_result); + test_one<2>(r, pairwise_result); + test_one<4>(r, adjacent4_result); + test_one<7>(r, adjacent7_result); + test_adjacent0(r); + + int swap_check[4] = {1, 2, 3, 4}; + test_range swap_check_r{swap_check}; + test_iter_swap<2>(swap_check_r); + test_iter_swap<3>(swap_check_r); + } +#endif // TEST_EVERYTHING +}; + +constexpr void instantiation_test() { +#ifdef TEST_EVERYTHING + test_fwd(); +#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(); + instantiator::call(); + instantiator::call(); + instantiator::call(); + + instantiator::call(); + instantiator::call(); + instantiator::call(); + instantiator::call(); + + instantiator::call(); + instantiator::call(); + instantiator::call(); + instantiator::call(); + + instantiator::call(); + instantiator::call(); + instantiator::call(); + instantiator::call(); +#endif // TEST_EVERYTHING +} + +template > +using move_only_view = test::range}, + test::CanView::yes, test::Copyability::move_only>; + +int main() { + { // Check views + // ... copyable + constexpr span s{some_ints}; + STATIC_ASSERT(test_one<4>(s, adjacent4_result)); + test_one<4>(s, adjacent4_result); + } + + { // ... move-only + test_one<1>(move_only_view{some_ints}, adjacent1_result); + test_one<2>(move_only_view{some_ints}, pairwise_result); + test_one<3>(move_only_view{some_ints}, adjacent3_result); + test_one<4>(move_only_view{some_ints}, adjacent4_result); + test_one<5>(move_only_view{some_ints}, adjacent5_result); + test_one<6>(move_only_view{some_ints}, adjacent6_result); + } + + { // Check non-views + STATIC_ASSERT(test_one<2>(some_ints, pairwise_result)); + test_one<2>(some_ints, pairwise_result); + + auto vec = some_ints | ranges::to(); + test_one<3>(vec, adjacent3_result); + + auto lst = some_ints | ranges::to(); + test_one<4>(lst, adjacent4_result); + } + + { // Check empty range + STATIC_ASSERT(test_one<3>(span{}, span>{})); + test_one<3>(span{}, span>{}); + } + + STATIC_ASSERT((instantiation_test(), true)); + instantiation_test(); +}