diff --git a/stl/inc/__msvc_iter_core.hpp b/stl/inc/__msvc_iter_core.hpp index 96efb43363..aeacf38ca3 100644 --- a/stl/inc/__msvc_iter_core.hpp +++ b/stl/inc/__msvc_iter_core.hpp @@ -310,7 +310,7 @@ struct _Iter_traits_category2 { // clang-format off template -concept _Cpp17_forward_delta = constructible_from<_It> && is_lvalue_reference_v> +concept _Cpp17_forward_delta = constructible_from<_It> && is_reference_v> && same_as>, typename indirectly_readable_traits<_It>::value_type> && requires(_It __i) { { __i++ } -> convertible_to; diff --git a/stl/inc/ranges b/stl/inc/ranges index 19f422d78f..e10fb17680 100644 --- a/stl/inc/ranges +++ b/stl/inc/ranges @@ -1311,7 +1311,7 @@ namespace ranges { is_nothrow_move_constructible_v<_Wi>&& is_nothrow_move_constructible_v<_Bo>) // strengthened : _Value(_STD move(_Value_)), _Bound(_STD move(_Bound_)) { if constexpr (totally_ordered_with<_Wi, _Bo>) { - _STL_ASSERT(_Value_ <= _Bound_, "Per N4878 [range.iota.view]/8, the first argument must precede the " + _STL_ASSERT(_Value_ <= _Bound_, "Per N4928 [range.iota.view]/8, the first argument must precede the " "second when their types are totally ordered."); } } @@ -2018,7 +2018,7 @@ namespace ranges { _NODISCARD constexpr _Iterator begin() { #if _CONTAINER_DEBUG_LEVEL > 0 _STL_VERIFY( - _Pred, "N4861 [range.filter.view]/3 forbids calling begin on a filter_view that holds no predicate"); + _Pred, "N4928 [range.filter.view]/3 forbids calling begin on a filter_view that holds no predicate"); #endif // _CONTAINER_DEBUG_LEVEL > 0 if constexpr (forward_range<_Vw>) { if (this->_Has_cache()) { @@ -2091,12 +2091,12 @@ namespace ranges { template requires forward_range<_Maybe_const<_Const, _Vw>> struct _Category_base<_Const> { - using _Base = _Maybe_const<_Const, _Vw>; - using iterator_category = conditional_t< - is_lvalue_reference_v&, range_reference_t<_Base>>>, - conditional_t>, contiguous_iterator_tag>, - random_access_iterator_tag, _Iter_cat_t>>, - input_iterator_tag>; + using _Base = _Maybe_const<_Const, _Vw>; + using iterator_category = + conditional_t&, range_reference_t<_Base>>>, + conditional_t>, contiguous_iterator_tag>, + random_access_iterator_tag, _Iter_cat_t>>, + input_iterator_tag>; }; template @@ -3015,7 +3015,7 @@ namespace ranges { is_nothrow_move_constructible_v<_Vw>) // strengthened : _Range(_STD move(_Range_)), _Count{_Count_} { #if _CONTAINER_DEBUG_LEVEL > 0 - _STL_VERIFY(_Count_ >= 0, "Numer of elements to drop must be non-negative (N4861 [range.drop.view]/1"); + _STL_VERIFY(_Count_ >= 0, "Number of elements to drop must be non-negative (N4928 [range.drop.view]/1"); #endif // _CONTAINER_DEBUG_LEVEL > 0 } @@ -3216,7 +3216,7 @@ namespace ranges { _NODISCARD constexpr auto begin() { #if _CONTAINER_DEBUG_LEVEL > 0 _STL_VERIFY( - _Pred, "N4885 [range.drop.while.view] forbids calling begin on a drop_while_view with no predicate"); + _Pred, "N4928 [range.drop.while.view]/3 forbids calling begin on a drop_while_view with no predicate"); #endif // _CONTAINER_DEBUG_LEVEL > 0 if constexpr (forward_range<_Vw>) { if (this->_Has_cache()) { @@ -3716,7 +3716,7 @@ namespace ranges { using _PatternBase = _Maybe_const<_Const, _Pat>; using iterator_category = conditional_t< - !is_lvalue_reference_v, range_reference_t<_PatternBase>>>, + !is_reference_v, range_reference_t<_PatternBase>>>, input_iterator_tag, conditional_t && common_range<_PatternBase> && derived_from<_Iter_cat_t>, bidirectional_iterator_tag> @@ -6483,7 +6483,7 @@ namespace ranges { is_nothrow_move_constructible_v<_Vw>) /* strengthened */ : _Range(_STD move(_Range_)), _Count{_Count_} { #if _CONTAINER_DEBUG_LEVEL > 0 - _STL_VERIFY(_Count > 0, "The window size must be positive (N4917 [range.slide.view]/1)"); + _STL_VERIFY(_Count > 0, "The window size must be positive (N4928 [range.slide.view]/1)"); #endif // _CONTAINER_DEBUG_LEVEL > 0 } diff --git a/tests/std/tests/P0896R4_ranges_iterator_machinery/test.cpp b/tests/std/tests/P0896R4_ranges_iterator_machinery/test.cpp index 60f3a66777..09e34c9830 100644 --- a/tests/std/tests/P0896R4_ranges_iterator_machinery/test.cpp +++ b/tests/std/tests/P0896R4_ranges_iterator_machinery/test.cpp @@ -204,6 +204,56 @@ struct std::indirectly_readable_traits> { using value_type = double; }; +// Validate iterator_category of iterators whose reference types are rvalue references (LWG-3798). +struct xvalue_forward_iter { + using value_type = double; + using difference_type = long; + + value_type&& operator*() const; + xvalue_forward_iter& operator++(); + xvalue_forward_iter operator++(int); + + bool operator==(xvalue_forward_iter const&) const = default; +}; + +struct xvalue_bidi_iter { + using value_type = double; + using difference_type = long; + using reference = value_type&&; + + value_type&& operator*() const; + xvalue_bidi_iter& operator++(); + xvalue_bidi_iter operator++(int); + + bool operator==(xvalue_bidi_iter const&) const = default; + + xvalue_bidi_iter& operator--(); + xvalue_bidi_iter operator--(int); +}; + +struct xvalue_random_iter { + using value_type = double; + using D = long; + + value_type&& operator*() const; + xvalue_random_iter& operator++(); + xvalue_random_iter operator++(int); + + xvalue_random_iter& operator--(); + xvalue_random_iter operator--(int); + + bool operator==(xvalue_random_iter const&) const; + std::strong_ordering operator<=>(xvalue_random_iter const&) const; + + value_type&& operator[](D) const; + xvalue_random_iter& operator-=(D); + xvalue_random_iter operator-(D) const; + D operator-(xvalue_random_iter const&) const; + xvalue_random_iter& operator+=(D); + xvalue_random_iter operator+(D) const; + friend xvalue_random_iter operator+(D, xvalue_random_iter const&); +}; + template struct proxy_iterator { using difference_type = int; @@ -592,7 +642,7 @@ inline constexpr std::size_t contig_iterator_archetype_max = 34; #pragma warning(pop) struct iter_concept_example { - // bidirectional_iterator and Cpp17InputIterator, but not Cpp17ForwardIterator (N4820 [iterator.concepts.general]/2) + // bidirectional_iterator and Cpp17InputIterator, but not Cpp17ForwardIterator (N4928 [iterator.concepts.general]/2) using value_type = int; using difference_type = int; @@ -866,43 +916,45 @@ namespace iterator_traits_test { return true; } - // N4820 [iterator.traits]/3.2: "Otherwise, if I satisfies the exposition-only concept cpp17-input-iterator..." + // N4928 [iterator.traits]/3.2: "Otherwise, if I satisfies the exposition-only concept cpp17-input-iterator..." - // N4820 [iterator.traits]: + // N4928 [iterator.traits]: // * 3.2.1: "... Otherwise, pointer names void." // * 3.2.2: "... Otherwise, reference names iter_reference_t." // * 3.2.3.4 "... Otherwise, iterator_category names... input_iterator_tag." STATIC_ASSERT(check()); - // N4820 [iterator.traits]: + // N4928 [iterator.traits]: // * 3.2.1: "... Otherwise, pointer names void." // * 3.2.2: "... Otherwise, reference names iter_reference_t." // * 3.2.3.3 "... Otherwise, iterator_category names... forward_iterator_tag if I satisfies cpp17-forward-iterator." STATIC_ASSERT( check, no_such_type, forward_iterator_tag, double, long, void, double const&>()); - // N4820 [iterator.traits]/3.2.1: "... Otherwise, if decltype(declval().operator->()) is well-formed, then + STATIC_ASSERT(check()); + // N4928 [iterator.traits]/3.2.1: "... Otherwise, if decltype(declval().operator->()) is well-formed, then // pointer names that type." STATIC_ASSERT(check>, no_such_type, forward_iterator_tag, double, long, double const*, double const&>()); - // N4820 [iterator.traits]/3.2.3: "If the qualified-id I::iterator_category is valid and denotes a type, + // N4928 [iterator.traits]/3.2.3: "If the qualified-id I::iterator_category is valid and denotes a type, // iterator_category names that type." STATIC_ASSERT(check>, no_such_type, output_iterator_tag, double, long, void, double const&>()); STATIC_ASSERT(check>, no_such_type, input_iterator_tag, double, long, void, double const&>()); - // N4820 [iterator.traits]: + // N4928 [iterator.traits]: // * 3.2.1: "... Otherwise, pointer names void." // * 3.2.2: "If the qualified-id I::reference is valid and denotes a type, reference names that type." // * 3.2.3.2 "... Otherwise, iterator_category names... bidirectional_iterator_tag if I satisfies // cpp17-bidirectional-iterator." STATIC_ASSERT( check, no_such_type, bidirectional_iterator_tag, double, long, void, double const&>()); - // N4820 [iterator.traits]/3.2.1: "... Otherwise, if decltype(declval().operator->()) is well-formed, then + STATIC_ASSERT(check()); + // N4928 [iterator.traits]/3.2.1: "... Otherwise, if decltype(declval().operator->()) is well-formed, then // pointer names that type." STATIC_ASSERT(check>, no_such_type, bidirectional_iterator_tag, double, long, double const*, double const&>()); - // N4820 [iterator.traits]/3.2.3: "If the qualified-id I::iterator_category is valid and denotes a type, + // N4928 [iterator.traits]/3.2.3: "If the qualified-id I::iterator_category is valid and denotes a type, // iterator_category names that type." STATIC_ASSERT(check>, no_such_type, output_iterator_tag, double, long, void, double const&>()); @@ -911,22 +963,23 @@ namespace iterator_traits_test { STATIC_ASSERT(check>, no_such_type, forward_iterator_tag, double, long, void, double const&>()); - // N4820 [iterator.traits]: + // N4928 [iterator.traits]: // * 3.2.1: "... Otherwise, pointer names void." // * 3.2.2: "... Otherwise, reference names iter_reference_t." // * 3.2.3.1 "... Otherwise, iterator_category names... random_access_iterator_tag if I satisfies // cpp17-random-access-iterator." STATIC_ASSERT( check, no_such_type, random_access_iterator_tag, double, long, void, double const&>()); + STATIC_ASSERT(check()); STATIC_ASSERT( check, no_such_type, random_access_iterator_tag, double, long, void, double const&>()); - // N4820 [iterator.traits]/3.2.1: "... Otherwise, if decltype(declval().operator->()) is well-formed, then + // N4928 [iterator.traits]/3.2.1: "... Otherwise, if decltype(declval().operator->()) is well-formed, then // pointer names that type." STATIC_ASSERT(check>, no_such_type, random_access_iterator_tag, double, long, double const*, double const&>()); STATIC_ASSERT(check>, no_such_type, random_access_iterator_tag, double, long, double const*, double const&>()); - // N4820 [iterator.traits]/3.2.3: "If the qualified-id I::iterator_category is valid and denotes a type, + // N4928 [iterator.traits]/3.2.3: "If the qualified-id I::iterator_category is valid and denotes a type, // iterator_category names that type." STATIC_ASSERT(check>, no_such_type, output_iterator_tag, double, long, void, double const&>()); @@ -945,7 +998,7 @@ namespace iterator_traits_test { STATIC_ASSERT(check>, no_such_type, contiguous_iterator_tag, double, long, void, double const&>()); - // N4820 [iterator.traits]/3.3: "Otherwise, if I satisfies the exposition-only concept cpp17-iterator..." + // N4928 [iterator.traits]/3.3: "Otherwise, if I satisfies the exposition-only concept cpp17-iterator..." template struct simple_output_iter : Base { simple_output_iter const& operator*() const; @@ -959,13 +1012,13 @@ namespace iterator_traits_test { // "... otherwise, it names void." STATIC_ASSERT(check, no_such_type, output_iterator_tag, void, void, void, void>()); - // N4820 [iterator.traits]/3.4: "Otherwise, iterator_traits has no members by any of the above names." + // N4928 [iterator.traits]/3.4: "Otherwise, iterator_traits has no members by any of the above names." STATIC_ASSERT(has_empty_traits); STATIC_ASSERT(has_empty_traits); STATIC_ASSERT(has_empty_traits); STATIC_ASSERT(has_empty_traits); - // N4820 [iterator.traits]/5: "iterator_traits is specialized for pointers..." + // N4928 [iterator.traits]/5: "iterator_traits is specialized for pointers..." STATIC_ASSERT(check()); STATIC_ASSERT(check()); @@ -986,7 +1039,8 @@ namespace iterator_cust_move_test { template concept can_iter_rvalue_ref = requires { typename iter_rvalue_reference_t; }; - // N4820 [iterator.cust.move]/1.1 "iter_move(E), if that expression is valid, with overload resolution..." + // N4928 [iterator.cust.move]/1.1 "iter_move(E), if [...] iter_move(E) is a well-formed expression when [...] + // performing argument-dependent lookup only." struct friend_hook { friend constexpr double iter_move(friend_hook) noexcept { return 3.14; @@ -1015,7 +1069,7 @@ namespace iterator_cust_move_test { STATIC_ASSERT(static_cast(ranges::iter_move(E1::x)) == 0); STATIC_ASSERT(noexcept(ranges::iter_move(E1::x))); - // N4820 [iterator.cust.move]/1.2.1 "if *E is an lvalue, std::move(*E)" + // N4928 [iterator.cust.move]/1.2.1 "if *E is an lvalue, std::move(*E)" static constexpr int some_ints[] = {0, 1, 2, 3}; STATIC_ASSERT(same_as, int&&>); STATIC_ASSERT(ranges::iter_move(&some_ints[1]) == 1); @@ -1062,7 +1116,7 @@ namespace iterator_cust_move_test { STATIC_ASSERT(same_as, int const&&>); // oblivious to nested types STATIC_ASSERT(ranges::iter_move(with_bogus_typedefs{}) == 1); - // N4820 [iterator.cust.move]/1.2.2 "otherwise, *E." + // N4928 [iterator.cust.move]/1.2.2 "otherwise, *E." struct ref_is_prvalue { int operator*() const { return 42; @@ -1076,7 +1130,7 @@ namespace iterator_cust_move_test { STATIC_ASSERT(same_as, int&&>); STATIC_ASSERT(!noexcept(ranges::iter_move(ref_is_xvalue{}))); - // N4820 [iterator.cust.move]/1.3 "Otherwise, ranges::iter_move(E) is ill-formed." + // N4928 [iterator.cust.move]/1.3 "Otherwise, ranges::iter_move(E) is ill-formed." STATIC_ASSERT(!can_iter_move); STATIC_ASSERT(!can_iter_move); STATIC_ASSERT(!can_iter_move); @@ -1093,7 +1147,8 @@ namespace iterator_cust_swap_test { template concept can_iter_swap = requires(T&& t, U&& u) { ranges::iter_swap(std::forward(t), std::forward(u)); }; - // N4820 [iterator.cust.swap]/4.1: "(void)iter_swap(E1, E2), if that expression is valid, with..." + // N4928 [iterator.cust.swap]/4.1: "(void)iter_swap(E1, E2), if [...] iter_swap(E1, E2) is a + // well-formed expression with overload resolution performed in a context [...]" namespace adl_barrier { template void iter_swap(T, U) = delete; @@ -1127,8 +1182,8 @@ namespace iterator_cust_swap_test { STATIC_ASSERT(bullet1); STATIC_ASSERT((ranges::iter_swap(E1::x, E1::x), true)); - // N4849 [iterator.cust.swap]/4.2: "Otherwise if the types of E1 and E2 each model indirectly_readable, and if the - // reference types of E1 and E2 model swappable_with, then ranges::swap(*E1, *E2)." + // N4928 [iterator.cust.swap]/4.2: "Otherwise, if the types of E1 and E2 each model indirectly_readable, + // and if the reference types of E1 and E2 model swappable_with, then ranges::swap(*E1, *E2)." // clang-format off template concept bullet2 = !bullet1 && indirectly_readable> @@ -1178,7 +1233,7 @@ namespace iterator_cust_swap_test { STATIC_ASSERT((ranges::iter_swap(swap_proxy_readable<0>{}, swap_proxy_readable<1>{}), true)); STATIC_ASSERT(noexcept(ranges::iter_swap(swap_proxy_readable<0>{}, swap_proxy_readable<1>{}))); - // N4820 [iterator.cust.swap]/4.3: "Otherwise, if the types T1 and T2 of E1 and E2 model + // N4928 [iterator.cust.swap]/4.3: "Otherwise, if the types T1 and T2 of E1 and E2 model // indirectly_movable_storable and indirectly_movable_storable..." // clang-format off template @@ -1204,7 +1259,7 @@ namespace iterator_cust_swap_test { STATIC_ASSERT(same_as{}, unswap_proxy_readable<1>{})), void>); STATIC_ASSERT(noexcept(ranges::iter_swap(unswap_proxy_readable<0>{}, unswap_proxy_readable<1>{}))); - // N4820 [iterator.cust.swap]/4.4: "Otherwise, ranges::iter_swap(E1, E2) is ill-formed." + // N4928 [iterator.cust.swap]/4.4: "Otherwise, ranges::iter_swap(E1, E2) is ill-formed." template concept bullet4 = (!can_iter_swap); @@ -3092,6 +3147,10 @@ namespace reverse_iterator_test { STATIC_ASSERT(same_as>::iterator_category, random_access_iterator_tag>); STATIC_ASSERT(same_as>::iterator_concept, bidirectional_iterator_tag>); STATIC_ASSERT(same_as>::iterator_category, bidirectional_iterator_tag>); + STATIC_ASSERT(same_as::iterator_concept, random_access_iterator_tag>); + STATIC_ASSERT(same_as::iterator_category, random_access_iterator_tag>); + STATIC_ASSERT(same_as::iterator_concept, bidirectional_iterator_tag>); + STATIC_ASSERT(same_as::iterator_category, bidirectional_iterator_tag>); // Validate operator-> for a pointer, and for non-pointers with and without operator->() // clang-format off @@ -3285,6 +3344,12 @@ namespace move_iterator_test { STATIC_ASSERT(same_as::iterator_concept, input_iterator_tag>); STATIC_ASSERT(same_as::iterator_category, input_iterator_tag>); STATIC_ASSERT(same_as>::iterator_concept, input_iterator_tag>); + STATIC_ASSERT(same_as::iterator_concept, random_access_iterator_tag>); + STATIC_ASSERT(same_as::iterator_category, random_access_iterator_tag>); + STATIC_ASSERT(same_as::iterator_concept, bidirectional_iterator_tag>); + STATIC_ASSERT(same_as::iterator_category, bidirectional_iterator_tag>); + STATIC_ASSERT(same_as::iterator_concept, forward_iterator_tag>); + STATIC_ASSERT(same_as::iterator_category, forward_iterator_tag>); STATIC_ASSERT(!has_member_iter_category>>); STATIC_ASSERT(same_as>::iterator_concept, input_iterator_tag>); STATIC_ASSERT(!has_member_iter_category>>); @@ -3448,6 +3513,12 @@ namespace counted_iterator_test { STATIC_ASSERT( same_as>>::iterator_category, forward_iterator_tag>); STATIC_ASSERT(same_as>::iterator_category, input_iterator_tag>); + STATIC_ASSERT( + same_as>::iterator_category, random_access_iterator_tag>); + STATIC_ASSERT( + same_as>::iterator_category, bidirectional_iterator_tag>); + STATIC_ASSERT( + same_as>::iterator_category, forward_iterator_tag>); // Validate postincrement STATIC_ASSERT(same_as&>()++), simple_input_iter>); diff --git a/tests/std/tests/P0896R4_views_transform/test.cpp b/tests/std/tests/P0896R4_views_transform/test.cpp index bb7ec19ba7..887884c942 100644 --- a/tests/std/tests/P0896R4_views_transform/test.cpp +++ b/tests/std/tests/P0896R4_views_transform/test.cpp @@ -363,6 +363,36 @@ constexpr void test_difference_on_const_functor(Rng&& rng) { } } +// Test xvalue ranges (LWG-3798) +struct move_fn { + constexpr auto&& operator()(auto&& x) const noexcept { + return move(x); + } +}; + +template +constexpr void test_xvalue_ranges(Rng&& rng) { + using ranges::transform_view, ranges::forward_range, ranges::iterator_t, ranges::range_reference_t; + + using V = views::all_t; + using TV = transform_view; + + auto r = forward(rng) | views::transform(move_fn{}); + STATIC_ASSERT(is_same_v); + + STATIC_ASSERT(is_rvalue_reference_v>); + + if constexpr (forward_range) { + using It = iterator_t; + using TVIt = iterator_t; + using VItCat = typename iterator_traits::iterator_category; + using TVItCat = typename iterator_traits::iterator_category; + STATIC_ASSERT( + is_same_v + || (is_same_v && is_same_v) ); + } +} + static constexpr int some_ints[] = {0, 1, 2, 3, 4, 5, 6, 7}; static constexpr int transformed_ints[] = {8, 9, 10, 11, 12, 13, 14, 15}; @@ -374,6 +404,9 @@ struct instantiator { R r2{some_ints}; test_difference_on_const_functor(r2); + + R r3{some_ints}; + test_xvalue_ranges(r3); } }; diff --git a/tests/std/tests/P2441R2_views_join_with/test.cpp b/tests/std/tests/P2441R2_views_join_with/test.cpp index 0233e7a5b0..d5b965a47c 100644 --- a/tests/std/tests/P2441R2_views_join_with/test.cpp +++ b/tests/std/tests/P2441R2_views_join_with/test.cpp @@ -55,6 +55,55 @@ constexpr void test_one(Outer&& rng, Delimiter&& delimiter, Expected&& expected) && common_range && bidirectional_range && common_range) ); STATIC_ASSERT(!ranges::random_access_range); + // Validate iterator_category + if constexpr (forward_range) { + using OuterIter = iterator_t; + using InnerIter = iterator_t>; + using PatternIter = iterator_t; + using OuterCat = typename iterator_traits::iterator_category; + using InnerCat = typename iterator_traits::iterator_category; + using PatternCat = typename iterator_traits::iterator_category; + + if constexpr (!is_reference_v, iter_reference_t>>) { + STATIC_ASSERT(same_as::iterator_category, input_iterator_tag>); + } else if constexpr (derived_from + && derived_from + && derived_from + && common_range> && common_range) { + STATIC_ASSERT(same_as::iterator_category, bidirectional_iterator_tag>); + } else if constexpr (derived_from + && derived_from + && derived_from) { + STATIC_ASSERT(same_as::iterator_category, forward_iterator_tag>); + } else { + STATIC_ASSERT(same_as::iterator_category, input_iterator_tag>); + } + } + + if constexpr (forward_range) { + using OuterIter = iterator_t; + using InnerIter = iterator_t>; + using PatternIter = iterator_t; + using OuterCat = typename iterator_traits::iterator_category; + using InnerCat = typename iterator_traits::iterator_category; + using PatternCat = typename iterator_traits::iterator_category; + + if constexpr (!is_reference_v, iter_reference_t>>) { + STATIC_ASSERT(same_as::iterator_category, input_iterator_tag>); + } else if constexpr (derived_from + && derived_from + && derived_from + && common_range> && common_range) { + STATIC_ASSERT(same_as::iterator_category, bidirectional_iterator_tag>); + } else if constexpr (derived_from + && derived_from + && derived_from) { + STATIC_ASSERT(same_as::iterator_category, forward_iterator_tag>); + } else { + STATIC_ASSERT(same_as::iterator_category, input_iterator_tag>); + } + } + // Validate range adaptor object and range adaptor closure constexpr bool is_view = ranges::view>; const auto closure = views::join_with(delimiter); @@ -314,6 +363,19 @@ struct instantiator { Outer empty{span{}}; test_one(empty, "*#"sv, views::empty); } +#ifdef __clang__ // TRANSITION, LLVM-60293 + if constexpr (ranges::forward_range || ranges::common_range) +#endif // __clang__ + { // Range-of-rvalue 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::as_rvalue, "*#"sv | views::as_rvalue, expected_range); + + Outer empty{span{}}; + test_one(empty | views::as_rvalue, "*#"sv | views::as_rvalue, views::empty); + } } };