From 41b16acee1dbbd9cc87aa52c2f336c9a60e7fd13 Mon Sep 17 00:00:00 2001 From: cpplearner Date: Mon, 14 Feb 2022 04:09:36 +0800 Subject: [PATCH] Implement views::join_with --- stl/inc/ranges | 397 ++++++++++++++ tests/std/test.lst | 1 + .../std/tests/P2441R2_views_join_with/env.lst | 4 + .../tests/P2441R2_views_join_with/test.cpp | 516 ++++++++++++++++++ 4 files changed, 918 insertions(+) create mode 100644 tests/std/tests/P2441R2_views_join_with/env.lst create mode 100644 tests/std/tests/P2441R2_views_join_with/test.cpp diff --git a/stl/inc/ranges b/stl/inc/ranges index 10a150f0b2f..6eed7a7c022 100644 --- a/stl/inc/ranges +++ b/stl/inc/ranges @@ -18,6 +18,9 @@ #include #include #include +#if _HAS_CXX23 +#include +#endif // _HAS_CXX23 #pragma pack(push, _CRT_PACKING) #pragma warning(push, _STL_WARNING_LEVEL) @@ -4749,6 +4752,400 @@ namespace ranges { } // namespace views #if _HAS_CXX23 + template + concept _Compatible_joinable_ranges = common_with, range_value_t<_Pat>> // + && common_reference_with, range_reference_t<_Pat>> // + && common_reference_with, range_rvalue_reference_t<_Pat>>; + + template + concept _Bidirectional_common = bidirectional_range<_Rng> && common_range<_Rng>; + + template // TRANSITION, LLVM-47414 + concept _Can_const_join_with = + input_range && forward_range && is_reference_v>; + + template + requires view<_Vw> && input_range> && view<_Pat> // + && _Compatible_joinable_ranges, _Pat> + class join_with_view; + + template + class _Join_with_view_base : public view_interface> { + private: + struct _Cache_wrapper { + template + constexpr _Cache_wrapper(_Not_quite_object::_Construct_tag, const _Iter& _It) noexcept(noexcept(*_It)) + : _Val(*_It) {} + + remove_cv_t> _Val; + }; + + protected: + /* [[no_unique_address]] */ _Non_propagating_cache<_Cache_wrapper, false> _Inner{}; + }; + + template + requires is_reference_v> + class _Join_with_view_base<_Vw, _Pat> : public view_interface> { + }; + + template + requires view<_Vw> && input_range> && view<_Pat> // + && _Compatible_joinable_ranges, _Pat> + class join_with_view : public _Join_with_view_base<_Vw, _Pat> { + private: + template + using _InnerRng = range_reference_t<_Maybe_const<_Const, _Vw>>; + + /* [[no_unique_address]] */ _Vw _Range{}; + /* [[no_unique_address]] */ _Pat _Pattern{}; + + template + class _Sentinel; + + template + struct _Category_base {}; + + template + struct _Category_base<_Outer, _Inner, _Pattern, true> { + using iterator_category = conditional_t< + !is_lvalue_reference_v, range_reference_t<_Pattern>>>, + input_iterator_tag, + conditional_t && common_range<_Pattern> // + && derived_from<_Iter_cat_t>, bidirectional_iterator_tag> // + && derived_from<_Iter_cat_t>, bidirectional_iterator_tag> // + && derived_from<_Iter_cat_t>, bidirectional_iterator_tag>, + bidirectional_iterator_tag, + conditional_t>, forward_iterator_tag> // + && derived_from<_Iter_cat_t>, forward_iterator_tag> // + && derived_from<_Iter_cat_t>, forward_iterator_tag>, + forward_iterator_tag, input_iterator_tag>>>; + }; + + template + class _Iterator : public _Category_base<_Maybe_const<_Const, _Vw>, _InnerRng<_Const>, + _Maybe_const<_Const, _Pat>, is_reference_v<_InnerRng<_Const>>> { + private: + friend join_with_view; + + template + friend class _Iterator; + + template + friend class _Sentinel; + + using _Parent_t = _Maybe_const<_Const, join_with_view>; + using _Base = _Maybe_const<_Const, _Vw>; + using _PatternBase = _Maybe_const<_Const, _Pat>; + + using _OuterIter = iterator_t<_Base>; + using _InnerIter = iterator_t<_InnerRng<_Const>>; + using _PatternIter = iterator_t<_PatternBase>; + + // 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>>; + + _Parent_t* _Parent{}; + /* [[no_unique_address]] */ _OuterIter _Outer_it{}; + variant<_PatternIter, _InnerIter> _Inner_it{}; + + constexpr _Iterator(_Parent_t& _Parent_, iterator_t<_Base> _Outer_) + : _Parent{_STD addressof(_Parent_)}, _Outer_it{_STD move(_Outer_)} { + if (_Outer_it != _RANGES end(_Parent->_Range)) { + auto&& _Inner = _Update_inner(); + _Inner_it.template emplace<1>(_RANGES begin(_Inner)); + _Satisfy(); + } + } + + _NODISCARD constexpr auto&& _Update_inner() { + if constexpr (_Deref_is_glvalue) { + return *_Outer_it; + } else { + return _Parent->_Inner._Emplace(_Not_quite_object::_Construct_tag{}, _Outer_it)._Val; + } + } + + _NODISCARD constexpr auto&& _Get_inner() { + if constexpr (_Deref_is_glvalue) { + return *_Outer_it; + } else { + return (*_Parent->_Inner)._Val; + } + } + + constexpr void _Satisfy() { + while (true) { + if (_Inner_it.index() == 0) { + if (_STD get<0>(_Inner_it) != _RANGES end(_Parent->_Pattern)) { + break; + } + + auto&& _Inner = _Update_inner(); + _Inner_it.template emplace<1>(_RANGES begin(_Inner)); + } else { + auto&& _Inner = _Get_inner(); + if (_STD get<1>(_Inner_it) != _RANGES end(_Inner)) { + break; + } + + ++_Outer_it; + if (_Outer_it == _RANGES end(_Parent->_Range)) { + if constexpr (_Deref_is_glvalue) { + _Inner_it.template emplace<0>(); + } + break; + } + + _Inner_it.template emplace<0>(_RANGES begin(_Parent->_Pattern)); + } + } + } + + public: + using iterator_concept = // + conditional_t<_Deref_is_glvalue && bidirectional_range<_Base> // + && _Bidirectional_common<_InnerRng<_Const>> && _Bidirectional_common<_PatternBase>, + bidirectional_iterator_tag, + conditional_t<_Deref_is_glvalue && forward_range<_Base> && forward_range<_InnerRng<_Const>>, + forward_iterator_tag, input_iterator_tag>>; + using value_type = common_type_t, iter_value_t<_PatternIter>>; + using difference_type = common_type_t, iter_difference_t<_InnerIter>, + iter_difference_t<_PatternIter>>; + + // clang-format off + _Iterator() requires default_initializable<_OuterIter> = default; + // clang-format on + + constexpr _Iterator(_Iterator _It) requires _Const // + && convertible_to, _OuterIter> // + && convertible_to>, _InnerIter> // + && convertible_to, _OuterIter> // + : _Outer_it{_STD move(_It._Outer_it)}, _Parent{_It._Parent} { + if (_It._Inner_it.index() == 0) { + _Inner_it.template emplace<0>(_STD get<0>(_STD move(_It._Inner_it))); + } else { + _Inner_it.template emplace<1>(_STD get<1>(_STD move(_It._Inner_it))); + } + } + + _NODISCARD constexpr decltype(auto) operator*() const { + using _Ref = common_reference_t, iter_reference_t<_PatternIter>>; + return _STD visit([](auto&& _It) -> _Ref { return *_It; }, _Inner_it); + } + + constexpr _Iterator& operator++() { + _STD visit([](auto&& _It) { ++_It; }, _Inner_it); + _Satisfy(); + return *this; + } + + constexpr void operator++(int) { + ++*this; + } + + constexpr _Iterator operator++(int) requires _Deref_is_glvalue + && forward_iterator<_OuterIter> && forward_iterator<_InnerIter> { + auto _Tmp = *this; + ++*this; + return _Tmp; + } + + constexpr _Iterator& operator--() requires _Deref_is_glvalue && bidirectional_range<_Base> // + && _Bidirectional_common<_InnerRng<_Const>> && _Bidirectional_common<_PatternBase> { + if (_Outer_it == _RANGES end(_Parent->_Range)) { + --_Outer_it; + auto&& _Inner = *_Outer_it; + _Inner_it.template emplace<1>(_RANGES end(_Inner)); + } + + while (true) { + if (_Inner_it.index() == 0) { + auto& _It = _STD get<0>(_Inner_it); + if (_It == _RANGES begin(_Parent->_Pattern)) { + --_Outer_it; + auto&& _Inner = *_Outer_it; + _Inner_it.template emplace<1>(_RANGES end(_Inner)); + } else { + break; + } + } else { + auto& _It = _STD get<1>(_Inner_it); + auto&& _Inner = *_Outer_it; + if (_It == _RANGES begin(_Inner)) { + _Inner_it.template emplace<0>(_RANGES end(_Parent->_Pattern)); + } else { + break; + } + } + } + + _STD visit([](auto&& _It) { --_It; }, _Inner_it); + return *this; + } + + constexpr _Iterator operator--(int) requires _Deref_is_glvalue && bidirectional_range<_Base> // + && _Bidirectional_common<_InnerRng<_Const>> && _Bidirectional_common<_PatternBase> { + auto _Tmp = *this; + --*this; + return _Tmp; + } + + friend constexpr bool operator==(const _Iterator& _Left, const _Iterator& _Right) requires _Deref_is_glvalue + && equality_comparable<_OuterIter> && equality_comparable<_InnerIter> { + return _Left._Outer_it == _Right._Outer_it && _Left._Inner_it == _Right._Inner_it; + } + + friend constexpr decltype(auto) iter_move(const _Iterator& _It) { + using _Rvalue_ref = + common_reference_t, iter_rvalue_reference_t<_PatternIter>>; + return _STD visit<_Rvalue_ref>(_RANGES iter_move, _It._Inner_it); + } + + friend constexpr void iter_swap(const _Iterator& _Left, + const _Iterator& _Right) requires indirectly_swappable<_InnerIter, _PatternIter> { + _STD visit(_RANGES iter_swap, _Left._Inner_it, _Right._Inner_it); + } + }; + + template + class _Sentinel { + private: + friend join_with_view; + + using _Parent_t = _Maybe_const<_Const, join_with_view>; + using _Base = _Maybe_const<_Const, _Vw>; + + /* [[no_unique_address]] */ sentinel_t<_Base> _Last{}; + + constexpr explicit _Sentinel(_Parent_t& _Parent) noexcept( + noexcept(_RANGES end(_Parent._Range)) + && is_nothrow_move_constructible_v>) // strengthened + : _Last(_RANGES end(_Parent._Range)) {} + + template + _NODISCARD constexpr bool _Equal(const _Iterator<_OtherConst>& _It) const + noexcept(noexcept(_Implicitly_convert_to(_It._Outer_it == _Last))) { + _STL_INTERNAL_STATIC_ASSERT( + sentinel_for, iterator_t<_Maybe_const<_OtherConst, _Vw>>>); + return _It._Outer_it == _Last; + } + + public: + _Sentinel() = default; + + 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, iterator_t<_Maybe_const<_OtherConst, _Vw>>> + _NODISCARD friend constexpr bool operator==(const _Iterator<_OtherConst>& _Left, + const _Sentinel& _Right) noexcept(noexcept(_Right._Equal(_Left))) /* strengthened */ { + return _Right._Equal(_Left); + } + }; + + public: + // clang-format off + join_with_view() requires default_initializable<_Vw> && default_initializable<_Pat> = default; + // clang-format on + + constexpr join_with_view(_Vw _Range_, _Pat _Pattern_) noexcept( + is_nothrow_move_constructible_v<_Vw>&& is_nothrow_move_constructible_v<_Pat>) // strengthened + : _Range{_STD move(_Range_)}, _Pattern{_STD move(_Pattern_)} {} + + template + requires constructible_from<_Vw, views::all_t<_Rng>> && constructible_from<_Pat, + single_view>>> + constexpr join_with_view(_Rng&& _Range_, range_value_t<_InnerRng> _Elem) noexcept( + noexcept(_Vw(views::all(_STD forward<_Rng>(_Range_)))) && noexcept( + _Pat(views::single(_STD move(_Elem))))) // strengthened + : _Range(views::all(_STD forward<_Rng>(_Range_))), _Pattern(views::single(_STD move(_Elem))) {} + + _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> && _Simple_view<_Pat>; + return _Iterator<_Use_const>{*this, _RANGES begin(_Range)}; + } + + _NODISCARD constexpr auto begin() const +#ifdef __clang__ // TRANSITION, LLVM-47414 + requires _Can_const_join_with<_Vw, _Pat> +#else // ^^^ workaround / no workaround vvv + requires input_range && forward_range && is_reference_v<_InnerRng> +#endif // TRANSITION, LLVM-47414 + { + return _Iterator{*this, _RANGES begin(_Range)}; + } + + _NODISCARD constexpr auto end() { + constexpr bool _Both_simple = _Simple_view<_Vw> && _Simple_view<_Pat>; + if constexpr (forward_range<_Vw> // + && is_reference_v<_InnerRng> && forward_range<_InnerRng> // + && common_range<_Vw> && common_range<_InnerRng>) { + return _Iterator<_Both_simple>{*this, _RANGES end(_Range)}; + } else { + return _Sentinel<_Both_simple>{*this}; + } + } + + _NODISCARD constexpr auto end() const +#ifdef __clang__ // TRANSITION, LLVM-47414 + requires _Can_const_join_with<_Vw, _Pat> +#else // ^^^ workaround / no workaround vvv + requires input_range && forward_range && is_reference_v<_InnerRng> +#endif // TRANSITION, LLVM-47414 + { + if constexpr (forward_range && forward_range<_InnerRng> // + && common_range<_Vw> && common_range<_InnerRng>) { + return _Iterator{*this, _RANGES end(_Range)}; + } else { + return _Sentinel{*this}; + } + } + }; + + template + join_with_view(_Rng&&, _Pat&&) -> join_with_view, views::all_t<_Pat>>; + + template + join_with_view(_Rng&&, range_value_t>) + -> join_with_view, single_view>>>; + + namespace views { + struct _Join_with_fn { + // clang-format off + template + _NODISCARD constexpr auto operator()(_Rng&& _Range, _Pat&& _Pattern) const noexcept( + noexcept(join_with_view(_STD forward<_Rng>(_Range), _STD forward<_Pat>(_Pattern)))) requires requires { + join_with_view(_STD forward<_Rng>(_Range), _STD forward<_Pat>(_Pattern)); + } + { // clang-format on + return join_with_view(_STD forward<_Rng>(_Range), _STD forward<_Pat>(_Pattern)); + } + + // clang-format off + template + requires constructible_from, _Delim> + _NODISCARD constexpr auto operator()(_Delim&& _Delimiter) const + noexcept(is_nothrow_constructible_v, _Delim>) { + // clang-format on + return _Range_closure<_Join_with_fn, decay_t<_Delim>>{_STD forward<_Delim>(_Delimiter)}; + } + }; + + inline constexpr _Join_with_fn join_with; + } // namespace views + template , iterator_t<_Vw>> _Pr> requires view<_Vw> && is_object_v<_Pr> class chunk_by_view : public _Cached_position<_Vw, chunk_by_view<_Vw, _Pr>> { diff --git a/tests/std/test.lst b/tests/std/test.lst index 695faf3d5bf..31fe54c8d7b 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -468,6 +468,7 @@ tests\P2231R1_complete_constexpr_optional_variant tests\P2273R3_constexpr_unique_ptr tests\P2401R0_conditional_noexcept_for_exchange tests\P2415R2_owning_view +tests\P2441R2_views_join_with tests\P2443R1_views_chunk_by tests\P2443R1_views_chunk_by_death tests\VSO_0000000_allocator_propagation diff --git a/tests/std/tests/P2441R2_views_join_with/env.lst b/tests/std/tests/P2441R2_views_join_with/env.lst new file mode 100644 index 00000000000..8ac7033b206 --- /dev/null +++ b/tests/std/tests/P2441R2_views_join_with/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/P2441R2_views_join_with/test.cpp b/tests/std/tests/P2441R2_views_join_with/test.cpp new file mode 100644 index 00000000000..6438366b349 --- /dev/null +++ b/tests/std/tests/P2441R2_views_join_with/test.cpp @@ -0,0 +1,516 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +using namespace std; + +template +concept CanViewJoinWith = + requires(Rng&& r, Delimiter&& d) { views::join_with(forward(r), forward(d)); }; + +template +struct delimiter_view_impl { + template + using apply = ranges::single_view>; +}; +template <> +struct delimiter_view_impl { + template + using apply = views::all_t; +}; +template +using delimiter_view_t = + typename delimiter_view_impl>>::template apply; + +template +constexpr void test_one(Outer&& rng, Delimiter&& delimiter, Expected&& expected) { + using ranges::join_with_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::sentinel_t, ranges::range_value_t, ranges::range_reference_t; + + using Inner = range_value_t; + constexpr bool deref_is_glvalue = is_reference_v>; + + using V = views::all_t; + using DV = delimiter_view_t; + using R = join_with_view; + + // Validate type properties + STATIC_ASSERT(ranges::view); + STATIC_ASSERT(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 && bidirectional_range && common_range) ); + // clang-format on + STATIC_ASSERT(!ranges::random_access_range); + + // Validate range adaptor object and range adaptor closure + constexpr bool is_view = ranges::view>; + const auto closure = views::join_with(delimiter); + + // ... with lvalue argument + STATIC_ASSERT(CanViewJoinWith == (!is_view || copy_constructible) ); + if constexpr (CanViewJoinWith) { + constexpr bool is_noexcept = + (!is_view || is_nothrow_copy_constructible_v) &&is_nothrow_copy_constructible_v; + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(views::join_with(rng, delimiter)) == is_noexcept); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(rng | closure) == is_noexcept); + } + + // ... with const lvalue argument + STATIC_ASSERT( + CanViewJoinWith&, Delimiter&> == (!is_view || copy_constructible) ); + if constexpr (CanViewJoinWith&, Delimiter&>) { + using RC = join_with_view&>, DV>; + constexpr bool is_noexcept = + (!is_view || is_nothrow_copy_constructible_v) &&is_nothrow_copy_constructible_v; + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(views::join_with(as_const(rng), delimiter)) == is_noexcept); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(as_const(rng) | closure) == is_noexcept); + } + + // ... with rvalue argument + STATIC_ASSERT( + CanViewJoinWith, Delimiter&> == (is_view || movable>) ); + if constexpr (CanViewJoinWith, Delimiter&>) { + using RS = join_with_view>, DV>; + constexpr bool is_noexcept = is_nothrow_move_constructible_v && is_nothrow_copy_constructible_v; + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(views::join_with(move(rng), delimiter)) == is_noexcept); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(move(rng) | closure) == is_noexcept); + } + + // ... with const rvalue argument + STATIC_ASSERT(CanViewJoinWith, Delimiter&> == (is_view && copy_constructible) ); + if constexpr (CanViewJoinWith, Delimiter&>) { + constexpr bool is_noexcept = is_nothrow_copy_constructible_v && is_nothrow_copy_constructible_v; + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(views::join_with(move(as_const(rng)), delimiter)) == is_noexcept); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(move(as_const(rng)) | closure) == is_noexcept); + } + + // Validate deduction guide + same_as auto r = join_with_view{forward(rng), forward(delimiter)}; + assert(ranges::equal(r, expected)); + + // Validate view_interface::empty and operator bool + const bool is_empty = ranges::empty(expected); + STATIC_ASSERT(CanEmpty == forward_range); + STATIC_ASSERT(CanMemberEmpty == CanEmpty); + STATIC_ASSERT(CanBool == CanEmpty); + if constexpr (CanMemberEmpty) { + assert(r.empty() == is_empty); + assert(static_cast(r) == !is_empty); + + STATIC_ASSERT(CanEmpty == forward_range); + STATIC_ASSERT(CanMemberEmpty == CanEmpty); + STATIC_ASSERT(CanBool == CanEmpty); + if constexpr (CanMemberEmpty) { + assert(as_const(r).empty() == is_empty); + assert(static_cast(as_const(r)) == !is_empty); + } + } + + // Validate join_with_view::begin + STATIC_ASSERT(CanMemberBegin); + // clang-format off + STATIC_ASSERT(CanMemberBegin + == (input_range && forward_range && is_reference_v>) ); + // clang-format on + if (forward_range) { // intentionally not if constexpr + const auto i = r.begin(); + if (!is_empty) { + assert(*i == *ranges::begin(expected)); + } + + if constexpr (copy_constructible) { + auto r2 = r; + const auto i2 = r2.begin(); + if (!is_empty) { + assert(*i2 == *i); + } + } + + static_assert(CanMemberBegin == CanBegin); + if constexpr (CanMemberBegin) { + const same_as> auto ci = as_const(r).begin(); + if (!is_empty) { + assert(*ci == *i); + } + + if constexpr (copy_constructible) { + const auto r2 = r; + const same_as> auto ci2 = r2.begin(); + if (!is_empty) { + assert(*ci2 == *i); + } + } + } + } + + // Validate join_with_view::end + static_assert(CanMemberEnd); + // clang-format off + static_assert(CanMemberEnd + == (input_range && forward_range && is_reference_v>) ); + static_assert(common_range + == (forward_range && is_reference_v> && common_range + && forward_range && common_range) ); + static_assert(common_range + == (forward_range && forward_range && is_reference_v> + && common_range && forward_range> + && common_range>) ); + // clang-format on + const same_as> auto 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 same_as> auto 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 same_as> auto cs2 = r2.end(); + assert(*prev(cs2) == *prev(end(expected))); + } + } + } + } + + // Validate view_interface::data + STATIC_ASSERT(!CanData); + STATIC_ASSERT(!CanData); + + // Validate view_interface::size + STATIC_ASSERT(!CanSize); + STATIC_ASSERT(!CanSize); + + // 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) ); + static_assert(CanMemberBack == (bidirectional_range && common_range) ); + 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_with_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(); + if (!ranges::empty(*bi1)) { + auto&& inner_first = *bi1; + assert(*begin(inner_first) == *begin(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(); + if (!ranges::empty(*bi2)) { + auto&& inner_first = *bi2; + assert(*begin(inner_first) == *begin(expected)); + } + } + } + } +} + +constexpr string_view input[] = {{}, "This"sv, "is"sv, {}, "a"sv, "test"sv, {}, {}}; +constexpr string_view expected_single = "*This*is**a*test**"; +constexpr string_view expected_range = "*#This*#is*#*#a*#test*#*#"; +constexpr string_view expected_empty = "Thisisatest"; + +struct instantiator { + template + static constexpr void call() { + static_assert(ranges::size(input) == 8); + + { // Single-element delimiter + Inner inner_ranges[] = {Inner{span{input[0]}}, Inner{span{input[1]}}, Inner{span{input[2]}}, + Inner{span{input[3]}}, Inner{span{input[4]}}, Inner{span{input[5]}}, Inner{span{input[6]}}, + Inner{span{input[7]}}}; + Outer r{inner_ranges}; + test_one(r, '*', expected_single); + + Outer empty{span{}}; + test_one(empty, '*', views::empty); + } + { // Empty delimiter + Inner inner_ranges[] = {Inner{span{input[0]}}, Inner{span{input[1]}}, Inner{span{input[2]}}, + Inner{span{input[3]}}, Inner{span{input[4]}}, Inner{span{input[5]}}, Inner{span{input[6]}}, + Inner{span{input[7]}}}; + Outer r{inner_ranges}; + test_one(r, views::empty, expected_empty); + + Outer empty{span{}}; + test_one(empty, views::empty, views::empty); + } + { // Range delimiter + Inner inner_ranges[] = {Inner{span{input[0]}}, Inner{span{input[1]}}, Inner{span{input[2]}}, + Inner{span{input[3]}}, Inner{span{input[4]}}, Inner{span{input[5]}}, Inner{span{input[6]}}, + Inner{span{input[7]}}}; + Outer r{inner_ranges}; + test_one(r, "*#"sv, expected_range); + + Outer empty{span{}}; + test_one(empty, "*#"sv, views::empty); + } + } +}; + +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, outer, and pattern 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 the pattern 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) + 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; +} + +struct throwing_iterator { + struct tag {}; + + throwing_iterator() = default; + throwing_iterator(int j) : i(j) {} + throwing_iterator(const throwing_iterator& other) : i(other.i) { + if (i == 1) { + throw tag{}; + } + } + throwing_iterator& operator=(const throwing_iterator&) = default; + + using difference_type = ptrdiff_t; + using value_type = int; + + throwing_iterator& operator++() { + ++i; + return *this; + } + throwing_iterator operator++(int) { + return throwing_iterator{i++}; + } + throwing_iterator& operator--() { + --i; + return *this; + } + throwing_iterator operator--(int) { + return throwing_iterator{i--}; + } + int operator*() const { + return i; + } + bool operator==(const throwing_iterator& other) const { + return i == other.i; + } + int i; +}; + +void test_valueless_iterator() { + auto r = vector{"0"sv, ""sv} | views::join_with(ranges::subrange{throwing_iterator{0}, throwing_iterator{2}}); + + auto it = r.begin(); + ++it; + ++it; + assert(*it == 1); + auto it2 = r.begin(); + try { + it2 = it; + assert(false); + } catch (throwing_iterator::tag&) { + } + + try { + (void) *it2; + assert(false); + } catch (std::bad_variant_access&) { + } + try { + (void) ++it2; + assert(false); + } catch (std::bad_variant_access&) { + } + try { + (void) --it2; + assert(false); + } catch (std::bad_variant_access&) { + } + try { + (void) ranges::iter_move(it2); + assert(false); + } catch (std::bad_variant_access&) { + } +} + +int main() { + { + auto filtered_and_joined = + vector>{} | views::filter([](auto) { return true; }) | views::join_with(0); + assert(ranges::empty(filtered_and_joined)); + } + + STATIC_ASSERT(instantiation_test()); + instantiation_test(); + + test_valueless_iterator(); +}