diff --git a/stl/inc/ranges b/stl/inc/ranges index 367648ffe80..7381bd948e6 100644 --- a/stl/inc/ranges +++ b/stl/inc/ranges @@ -1334,7 +1334,6 @@ namespace ranges { template <_Copy_constructible_object _Pr> _NODISCARD constexpr auto operator()(_Pr _Pred) const noexcept(is_nothrow_move_constructible_v<_Pr>) { - // clang-format on return _Partial<_Pr>{._Pred = {in_place, _STD move(_Pred)}}; } }; @@ -1634,7 +1633,7 @@ namespace ranges { } friend constexpr void iter_swap(const _Iterator& _Left, const _Iterator& _Right) _NOEXCEPT_IDL0(noexcept( - ranges::iter_swap(_Left._Current, _Right._Current))) requires indirectly_swappable> { + _RANGES iter_swap(_Left._Current, _Right._Current))) requires indirectly_swappable> { #if _ITERATOR_DEBUG_LEVEL != 0 _Left._Check_dereference(); _Right._Check_dereference(); @@ -2490,51 +2489,6 @@ namespace ranges { }; inline constexpr _Drop_fn drop; - - // VARIABLE views::counted - class _Counted_fn { - private: - enum class _St { _Span, _Subrange, _Subrange_counted }; - - template - _NODISCARD static _CONSTEVAL _Choice_t<_St> _Choose() noexcept { - _STL_INTERNAL_STATIC_ASSERT(input_or_output_iterator<_It>); - if constexpr (contiguous_iterator<_It>) { - return {_St::_Span, noexcept(span{_STD to_address(_STD declval<_It>()), iter_difference_t<_It>{}})}; - } else if constexpr (random_access_iterator<_It>) { - return {_St::_Subrange, - noexcept(subrange{_STD declval<_It>(), _STD declval<_It>() + iter_difference_t<_It>{}})}; - } else { - return {_St::_Subrange_counted, - noexcept(subrange{ - counted_iterator{_STD declval<_It>(), iter_difference_t<_It>{}}, default_sentinel})}; - } - } - - template - static constexpr _Choice_t<_St> _Choice = _Choose<_It>(); - - public: - // clang-format off - template - requires input_or_output_iterator> - _NODISCARD constexpr auto operator()(_It&& _First, const iter_difference_t> _Count) const - noexcept(_Choice>._No_throw) { - // clang-format on - _STL_ASSERT(_Count >= 0, "The size passed to views::counted must be non-negative"); - constexpr _St _Strat = _Choice>._Strategy; - - if constexpr (_Strat == _St::_Span) { - return span{_STD to_address(_STD forward<_It>(_First)), static_cast(_Count)}; - } else if constexpr (_Strat == _St::_Subrange) { - return subrange{_First, _First + _Count}; - } else if constexpr (_Strat == _St::_Subrange_counted) { - return subrange{counted_iterator{_STD forward<_It>(_First), _Count}, default_sentinel}; - } - } - }; - - inline constexpr _Counted_fn counted; } // namespace views // CLASS TEMPLATE ranges::drop_while_view @@ -2635,6 +2589,422 @@ namespace ranges { inline constexpr _Drop_while_fn drop_while; } // namespace views + // CLASS TEMPLATE ranges::join_view + // clang-format off + template + requires view<_Vw> && input_range> + && (is_reference_v> || view>) + class join_view; + // clang-format on + + template + class _Join_view_base : public view_interface> { + protected: + /* [[no_unique_address]] */ views::all_t> _Inner{}; + }; + + // clang-format off + template + requires is_reference_v> + class _Join_view_base<_Vw> : public view_interface> {}; + + template + requires view<_Vw> && input_range> + && (is_reference_v> || view>) + class join_view : public _Join_view_base<_Vw> { + // clang-format on + private: + template + using _InnerRng = range_reference_t<_Maybe_const<_Const, _Vw>>; + /* [[no_unique_address]] */ _Vw _Range{}; + + template + class _Sentinel; + + template // TRANSITION, LWG-3289 + struct _Category_base {}; + + // clang-format off + template + requires _Has_member_iterator_category<_OuterTraits> && _Has_member_iterator_category<_InnerTraits> + struct _Category_base<_OuterTraits, _InnerTraits, _Deref_is_glvalue, _Inner_common> { + using iterator_category = + conditional_t<_Deref_is_glvalue && _Inner_common // per LWG issue unnumbered as of 2021-03-16 + && derived_from + && derived_from, + bidirectional_iterator_tag, + conditional_t<_Deref_is_glvalue + && 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<_InnerRng<_Const>>, + common_range<_InnerRng<_Const>>> { + private: + template + friend class _Iterator; + template + friend class _Sentinel; + + using _Parent_t = _Maybe_const<_Const, join_view>; + using _Base = _Maybe_const<_Const, _Vw>; + using _OuterIter = iterator_t<_Base>; + using _InnerIter = iterator_t<_InnerRng<_Const>>; + + // True if and only if the expression *i, where i is an iterator from the outer range, is a glvalue: + static constexpr bool _Deref_is_glvalue = is_reference_v<_InnerRng<_Const>>; + + /* [[no_unique_address]] */ _OuterIter _Outer{}; + /* [[no_unique_address]] */ _InnerIter _Inner{}; + _Parent_t* _Parent{}; + + constexpr auto& _Update_inner(_InnerRng<_Const> _Range) { + if constexpr (_Deref_is_glvalue) { + return _Range; + } else { + _Parent->_Inner = views::all(_STD move(_Range)); + return _Parent->_Inner; + } + } + + constexpr void _Satisfy() { + const auto _Last = _RANGES end(_Parent->_Range); + for (; _Outer != _Last; ++_Outer) { + auto& _Tmp = _Update_inner(*_Outer); + _Inner = _RANGES begin(_Tmp); + if (_Inner != _RANGES end(_Tmp)) { + return; + } + } + if constexpr (_Deref_is_glvalue) { + _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 join_view end iterator"); + sentinel_t<_InnerRng> _Last; + if constexpr (_Deref_is_glvalue) { + _Last = _RANGES end(*_Outer); + } else { + _Last = _RANGES end(_Parent->_Inner); + } + _STL_VERIFY(_Inner != _Last, "cannot dereference join_view end iterator"); + } + + 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: + // clang-format off + // Per LWG issue unnumbered as of 2021-03-16 + using iterator_concept = conditional_t<_Deref_is_glvalue + && bidirectional_range<_Base> && bidirectional_range<_InnerRng<_Const>> + && common_range<_InnerRng<_Const>>, bidirectional_iterator_tag, + conditional_t<_Deref_is_glvalue && forward_range<_Base> && forward_range<_InnerRng<_Const>>, + forward_iterator_tag, input_iterator_tag>>; + // clang-format on + using value_type = range_value_t<_InnerRng<_Const>>; + using difference_type = common_type_t, range_difference_t<_InnerRng<_Const>>>; + + _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; + } + + // clang-format off + _NODISCARD constexpr _InnerIter operator->() const noexcept(is_nothrow_copy_constructible_v<_InnerIter>) + /* strengthened */ requires _Has_arrow<_InnerIter> && copyable<_InnerIter> { + // clang-format on +#if _ITERATOR_DEBUG_LEVEL != 0 + _Check_dereference(); +#endif // _ITERATOR_DEBUG_LEVEL != 0 + return _Inner; + } + + constexpr _Iterator& operator++() { + if constexpr (_Deref_is_glvalue) { + if (++_Inner == _RANGES end(*_Outer)) { + ++_Outer; + _Satisfy(); + } + } else { + if (++_Inner == _RANGES end(_Parent->_Inner)) { + ++_Outer; + _Satisfy(); + } + } + return *this; + } + + constexpr decltype(auto) operator++(int) { + if constexpr (_Deref_is_glvalue && forward_range<_Base> && forward_range<_InnerRng<_Const>>) { + auto _Tmp = *this; + ++*this; + return _Tmp; + } else { + ++*this; + } + } + + // clang-format off + constexpr _Iterator& operator--() + requires _Deref_is_glvalue && bidirectional_range<_Base> && bidirectional_range<_InnerRng<_Const>> + && common_range<_InnerRng<_Const>> { + // clang-format on + if (_Outer == _RANGES end(_Parent->_Range)) { + --_Outer; + _Inner = _RANGES end(*_Outer); + } + while (_Inner == _RANGES begin(*_Outer)) { + --_Outer; + _Inner = _RANGES end(*_Outer); + } + --_Inner; + return *this; + } + + // clang-format off + constexpr _Iterator operator--(int) + requires _Deref_is_glvalue && bidirectional_range<_Base> && bidirectional_range<_InnerRng<_Const>> + && common_range<_InnerRng<_Const>> { + // 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(_Implicitly_convert_to(_Left._Outer == _Right._Outer + && _Left._Inner == _Right._Inner))) /* strengthened */ + requires _Deref_is_glvalue && 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); + } + + // clang-format off + friend constexpr void iter_swap(const _Iterator& _Left, const _Iterator& _Right) noexcept( + noexcept(_RANGES iter_swap(_Left._Inner, _Right._Inner))) + requires indirectly_swappable<_InnerIter> { // per LWG-3517 + // clang-format on +#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>>; + + /* [[no_unique_address]] */ sentinel_t<_Base> _Last{}; + + // clang-format off + template + requires sentinel_for, _Maybe_const_iter<_OtherConst>> + _NODISCARD constexpr bool _Equal(const _Iterator<_OtherConst>& _It) const noexcept( + noexcept(_Implicitly_convert_to(_It._Outer == _Last))) { + // clang-format on + return _It._Outer == _Last; + } + + public: + _Sentinel() = default; + constexpr explicit _Sentinel(_Parent_t& _Parent) noexcept( + noexcept(_RANGES end(_Parent._Range)) + && is_nothrow_move_constructible_v>) // strengthened + : _Last(_RANGES end(_Parent._Range)) {} + + // clang-format off + 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(_Right._Equal(_Left))) /* strengthened */ { + // clang-format on + return _Right._Equal(_Left); + } + }; + + 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 auto begin() { + constexpr bool _Use_const = _Simple_view<_Vw> && is_reference_v<_InnerRng>; + return _Iterator<_Use_const>{*this, _RANGES begin(_Range)}; + } + + // clang-format off + _NODISCARD constexpr _Iterator begin() const + requires input_range && is_reference_v<_InnerRng> { + // clang-format on + return _Iterator{*this, _RANGES begin(_Range)}; + } + + // clang-format off + _NODISCARD constexpr auto end() { + if constexpr (forward_range<_Vw> && is_reference_v<_InnerRng> + && 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 + requires input_range && is_reference_v<_InnerRng> { + if constexpr (forward_range && is_reference_v<_InnerRng> + && forward_range<_InnerRng> && common_range + && common_range<_InnerRng>) { + // clang-format on + return _Iterator{*this, _RANGES end(_Range)}; + } else { + return _Sentinel{*this}; + } + } + }; + + template + explicit 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; + + // VARIABLE views::counted + class _Counted_fn { + private: + enum class _St { _Span, _Subrange, _Subrange_counted }; + + template + _NODISCARD static _CONSTEVAL _Choice_t<_St> _Choose() noexcept { + _STL_INTERNAL_STATIC_ASSERT(input_or_output_iterator<_It>); + if constexpr (contiguous_iterator<_It>) { + return {_St::_Span, noexcept(span{_STD to_address(_STD declval<_It>()), iter_difference_t<_It>{}})}; + } else if constexpr (random_access_iterator<_It>) { + return {_St::_Subrange, + noexcept(subrange{_STD declval<_It>(), _STD declval<_It>() + iter_difference_t<_It>{}})}; + } else { + return {_St::_Subrange_counted, + noexcept(subrange{ + counted_iterator{_STD declval<_It>(), iter_difference_t<_It>{}}, default_sentinel})}; + } + } + + template + static constexpr _Choice_t<_St> _Choice = _Choose<_It>(); + + public: + // clang-format off + template + requires input_or_output_iterator> + _NODISCARD constexpr auto operator()(_It&& _First, const iter_difference_t> _Count) const + noexcept(_Choice>._No_throw) { + // clang-format on + _STL_ASSERT(_Count >= 0, "The size passed to views::counted must be non-negative"); + constexpr _St _Strat = _Choice>._Strategy; + + if constexpr (_Strat == _St::_Span) { + return span{_STD to_address(_STD forward<_It>(_First)), static_cast(_Count)}; + } else if constexpr (_Strat == _St::_Subrange) { + return subrange{_First, _First + _Count}; + } else if constexpr (_Strat == _St::_Subrange_counted) { + return subrange{counted_iterator{_STD forward<_It>(_First), _Count}, default_sentinel}; + } + } + }; + + inline constexpr _Counted_fn counted; + } // namespace views + // CLASS TEMPLATE ranges::common_view // clang-format off template diff --git a/tests/std/include/range_algorithm_support.hpp b/tests/std/include/range_algorithm_support.hpp index 901f64822b6..3dc819bee4c 100644 --- a/tests/std/include/range_algorithm_support.hpp +++ b/tests/std/include/range_algorithm_support.hpp @@ -87,7 +87,7 @@ namespace test { enum class CanDifference : bool { no, yes }; enum class CanCompare : bool { no, yes }; - enum class ProxyRef : bool { no, yes }; + enum class ProxyRef { no, yes, prvalue }; enum class IsWrapped : bool { no, yes }; template @@ -341,14 +341,15 @@ namespace test { // Interact with the STL's iterator unwrapping machinery? IsWrapped Wrapped = IsWrapped::yes> requires (to_bool(Eq) || !derived_from) - && (!to_bool(Proxy) || !derived_from) + && (Proxy == ProxyRef::no || !derived_from) class iterator { Element* ptr_; template static constexpr bool at_least = derived_from; - using ReferenceType = conditional_t, Element&>; + using ReferenceType = conditional_t, + conditional_t, Element&>>; struct post_increment_proxy { Element* ptr_; @@ -590,7 +591,7 @@ template > { using iterator_concept = Category; using iterator_category = conditional_t, // - conditional_t(Proxy), input_iterator_tag, Category>, // + conditional_t, // conditional_t(Eq), Category, void>>; // TRANSITION, LWG-3289 using value_type = remove_cv_t; using difference_type = ptrdiff_t; @@ -670,11 +671,11 @@ namespace test { template class range_base { public: - range_base() = default; + constexpr range_base() = default; constexpr explicit range_base(span elements) noexcept : elements_{elements} {} - range_base(const range_base&) = default; - range_base& operator=(const range_base&) = default; + constexpr range_base(const range_base&) = default; + constexpr range_base& operator=(const range_base&) = default; constexpr range_base(range_base&& that) noexcept : elements_{that.elements_}, moved_from_{that.moved_from_} { @@ -720,7 +721,7 @@ namespace test { Copyability Copy = IsView == CanView::yes ? Copyability::move_only : Copyability::immobile> requires (!to_bool(IsCommon) || to_bool(Eq)) && (to_bool(Eq) || !derived_from) - && (!to_bool(Proxy) || !derived_from) + && (Proxy == ProxyRef::no || !derived_from) && (!to_bool(IsView) || Copy != Copyability::immobile) class range : public detail::range_base { private: diff --git a/tests/std/test.lst b/tests/std/test.lst index e5866c93af1..4d3b516a529 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -375,6 +375,7 @@ tests\P0896R4_views_empty tests\P0896R4_views_filter tests\P0896R4_views_filter_death tests\P0896R4_views_iota +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 2e4698779e3..47ec793dcf2 100644 --- a/tests/std/tests/P0896R4_ranges_range_machinery/test.cpp +++ b/tests/std/tests/P0896R4_ranges_range_machinery/test.cpp @@ -105,6 +105,7 @@ 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::iota)); +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..62a24024479 --- /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 ..\strict_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..cd989531f13 --- /dev/null +++ b/tests/std/tests/P0896R4_views_join/test.cpp @@ -0,0 +1,511 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#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)); +}; + +template +constexpr bool test_one(Outer&& rng, Expected&& expected) { + using ranges::join_view, ranges::begin, ranges::end, ranges::next, ranges::prev, ranges::input_range, + ranges::forward_range, ranges::bidirectional_range, ranges::common_range, ranges::borrowed_range, + ranges::iterator_t, ranges::range_value_t, ranges::range_reference_t; + + using Inner = range_value_t; + constexpr bool deref_is_glvalue = is_reference_v>; + + // clang-format off + constexpr bool can_test = ranges::viewable_range + && input_range> + && (deref_is_glvalue || ranges::view); + // clang-format on + + if constexpr (can_test) { + using V = views::all_t; + using R = join_view; + static_assert(ranges::view); + static_assert(input_range == input_range); + static_assert(forward_range == (deref_is_glvalue && forward_range && forward_range) ); + // clang-format off + static_assert(bidirectional_range == + (deref_is_glvalue && bidirectional_range && bidirectional_range && common_range)); + // clang-format on + static_assert(!ranges::random_access_range); + static_assert(!ranges::contiguous_range); + + constexpr bool is_view = ranges::view; + + // 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); + } + + // ... 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); + } 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); + } + + // ... with rvalue argument + static_assert(CanViewJoin> == (is_view || 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); + } else if constexpr (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); + } + + // ... with const rvalue argument + static_assert(CanViewJoin> == (is_view && copyable) + || (!is_view && 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_noexcept); + + static_assert(same_as); + static_assert(noexcept(move(as_const(rng)) | views::join) == is_noexcept); + } else if constexpr (!is_view && 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); + } + + // Validate deduction guide + same_as auto r = join_view{forward(rng)}; + assert(ranges::equal(r, expected)); + const bool is_empty = ranges::empty(expected); + + // Validate lack of size + static_assert(!CanSize); + + // Validate view_interface::empty and operator bool + static_assert(CanEmpty == forward_range); + static_assert(CanMemberEmpty == CanEmpty); + if constexpr (CanMemberEmpty) { + assert(r.empty() == is_empty); + assert(static_cast(r) == !is_empty); + + static_assert(CanEmpty == forward_range); + static_assert(CanMemberEmpty == CanEmpty); + if constexpr (CanMemberEmpty) { + assert(as_const(r).empty() == is_empty); + assert(static_cast(as_const(r)) == !is_empty); + } + } + + // Validate join_view::begin + static_assert(CanMemberBegin); + static_assert(CanMemberBegin == (input_range && is_reference_v>) ); + if (forward_range) { + const iterator_t i = r.begin(); + if (!is_empty) { + assert(*i == *begin(expected)); + } + + if constexpr (copyable) { + auto r2 = r; + const same_as> auto i2 = r2.begin(); + if (!is_empty) { + assert(*i2 == *i); + } + } + + static_assert(CanMemberBegin == CanBegin); + if constexpr (CanMemberBegin) { + const iterator_t ci = as_const(r).begin(); + if (!is_empty) { + assert(*ci == *i); + } + + if constexpr (copyable) { + const auto r2 = r; + const same_as> auto ci2 = r2.begin(); + if (!is_empty) { + assert(*ci2 == *i); + } + } + } + } + + // Validate join_view::end + static_assert(CanMemberEnd); + static_assert(CanMemberEnd == (input_range && is_reference_v>) ); + // clang-format off + static_assert(common_range == (forward_range && is_reference_v> && common_range + && forward_range && common_range) ); + static_assert(common_range == (forward_range && is_reference_v> + && common_range && forward_range> + && common_range>) ); + // clang-format on + const ranges::sentinel_t s = r.end(); + if (!is_empty) { + if constexpr (bidirectional_range && common_range) { + assert(*prev(s) == *prev(end(expected))); + + if constexpr (copyable) { + auto r2 = r; + assert(*prev(r2.end()) == *prev(end(expected))); + } + } + + static_assert(CanMemberEnd == CanEnd); + if constexpr (CanMemberEnd) { + const ranges::sentinel_t cs = as_const(r).end(); + if constexpr (bidirectional_range && common_range) { + assert(*prev(cs) == *prev(end(expected))); + + if constexpr (copyable) { + const auto r2 = r; + const ranges::sentinel_t cs2 = r2.end(); + assert(*prev(cs2) == *prev(end(expected))); + } + } + } + } + + // Validate view_interface::data + static_assert(!CanData); + static_assert(!CanData); + + // Validate view_interface::operator[] + static_assert(!CanIndex); + static_assert(!CanIndex); + + // Validate view_interface::front and back + static_assert(CanMemberFront == forward_range); + static_assert(CanMemberFront == forward_range); + if (!is_empty) { + if constexpr (CanMemberFront) { + assert(r.front() == *begin(expected)); + } + + if constexpr (CanMemberFront) { + assert(as_const(r).front() == *begin(expected)); + } + } + + static_assert(CanMemberBack == (bidirectional_range && common_range) ); + // clang-format off + static_assert(CanMemberBack == (bidirectional_range && common_range + && is_reference_v>) ); + // clang-format on + if (!is_empty) { + if constexpr (CanMemberBack) { + assert(r.back() == *prev(end(expected))); + } + + if constexpr (CanMemberBack) { + assert(as_const(r).back() == *prev(end(expected))); + } + } + + // Validate join_view::base() const& + static_assert(CanMemberBase == copy_constructible); + if constexpr (copy_constructible && forward_range) { + same_as auto b1 = as_const(r).base(); + static_assert(noexcept(as_const(r).base()) == is_nothrow_copy_constructible_v); + if (!is_empty) { + auto bi1 = b1.begin(); + while (ranges::empty(*bi1)) { + ++bi1; + } + auto&& inner_first = *bi1; + assert(*begin(inner_first) == *begin(expected)); + + if constexpr (bidirectional_range && common_range) { + auto ei1 = prev(b1.end()); + while (ranges::empty(*ei1)) { + --ei1; + } + auto&& inner_last = *ei1; + assert(*prev(end(inner_last)) == *prev(end(expected))); + } + } + } + + // Validate join_view::base() && (NB: do this last since it leaves r moved-from) + if (forward_range) { // intentionally not if constexpr + same_as auto b2 = move(r).base(); + static_assert(noexcept(move(r).base()) == is_nothrow_move_constructible_v); + if constexpr (CanEmpty) { + if (!is_empty) { + auto bi2 = b2.begin(); + while (ranges::empty(*bi2)) { + ++bi2; + } + auto&& inner_first = *bi2; + assert(*begin(inner_first) == *begin(expected)); + + if constexpr (bidirectional_range && common_range) { + auto ei2 = prev(b2.end()); + while (ranges::empty(*ei2)) { + --ei2; + } + auto&& inner_last = *ei2; + assert(*prev(end(inner_last)) == *prev(end(expected))); + } + } + } + } + } + return true; +} + +constexpr int expected_ints[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; +constexpr span intervals[] = { + {expected_ints + 0, expected_ints + 3}, + {expected_ints + 3, expected_ints + 7}, + {expected_ints + 7, expected_ints + 7}, + {expected_ints + 7, expected_ints + 10}, + {expected_ints + 10, expected_ints + 10}, +}; + +struct instantiator { + template + static constexpr void call() { + static_assert(ranges::size(intervals) == 5); + Inner inner_ranges[] = { + Inner{intervals[0]}, Inner{intervals[1]}, Inner{intervals[2]}, Inner{intervals[3]}, Inner{intervals[4]}}; + test_one(Outer{inner_ranges}, expected_ints); + } +}; + +enum class RefOrView { reference, view }; + +template > +using inner_test_range = test::range || IsCommon == test::Common::yes}, + test::ProxyRef::no, IsView, test::Copyability::copyable>; + +template > +using outer_test_range = test::range || IsCommon == test::Common::yes}, + (RV == RefOrView::view ? test::ProxyRef::prvalue : test::ProxyRef::no), test::CanView::yes, + test::Copyability::copyable>; + +constexpr bool instantiation_test() { + // The adaptor is sensitive to: + // * inner and outer range common category (input, forward, bidi) + // * outer range's reference type referenceness vs. value type viewness + // * if the inner range models common_range + // * if the outer range models common_range + // * if both inner and outer iterators are equality_comparable (the defaults for input-non-common and forward + // suffice to get coverage here) + // * if the inner range has -> (Ditto defaults) + using test::CanView, test::Common; + + instantiator::call, + outer_test_range, + RefOrView::reference, Common::no>>(); + instantiator::call, + outer_test_range, + RefOrView::reference, Common::yes>>(); + instantiator::call, + outer_test_range, + RefOrView::reference, Common::no>>(); + instantiator::call, + outer_test_range, + RefOrView::reference, Common::yes>>(); + instantiator::call, + outer_test_range, + RefOrView::view, Common::no>>(); + instantiator::call, + outer_test_range, + RefOrView::view, Common::yes>>(); + instantiator::call, + outer_test_range, + RefOrView::view, Common::no>>(); + instantiator::call, + outer_test_range, + RefOrView::view, Common::yes>>(); + instantiator::call, + outer_test_range, + RefOrView::reference, Common::no>>(); + instantiator::call, + outer_test_range, + RefOrView::reference, Common::yes>>(); + instantiator::call, + outer_test_range, + RefOrView::reference, Common::no>>(); + instantiator::call, + outer_test_range, + RefOrView::reference, Common::yes>>(); + instantiator::call, + outer_test_range, + RefOrView::view, Common::no>>(); + instantiator::call, + outer_test_range, + RefOrView::view, Common::yes>>(); + instantiator::call, + outer_test_range, + RefOrView::view, Common::no>>(); + instantiator::call, + outer_test_range, + RefOrView::view, Common::yes>>(); + instantiator::call, + outer_test_range, RefOrView::reference, Common::no>>(); + instantiator::call, + outer_test_range, RefOrView::reference, + Common::yes>>(); + instantiator::call, + outer_test_range, RefOrView::reference, + Common::no>>(); + instantiator::call, + outer_test_range, RefOrView::reference, + Common::yes>>(); + instantiator::call, + outer_test_range, RefOrView::view, Common::no>>(); + instantiator::call, + outer_test_range, RefOrView::view, Common::yes>>(); + instantiator::call, + outer_test_range, RefOrView::view, Common::no>>(); + instantiator::call, + outer_test_range, RefOrView::view, Common::yes>>(); + + return true; +} + +using mo_inner = test::range; + +template > +using move_only_view = test::range}, test::ProxyRef::no, test::CanView::yes, + test::Copyability::move_only>; +void test_move_only_views() { + const auto gen = [] { + return array{mo_inner{intervals[0]}, mo_inner{intervals[1]}, mo_inner{intervals[2]}, mo_inner{intervals[3]}}; + }; + + auto input = gen(); + test_one(move_only_view{input}, expected_ints); + + input = gen(); + test_one(move_only_view{input}, expected_ints); + + input = gen(); + test_one(move_only_view{input}, expected_ints); + + input = gen(); + test_one(move_only_view{input}, expected_ints); + + input = gen(); + test_one(move_only_view{input}, expected_ints); +} + +int main() { + // Validate views + constexpr string_view expected = "Hello World!"sv; + + { // ...copyable + static constexpr array input = {{{}, "Hello "sv, {}, "World!"sv, {}}}; + constexpr span sp{input}; + static_assert(test_one(sp, expected)); + test_one(sp, expected); + } + // ... move-only + test_move_only_views(); + + // Validate non-views + { // ... C array + static constexpr int join_me[5][2] = {{0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}}; +#if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-934264 + static_assert(test_one(join_me, expected_ints)); +#endif // TRANSITION, VSO-934264 + test_one(join_me, expected_ints); + } + { // ... fwd container + forward_list lst = {{}, "Hello "sv, {}, "World!"sv, {}}; + test_one(lst, expected); + } + { // ... bidi container + list lst = {{}, "Hello "sv, {}, "World!"sv, {}}; + test_one(lst, expected); + } + { // ... random container + vector lst = {{}, "Hello "sv, {}, "World!"sv, {}}; + test_one(lst, expected); + } + + { // From example in LWG-3474 + vector>> nested_vectors = {{{1, 2, 3}, {4, 5}, {6}}, {{7}, {8, 9}, {10, 11, 12}}, {{13}}}; + auto joined = nested_vectors | views::join | views::join; + static constexpr int result[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}; + assert(ranges::equal(joined, result)); + } + +#if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-934264 + STATIC_ASSERT(instantiation_test()); +#endif // TRANSITION, VSO-934264 + instantiation_test(); +}