diff --git a/stl/inc/ranges b/stl/inc/ranges index 1720c95babd..59322d8aff4 100644 --- a/stl/inc/ranges +++ b/stl/inc/ranges @@ -2371,6 +2371,352 @@ namespace ranges { inline constexpr _Common_fn common; } // namespace views + // CLASS TEMPLATE ranges::join_view + // clang-format off + template + requires view<_Vw> && input_range> + && (is_reference_v> || view>) + class join_view : public view_interface> { + // clang-format on + private: + using _InnerRng = range_reference_t<_Vw>; + /* [[no_unique_address]] */ _Vw _Range{}; + /* [[no_unique_address]] */ views::all_t<_InnerRng> _Inner{}; + + template + class _Sentinel; + + template // TRANSITION, LWG-3289 + struct _Category_base {}; + + // clang-format off + template + requires _Has_member_iterator_category<_InnerTraits> && _Has_member_iterator_category<_OuterTraits> + struct _Category_base<_OuterTraits, _InnerTraits, _Deref_is_ref> { + using iterator_category = + conditional_t<_Deref_is_ref + && derived_from + && derived_from, + bidirectional_iterator_tag, + conditional_t<_Deref_is_ref + && derived_from + && derived_from, + forward_iterator_tag, + conditional_t + && derived_from, + input_iterator_tag, + output_iterator_tag>>>; + }; + // clang-format on + + template + class _Iterator : public _Category_base>>, + iterator_traits>>>, + is_reference_v>>> { + private: + template + friend class _Iterator; + template + friend class _Sentinel; + + using _Parent_t = _Maybe_const<_Const, join_view>; + using _Base = _Maybe_const<_Const, _Vw>; + using _SubRng = range_reference_t<_Base>; + using _OuterIter = iterator_t<_Base>; + using _InnerIter = iterator_t<_SubRng>; + + static constexpr bool _Deref_is_ref = is_reference_v<_SubRng>; + + _OuterIter _Outer{}; + _InnerIter _Inner{}; + _Parent_t* _Parent{}; + + constexpr void _Satisfy() { + auto _Update_inner = [this](_SubRng _Range) -> auto& { + if constexpr (_Deref_is_ref) { + return _Range; + } else { + return (_Parent->_Inner = views::all(_STD move(_Range))); + } + }; + + for (; _Outer != _RANGES end(_Parent->_Range); ++_Outer) { + auto& _Tmp = _Update_inner(*_Outer); + _Inner = _RANGES begin(_Tmp); + if (_Inner != _RANGES end(_Tmp)) { + return; + } + } + if constexpr (_Deref_is_ref) { + _Inner = _InnerIter{}; + } + } + +#if _ITERATOR_DEBUG_LEVEL != 0 + constexpr void _Check_dereference() const noexcept { + _STL_VERIFY(_Parent != nullptr, "cannot dereference value-initialized join_view iterator"); + _STL_VERIFY(_Outer != _RANGES end(_Parent->_Range), "cannot dereference end join_view iterator"); + _STL_VERIFY(_Inner != _RANGES end(_Parent->_Inner), "cannot dereference end join_view iterator"); + } +#endif // _ITERATOR_DEBUG_LEVEL != 0 + +#if _ITERATOR_DEBUG_LEVEL != 0 + constexpr void _Same_range(const _Iterator& _Right) const noexcept { + _STL_VERIFY(_Parent == _Right._Parent, "cannot compare incompatible join_view iterators"); + } +#endif // _ITERATOR_DEBUG_LEVEL != 0 + + public: + using iterator_concept = + conditional_t<_Deref_is_ref && bidirectional_range<_Base> && bidirectional_range<_SubRng>, + bidirectional_iterator_tag, + conditional_t<_Deref_is_ref && forward_range<_Base> && forward_range<_SubRng>, forward_iterator_tag, + input_iterator_tag>>; + using value_type = range_value_t<_SubRng>; + using difference_type = common_type, range_difference_t<_SubRng>>; + + _Iterator() = default; + + constexpr _Iterator(_Parent_t& _Parent_, _OuterIter _Outer_) + : _Outer{_STD move(_Outer_)}, _Parent{_STD addressof(_Parent_)} { +#if _ITERATOR_DEBUG_LEVEL != 0 + _Adl_verify_range(_Outer, _RANGES end(_Parent_._Range)); + if constexpr (forward_range<_Base>) { + _Adl_verify_range(_RANGES begin(_Parent_._Range), _Outer); + } +#endif // _ITERATOR_DEBUG_LEVEL != 0 + _Satisfy(); + } + + // clang-format off + constexpr _Iterator(_Iterator _It) + requires _Const && convertible_to, _OuterIter> + && convertible_to, _InnerIter> + : _Outer{_STD move(_It._Outer)}, _Inner{_STD move(_It._Inner)}, _Parent{_It._Parent} {} + // clang-format on + + _NODISCARD constexpr decltype(auto) operator*() const noexcept(noexcept(*_Inner)) /* strengthened */ { +#if _ITERATOR_DEBUG_LEVEL != 0 + _Check_dereference(); +#endif // _ITERATOR_DEBUG_LEVEL != 0 + return *_Inner; + } + + _NODISCARD constexpr _InnerIter operator->() const // Per resolution of LWG-XXXX + noexcept(is_nothrow_copy_constructible_v<_InnerIter>) /* strengthened */ + requires _Has_arrow<_InnerIter>&& copyable<_InnerIter> { +#if _ITERATOR_DEBUG_LEVEL != 0 + _Check_dereference(); +#endif // _ITERATOR_DEBUG_LEVEL != 0 + return _Inner; + } + + constexpr _Iterator& operator++() { + if constexpr (_Deref_is_ref) { + if (++_Inner == _RANGES end(*_Outer)) { + ++_Outer; + _Satisfy(); + } + return *this; + } else { + if (++_Inner == _RANGES end(_Parent->_Inner)) { + ++_Outer; + _Satisfy(); + } + return *this; + } + } + + constexpr void operator++(int) { + ++*this; + } + + // clang-format off + constexpr decltype(auto) operator++(int) + requires _Deref_is_ref && forward_range<_Base>&& forward_range<_SubRng> { + // clang-format on + auto _Tmp = *this; + ++*this; + return _Tmp; + } + + // clang-format off + constexpr _Iterator& operator--() + requires _Deref_is_ref && bidirectional_range<_Base> && bidirectional_range<_SubRng> + && common_range<_SubRng> { + // clang-format on + if (_Outer == _RANGES end(_Parent->_Range)) { + _Inner = _RANGES end(*--_Outer); + } + while (_Inner == _RANGES begin(*_Outer)) { + _Inner = _RANGES end(*--_Outer); + } + --_Inner; + return *this; + } + + // clang-format off + constexpr _Iterator& operator--(int) + requires _Deref_is_ref && bidirectional_range<_Base> && bidirectional_range<_SubRng> + && common_range<_SubRng> { + // clang-format on + auto _Tmp = *this; + --*this; + return _Tmp; + } + + // clang-format off + _NODISCARD friend constexpr bool operator==(const _Iterator& _Left, const _Iterator& _Right) noexcept( + noexcept(_Left._Outer == _Right._Outer && _Left._Inner == _Right._Inner)) /* strengthened */ + requires _Deref_is_ref && equality_comparable<_OuterIter> && equality_comparable<_InnerIter> { + // clang-format on +#if _ITERATOR_DEBUG_LEVEL != 0 + _Left._Same_range(_Right); +#endif // _ITERATOR_DEBUG_LEVEL != 0 + return _Left._Outer == _Right._Outer && _Left._Inner == _Right._Inner; + } + + _NODISCARD friend constexpr decltype(auto) iter_move(const _Iterator& _It) noexcept( + noexcept(_RANGES iter_move(_It._Inner))) { +#if _ITERATOR_DEBUG_LEVEL != 0 + _It._Check_dereference(); +#endif // _ITERATOR_DEBUG_LEVEL != 0 + return _RANGES iter_move(_It._Inner); + } + + friend constexpr void iter_swap(const _Iterator& _Left, const _Iterator& _Right) noexcept( + noexcept(ranges::iter_swap(_Left._Inner, _Right._Inner))) requires indirectly_swappable<_InnerIter> { +#if _ITERATOR_DEBUG_LEVEL != 0 + _Left._Check_dereference(); + _Right._Check_dereference(); +#endif // _ITERATOR_DEBUG_LEVEL != 0 + _RANGES iter_swap(_Left._Inner, _Right._Inner); + } + }; + + template + class _Sentinel { + private: + template + friend class _Iterator; + template + friend class _Sentinel; + + using _Parent_t = _Maybe_const<_Const, join_view>; + using _Base = _Maybe_const<_Const, _Vw>; + + template + using _Maybe_const_iter = iterator_t<_Maybe_const<_OtherConst, _Vw>>; + + sentinel_t<_Base> _Last{}; + + template + _NODISCARD static constexpr const _Maybe_const_iter<_OtherConst>& _Get_current( + const _Iterator<_OtherConst>& _It) noexcept { + return _It._Current; + } + + public: + _Sentinel() = default; + // clang-format off + constexpr explicit _Sentinel(_Parent_t& _Parent) noexcept(noexcept(_RANGES end(_STD declval<_Base>())) + && is_nothrow_move_constructible_v>) // strengthened + : _Last(_RANGES end(_Parent._Range)) {} + + constexpr _Sentinel(_Sentinel _Se) + noexcept(is_nothrow_constructible_v, sentinel_t<_Vw>>) // strengthened + requires _Const && convertible_to, sentinel_t<_Base>> + : _Last(_STD move(_Se._Last)) {} + + template + requires sentinel_for, _Maybe_const_iter<_OtherConst>> + _NODISCARD friend constexpr bool operator==(const _Iterator<_OtherConst>& _Left, + const _Sentinel& _Right) noexcept(noexcept(_Get_current(_Right) == _Last)) /* strengthened */ { + // clang-format on + return _Get_current(_Left) == _Right._Last; + } + }; + + public: + join_view() = default; + constexpr explicit join_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 _Iterator begin() noexcept( + noexcept(_RANGES begin(_Range)) && is_nothrow_move_constructible_v>) /* strengthened */ { + return _Iterator{*this, _RANGES begin(_Range)}; + } + + _NODISCARD constexpr _Iterator begin() noexcept( + noexcept(_RANGES begin(_Range)) && is_nothrow_move_constructible_v>) /* strengthened */ + requires _Simple_view<_Vw>&& is_reference_v> { + return _Iterator{*this, _RANGES begin(_Range)}; + } + + // clang-format off + _NODISCARD constexpr _Iterator begin() const noexcept(noexcept( + _RANGES begin(_Range)) && is_nothrow_move_constructible_v>) /* strengthened */ + requires input_range && is_reference_v> { + // clang-format on + return _Iterator{*this, _RANGES begin(_Range)}; + } + + // clang-format off + _NODISCARD constexpr auto end() noexcept(noexcept( + _RANGES end(_Range)) && is_nothrow_move_constructible_v>) /* strengthened */ { + if constexpr (forward_range<_Vw> && is_reference_v> + && forward_range<_InnerRng> && common_range<_Vw> && common_range<_InnerRng>) { + // clang-format on + return _Iterator<_Simple_view<_Vw>>{*this, _RANGES end(_Range)}; + } else { + return _Sentinel<_Simple_view<_Vw>>{*this}; + } + } + + // clang-format off + _NODISCARD constexpr auto end() const noexcept(noexcept( + _RANGES end(_Range)) && is_nothrow_move_constructible_v>) /* strengthened */ + requires input_range && is_reference_v> { + if constexpr (forward_range && is_reference_v> + && forward_range> && common_range + && common_range>) { + // clang-format on + return _Iterator{*this, _RANGES end(_Range)}; + } else { + return _Sentinel{*this}; + } + } + }; + + template + join_view(_Rng &&) -> join_view>; + + namespace views { + // VARIABLE views::join + class _Join_fn : public _Pipe::_Base<_Join_fn> { + public: + // clang-format off + template + _NODISCARD constexpr auto operator()(_Rng&& _Range) const noexcept(noexcept( + join_view>{_STD forward<_Rng>(_Range)})) requires requires { + join_view>{static_cast<_Rng&&>(_Range)}; + } { + // clang-format on + return join_view>{_STD forward<_Rng>(_Range)}; + } + }; + + inline constexpr _Join_fn join; + } // namespace views + // CLASS TEMPLATE ranges::reverse_view // clang-format off template diff --git a/tests/std/test.lst b/tests/std/test.lst index 3d71f043206..ccc18c3589e 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -356,6 +356,7 @@ tests\P0896R4_views_elements tests\P0896R4_views_empty tests\P0896R4_views_filter tests\P0896R4_views_filter_death +tests\P0896R4_views_join tests\P0896R4_views_reverse tests\P0896R4_views_single tests\P0896R4_views_take diff --git a/tests/std/tests/P0896R4_ranges_range_machinery/test.cpp b/tests/std/tests/P0896R4_ranges_range_machinery/test.cpp index 576c4340517..3e9b709b978 100644 --- a/tests/std/tests/P0896R4_ranges_range_machinery/test.cpp +++ b/tests/std/tests/P0896R4_ranges_range_machinery/test.cpp @@ -104,6 +104,7 @@ STATIC_ASSERT(test_cpo(ranges::views::drop)); STATIC_ASSERT(test_cpo(ranges::views::drop_while)); STATIC_ASSERT(test_cpo(ranges::views::elements<42>)); STATIC_ASSERT(test_cpo(ranges::views::filter)); +STATIC_ASSERT(test_cpo(ranges::views::join)); STATIC_ASSERT(test_cpo(ranges::views::keys)); STATIC_ASSERT(test_cpo(ranges::views::reverse)); STATIC_ASSERT(test_cpo(ranges::views::single)); diff --git a/tests/std/tests/P0896R4_views_join/env.lst b/tests/std/tests/P0896R4_views_join/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_views_join/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\concepts_matrix.lst diff --git a/tests/std/tests/P0896R4_views_join/test.cpp b/tests/std/tests/P0896R4_views_join/test.cpp new file mode 100644 index 00000000000..2f5b51f479f --- /dev/null +++ b/tests/std/tests/P0896R4_views_join/test.cpp @@ -0,0 +1,422 @@ +// 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 CanViewJoin = requires(Rng&& r) { + views::join(static_cast(r)); +}; + +// Test a silly precomposed range adaptor pipeline +constexpr auto pipeline = views::all | views::join; + +template +constexpr bool test_one(Outer&& rng, Inner&&, Expected&& expected) { + using ranges::begin, ranges::bidirectional_range, ranges::common_range, ranges::enable_borrowed_range, ranges::end, + ranges::forward_range, ranges::input_range, ranges::iterator_t, ranges::join_view, ranges::range_value_t; + + constexpr bool deref_is_reference = is_reference_v>; + constexpr bool is_view = ranges::view>; + + if constexpr (deref_is_reference || is_view) { + using V = views::all_t; + using R = join_view; + static_assert(ranges::view); + /* + static_assert(input_range == input_range && input_range); + static_assert(forward_range == forward_range && forward_range); + static_assert(bidirectional_range == bidirectional_range && bidirectional_range); + static_assert(!ranges::random_access_range); + static_assert(!ranges::contiguous_range); + + // Validate range adapter object + // ...with lvalue argument + static_assert(CanViewJoin == (!is_view || copyable) ); + if constexpr (CanViewJoin) { + constexpr bool is_noexcept = !is_view || is_nothrow_copy_constructible_v; + + static_assert(same_as); + static_assert(noexcept(views::join(rng)) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(rng | views::join) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(rng | pipeline) == is_noexcept); + } + + // ... with const lvalue argument + static_assert(CanViewJoin&> == (!is_view || copyable) ); + if constexpr (is_view && copyable) { + constexpr bool is_noexcept = is_nothrow_copy_constructible_v; + + static_assert(same_as); + static_assert(noexcept(views::join(as_const(rng))) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(as_const(rng) | views::join) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(as_const(rng) | pipeline) == is_noexcept); + } else if constexpr (!is_view) { + using RC = join_view&>>; + constexpr bool is_noexcept = is_nothrow_constructible_v&>; + + static_assert(same_as); + static_assert(noexcept(views::join(as_const(rng))) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(as_const(rng) | views::join) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(as_const(rng) | pipeline) == is_noexcept); + } + + // ... with rvalue argument + static_assert( + CanViewJoin> == is_view || enable_borrowed_range>); + if constexpr (is_view) { + constexpr bool is_noexcept = is_nothrow_move_constructible_v; + static_assert(same_as); + static_assert(noexcept(views::join(move(rng))) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(move(rng) | views::join) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(move(rng) | pipeline) == is_noexcept); + } else if constexpr (enable_borrowed_range) { + using S = decltype(ranges::subrange{move(rng)}); + using RS = join_view; + constexpr bool is_noexcept = noexcept(S{move(rng)}); + + static_assert(same_as); + static_assert(noexcept(views::join(move(rng))) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(move(rng) | views::join) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(move(rng) | pipeline) == is_noexcept); + } + + // ... with const rvalue argument + static_assert(CanViewJoin> == (is_view && copyable) + || (!is_view && enable_borrowed_range>) ); + if constexpr (is_view && copyable) { + constexpr bool is_noexcept = is_nothrow_copy_constructible_v; + + static_assert(same_as); + static_assert(noexcept(views::join(move(as_const(rng)))) == is_nothrow_copy_constructible_v); + + static_assert(same_as); + static_assert(noexcept(move(as_const(rng)) | views::join) == is_nothrow_copy_constructible_v); + + static_assert(same_as); + static_assert(noexcept(move(as_const(rng)) | pipeline) == is_noexcept); + } else if constexpr (!is_view && enable_borrowed_range>) { + using S = decltype(ranges::subrange{as_const(rng)}); + using RS = join_view; + constexpr bool is_noexcept = noexcept(S{as_const(rng)}); + + static_assert(same_as); + static_assert(noexcept(views::join(move(as_const(rng)))) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(move(as_const(rng)) | views::join) == is_noexcept); + + static_assert(same_as); + static_assert(noexcept(move(as_const(rng)) | pipeline) == is_noexcept); + } + + // Validate deduction guide +#if !defined(__clang__) && !defined(__EDG__) // TRANSITION, DevCom-1159442 + (void) 42; +#endif // TRANSITION, DevCom-1159442 + same_as auto r = join_view{forward(rng)}; + assert(ranges::equal(r, expected)); + + /* + // Validate join_view::size + static_assert(CanMemberSize == sized_range); + if constexpr (sized_range) { + assert(r.size() == static_cast>(size(expected))); + static_assert(noexcept(r.size()) == noexcept(size(rng))); + } + + static_assert(CanMemberSize == sized_range); + if constexpr (sized_range) { + assert(as_const(r).size() == static_cast>(size(expected))); + static_assert(noexcept(r.size()) == noexcept(size(as_const(rng)))); + } + + // Validate view_interface::empty and operator bool + const bool is_empty = ranges::empty(expected); + assert(r.empty() == is_empty); + assert(static_cast(r) == !is_empty); + static_assert(CanMemberEmpty == common_range); + if constexpr (common_range) { + assert(as_const(r).empty() == is_empty); + assert(static_cast(as_const(r)) == !is_empty); + } + + // Validate join_view::begin + static_assert(CanMemberBegin); + { + // join_view sometimes caches begin, so let's make several extra calls + const same_as>> auto i = r.begin(); + if (!is_empty) { + assert(*i == *begin(expected)); + } + assert(r.begin() == i); + assert(r.begin() == i); + // NB: non-const begin is unconditionally noexcept(false) due to caching + static_assert(!noexcept(r.begin())); + + if constexpr (copyable) { + auto r2 = r; + const same_as>> auto i2 = r2.begin(); + assert(r2.begin() == i2); + assert(r2.begin() == i2); + if (!is_empty) { + assert(*i2 == *i); + } + } + + static_assert(CanMemberBegin == common_range); + if constexpr (common_range) { + const same_as>> auto ci = as_const(r).begin(); + assert(as_const(r).begin() == ci); + assert(as_const(r).begin() == ci); + if (!is_empty) { + assert(*ci == *i); + } + static_assert(noexcept(as_const(r).begin()) == noexcept(join_iterator{end(as_const(rng))})); + + if constexpr (copyable) { + const auto r2 = r; + const same_as>> auto ci2 = r2.begin(); + assert(r2.begin() == ci2); + assert(r2.begin() == ci2); + if (!is_empty) { + assert(*ci2 == *i); + } + } + } + } + + // Validate join_view::end + static_assert(CanMemberEnd); + if (!is_empty) { + assert(*prev(r.end()) == *prev(end(expected))); + + if constexpr (copyable) { + auto r2 = r; + assert(*prev(r2.end()) == *prev(end(expected))); + } + static_assert(noexcept(r.end()) == noexcept(join_iterator{begin(rng)})); + + static_assert(CanMemberEnd == common_range); + if constexpr (common_range) { + assert(*prev(as_const(r).end()) == *prev(end(expected))); + static_assert(noexcept(as_const(r).end()) == noexcept(join_iterator{begin(as_const(rng))})); + } + } + + // Validate view_interface::data + static_assert(!CanData); + static_assert(!CanData); + + if (!is_empty) { + // Validate view_interface::operator[] + static_assert(CanIndex == random_access_range); + static_assert(CanIndex == (random_access_range && common_range) ); + if constexpr (random_access_range) { + assert(r[0] == *begin(expected)); + + if constexpr (common_range) { + assert(as_const(r)[0] == *begin(expected)); + } + } + + // Validate view_interface::front and back + assert(r.front() == *begin(expected)); + assert(r.back() == *prev(end(expected))); + + static_assert(CanMemberFront == common_range); + static_assert(CanMemberBack == common_range); + if constexpr (common_range) { + assert(as_const(r).front() == *begin(expected)); + assert(as_const(r).back() == *prev(end(expected))); + } + } + + // Validate join_view::base() const& + static_assert(CanMemberBase == copy_constructible); + if constexpr (copy_constructible) { + same_as auto b1 = as_const(r).base(); + static_assert(noexcept(as_const(r).base()) == is_nothrow_copy_constructible_v); + if (!is_empty) { + assert(*b1.begin() == *prev(end(expected))); + if constexpr (common_range) { + assert(*prev(b1.end()) == *begin(expected)); + } + } + } + + // Validate join_view::base() && (NB: do this last since it leaves r moved-from) + #if !defined(__clang__) && !defined(__EDG__) // TRANSITION, DevCom-1159442 + (void) 42; + #endif // TRANSITION, DevCom-1159442 + same_as auto b2 = move(r).base(); + static_assert(noexcept(move(r).base()) == is_nothrow_move_constructible_v); + if (!is_empty) { + assert(*b2.begin() == *prev(end(expected))); + if constexpr (common_range) { + assert(*prev(b2.end()) == *begin(expected)); + } + } + */ + } + return true; +} + +template +struct with_dependent_input_ranges { + template + static constexpr void call() { + using namespace test; + using test::range; + + // For all ranges, IsCommon implies Eq. + // For single-pass ranges, Eq is uninteresting without IsCommon (there's only one valid iterator + // value at a time, and no reason to compare it with itself for equality). + Continuation::template call>(); + Continuation::template call>(); + Continuation::template call>(); + Continuation::template call>(); + + Continuation::template call>(); + Continuation::template call>(); + Continuation::template call>(); + Continuation::template call>(); + + Continuation::template call>(); + Continuation::template call>(); + Continuation::template call>(); + Continuation::template call>(); + + Continuation::template call>(); + Continuation::template call>(); + Continuation::template call>(); + Continuation::template call>(); + + with_forward_ranges::template call(); + } +}; + +template +constexpr void test_nested_inout() { + with_input_or_output_ranges, Element>::call(); +} + +static constexpr int full_range[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; +static constexpr span first_range{full_range, full_range + 3}; +static constexpr span second_range{full_range + 3, full_range + 7}; +static constexpr span third_range{full_range + 7, full_range + 7}; // NB: Intentionally left empty +static constexpr span fourth_range{full_range + 7, full_range + 10}; + +struct instantiator { + template + static constexpr void call() { + array inner_ranges = { + Inner{first_range}, Inner{second_range}, Inner{third_range}, Inner{fourth_range}}; + Outer r{inner_ranges}; + test_one(r, first_range, full_range); + } +}; + +template > +using move_only_view = test::range}, + test::ProxyRef{!derived_from}, test::CanView::yes, test::Copyability::move_only>; + +int main() { + // Validate views + { // ...copyable + array data = {{{}, "Hello "sv, {}, "World!"sv, {}}}; + span input{data}; + constexpr string_view expected = "Hello World!"sv; + static_assert(test_one(input, string_view{}, expected)); + test_one(input, string_view{}, expected); + } + /* + { // ... move-only + test_one(move_only_view{some_ints}, joind_ints); + test_one(move_only_view{some_ints}, joind_ints); + test_one(move_only_view{some_ints}, joind_ints); + test_one(move_only_view{some_ints}, joind_ints); + test_one(move_only_view{some_ints}, joind_ints); + test_one(move_only_view{some_ints}, joind_ints); + } + + // Validate non-views + { // ... C array + static_assert(test_one(some_ints, joind_ints)); + test_one(some_ints, joind_ints); + } + { // ... contiguous container + string str{"Hello, World!"}; + constexpr auto expected = "!dlroW ,olleH"sv; + test_one(str, expected); + } + { // ... bidi container + list lst{3, 4, 5}; + static constexpr int joind[] = {5, 4, 3}; + test_one(lst, joind); + + static constexpr int joind_prefix[] = {4, 3}; + assert(ranges::equal( + views::join(ranges::subrange{counted_iterator{lst.begin(), 2}, default_sentinel}), joind_prefix)); + } + + // Validate a non-view borrowed range + { + constexpr span s{some_ints}; + static_assert(test_one(s, joind_ints)); + test_one(s, joind_ints); + } + // Get full instantiation coverage + static_assert((test_nested_inout(), true)); + test_nested_inout(); + */ +}