diff --git a/libcxx/docs/Status/Cxx20Issues.csv b/libcxx/docs/Status/Cxx20Issues.csv --- a/libcxx/docs/Status/Cxx20Issues.csv +++ b/libcxx/docs/Status/Cxx20Issues.csv @@ -234,7 +234,7 @@ "`3304 `__","Allocate functions of ``std::polymorphic_allocator``\ should require ``[[nodiscard]]``\ ","Prague","","" "`3307 `__","``std::allocator().allocate(n)``\ ","Prague","","" "`3310 `__","Replace ``SIZE_MAX``\ with ``numeric_limits::max()``\ ","Prague","","" -"`3313 `__","``join_view::iterator::operator--``\ is incorrectly constrained","Prague","","","|ranges|" +"`3313 `__","``join_view::iterator::operator--``\ is incorrectly constrained","Prague","|Complete|","14.0","|ranges|" "`3314 `__","Is stream insertion behavior locale dependent when ``Period::type``\ is ``micro``\ ?","Prague","","","|chrono|" "`3315 `__","Correct Allocator Default Behavior","Prague","","" "`3316 `__","Correctly define epoch for ``utc_clock``\ / ``utc_timepoint``\ ","Prague","","","|chrono|" diff --git a/libcxx/docs/Status/Cxx2bIssues.csv b/libcxx/docs/Status/Cxx2bIssues.csv --- a/libcxx/docs/Status/Cxx2bIssues.csv +++ b/libcxx/docs/Status/Cxx2bIssues.csv @@ -44,7 +44,7 @@ "`3467 `__","``bool`` can't be an integer-like type","November 2020","|Complete|","14.0" "`3472 `__","``counted_iterator`` is missing preconditions","November 2020","|Complete|","14.0","|ranges|" "`3473 `__","Normative encouragement in non-normative note","November 2020","|Nothing To Do|","","|format|" -"`3474 `__","Nesting ``join_views`` is broken because of CTAD","November 2020","","","|ranges|" +"`3474 `__","Nesting ``join_views`` is broken because of CTAD","November 2020","|Complete|","15.0","|ranges|" "`3476 `__","``thread`` and ``jthread`` constructors require that the parameters be move-constructible but never move construct the parameters","November 2020","","" "`3477 `__","Simplify constraints for ``semiregular-box``","November 2020","","","|ranges|" "`3482 `__","``drop_view``'s const begin should additionally require ``sized_range``","November 2020","|Complete|","14.0","|ranges|" @@ -56,7 +56,7 @@ "`3492 `__","Minimal improvements to ``elements_view::iterator``","February 2021","","","|ranges|" "`3494 `__","Allow ranges to be conditionally borrowed","February 2021","Superseded by `P2017R1 `__","","|ranges|" "`3495 `__","``constexpr launder`` makes pointers to inactive members of unions usable","February 2021","|Nothing To Do|","" -"`3500 `__","``join_view::iterator::operator->()`` is bogus","February 2021","","","|ranges|" +"`3500 `__","``join_view::iterator::operator->()`` is bogus","February 2021","|Complete|","14.0","|ranges|" "`3502 `__","``elements_view`` should not be allowed to return dangling reference","February 2021","","","|ranges|" "`3505 `__","``split_view::outer-iterator::operator++`` misspecified","February 2021","","","|ranges|" "","","","","" @@ -68,7 +68,7 @@ `3462 `__,"§[formatter.requirements]: Formatter requirements forbid use of ``fc.arg()``","June 2021","","","|format|" `3481 `__,"``viewable_range`` mishandles lvalue move-only views","June 2021","Superseded by `P2415R2 `__","","|ranges|" `3506 `__,"Missing allocator-extended constructors for ``priority_queue``","June 2021","|Complete|","14.0" -`3517 `__,"``join_view::iterator``'s ``iter_swap`` is underconstrained","June 2021","","","|ranges|" +`3517 `__,"``join_view::iterator``'s ``iter_swap`` is underconstrained","June 2021","|Complete|","14.0","|ranges|" `3518 `__,"Exception requirements on char trait operations unclear","June 2021","|Nothing To Do|","" `3519 `__,"Incomplete synopses for ```` classes","June 2021","","" `3520 `__,"``iter_move`` and ``iter_swap`` are inconsistent for ``transform_view::iterator``","June 2021","|Complete|","14.0","|ranges|" @@ -112,7 +112,7 @@ `3470 `__,"``convertible-to-non-slicing`` seems to reject valid case","October 2021","|Complete|","14.0","|ranges|" `3480 `__,"``directory_iterator`` and ``recursive_directory_iterator`` are not C++20 ranges","October 2021","|Complete|","14.0","|ranges|" `3498 `__,"Inconsistent ``noexcept``-specifiers for ``basic_syncbuf``","October 2021","","" -`3535 `__,"``join_view::iterator::iterator_category`` and ``::iterator_concept`` lie","October 2021","","","|ranges|" +`3535 `__,"``join_view::iterator::iterator_category`` and ``::iterator_concept`` lie","October 2021","|Complete|","15.0","|ranges|" `3554 `__,"``chrono::parse`` needs ``const charT*`` and ``basic_string_view`` overloads","October 2021","","","|chrono|" `3557 `__,"The ``static_cast`` expression in ``convertible_to`` has the wrong operand","October 2021","|Complete|","14.0" `3559 `__,"Semantic requirements of ``sized_range`` is circular","October 2021","|Nothing To Do|","","|ranges|" diff --git a/libcxx/docs/Status/RangesIssues.csv b/libcxx/docs/Status/RangesIssues.csv --- a/libcxx/docs/Status/RangesIssues.csv +++ b/libcxx/docs/Status/RangesIssues.csv @@ -24,7 +24,7 @@ `P2106R0 `__,Range Algorithm Result Types,, `P2325R3 `__,Views should not be required to be default constructible ,, -`P2328R1 `__,join_view should join all views of ranges,, +`P2328R1 `__,join_view should join all views of ranges,|Complete|,14.0 `P2210R2 `__,Superior String Splitting,, `P2281R1 `__,Clarifying range adaptor objects,|Complete|,14.0 `P2367R0 `__,Remove misuses of list-initialization from Clause 24,, diff --git a/libcxx/include/__ranges/join_view.h b/libcxx/include/__ranges/join_view.h --- a/libcxx/include/__ranges/join_view.h +++ b/libcxx/include/__ranges/join_view.h @@ -23,10 +23,8 @@ #include <__ranges/all.h> #include <__ranges/concepts.h> #include <__ranges/non_propagating_cache.h> -#include <__ranges/ref_view.h> -#include <__ranges/subrange.h> +#include <__ranges/range_adaptor.h> #include <__ranges/view_interface.h> -#include <__utility/declval.h> #include <__utility/forward.h> #include #include @@ -52,7 +50,8 @@ using _InnerC = typename iterator_traits>>::iterator_category; using iterator_category = _If< - derived_from<_OuterC, bidirectional_iterator_tag> && derived_from<_InnerC, bidirectional_iterator_tag>, + derived_from<_OuterC, bidirectional_iterator_tag> && derived_from<_InnerC, bidirectional_iterator_tag> && + common_range>, bidirectional_iterator_tag, _If< derived_from<_OuterC, forward_iterator_tag> && derived_from<_InnerC, forward_iterator_tag>, @@ -211,7 +210,8 @@ public: using iterator_concept = _If< - __ref_is_glvalue && bidirectional_range<_Base> && bidirectional_range>, + __ref_is_glvalue && bidirectional_range<_Base> && bidirectional_range> && + common_range>, bidirectional_iterator_tag, _If< __ref_is_glvalue && forward_range<_Base> && forward_range>, @@ -345,7 +345,22 @@ template explicit join_view(_Range&&) -> join_view>; - + +namespace views { +namespace __join_view { +struct __fn : __range_adaptor_closure<__fn> { + template + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI + constexpr auto operator()(_Range&& __range) const + noexcept(noexcept(join_view>(std::forward<_Range>(__range)))) + -> decltype( join_view>(std::forward<_Range>(__range))) + { return join_view>(std::forward<_Range>(__range)); } +}; +} // namespace __join_view +inline namespace __cpo { + inline constexpr auto join = __join_view::__fn{}; +} // namespace __cpo +} // namespace views } // namespace ranges #endif // _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES) diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/adaptor.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/adaptor.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/adaptor.pass.cpp @@ -0,0 +1,138 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: libcpp-has-no-incomplete-ranges + +// std::views::join + +#include + +#include +#include + +#include "types.h" + +struct MoveOnlyOuter : SimpleForwardCommonOuter { + using SimpleForwardCommonOuter::SimpleForwardCommonOuter; + + constexpr MoveOnlyOuter(MoveOnlyOuter&&) = default; + constexpr MoveOnlyOuter(const MoveOnlyOuter&) = delete; + + constexpr MoveOnlyOuter& operator=(MoveOnlyOuter&&) = default; + constexpr MoveOnlyOuter& operator=(const MoveOnlyOuter&) = delete; +}; + +struct Foo { + int i; + constexpr Foo(int ii) : i(ii) {} +}; + +template +concept CanBePiped = requires(View&& view, T&& t) { + { std::forward(view) | std::forward(t) }; +}; + +constexpr bool test() { + int buffer1[3] = {1, 2, 3}; + int buffer2[2] = {4, 5}; + int buffer3[4] = {6, 7, 8, 9}; + Foo nested[2][3][3] = {{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}, {{10, 11, 12}, {13, 14, 15}, {16, 17, 18}}}; + + { + // Test `views::join(v)` + ForwardCommonInner inners[3] = {buffer1, buffer2, buffer3}; + using Result = std::ranges::join_view>; + std::same_as decltype(auto) v = std::views::join(inners); + assert(std::ranges::next(v.begin(), 9) == v.end()); + assert(&(*v.begin()) == buffer1); + } + + { + // Test `views::join(move-only-view)` + ForwardCommonInner inners[3] = {buffer1, buffer2, buffer3}; + using Result = std::ranges::join_view; + std::same_as decltype(auto) v = std::views::join(MoveOnlyOuter{inners}); + assert(std::ranges::next(v.begin(), 9) == v.end()); + assert(&(*v.begin()) == buffer1); + + static_assert(std::invocable); + static_assert(!std::invocable); + } + + { + // LWG3474 Nesting `join_views` is broken because of CTAD + // views::join(join_view) should join the view instead of calling copy constructor + auto jv = std::views::join(nested); + ASSERT_SAME_TYPE(std::ranges::range_reference_t, Foo(&)[3]); + + auto jv2 = std::views::join(jv); + ASSERT_SAME_TYPE(std::ranges::range_reference_t, Foo&); + + assert(&(*jv2.begin()) == &nested[0][0][0]); + } + + { + // Test `v | views::join` + ForwardCommonInner inners[3] = {buffer1, buffer2, buffer3}; + + using Result = std::ranges::join_view>; + std::same_as decltype(auto) v = inners | std::views::join; + assert(std::ranges::next(v.begin(), 9) == v.end()); + assert(&(*v.begin()) == buffer1); + static_assert(CanBePiped); + } + + { + // Test `move-only-view | views::join` + ForwardCommonInner inners[3] = {buffer1, buffer2, buffer3}; + using Result = std::ranges::join_view; + std::same_as decltype(auto) v = MoveOnlyOuter{inners} | std::views::join; + assert(std::ranges::next(v.begin(), 9) == v.end()); + assert(&(*v.begin()) == buffer1); + + static_assert(CanBePiped); + static_assert(!CanBePiped); + } + + { + // LWG3474 Nesting `join_views` is broken because of CTAD + // join_view | views::join should join the view instead of calling copy constructor + auto jv = nested | std::views::join | std::views::join; + ASSERT_SAME_TYPE(std::ranges::range_reference_t, Foo&); + + assert(&(*jv.begin()) == &nested[0][0][0]); + static_assert(CanBePiped); + } + + { + // Test `adaptor | views::join` + auto join_twice = std::views::join | std::views::join; + auto jv = nested | join_twice; + ASSERT_SAME_TYPE(std::ranges::range_reference_t, Foo&); + + assert(&(*jv.begin()) == &nested[0][0][0]); + static_assert(CanBePiped); + } + + { + static_assert(!CanBePiped); + static_assert(!CanBePiped); + static_assert(!CanBePiped); + static_assert(CanBePiped); + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/begin.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/begin.pass.cpp --- a/libcxx/test/std/ranges/range.adaptors/range.join.view/begin.pass.cpp +++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/begin.pass.cpp @@ -10,12 +10,13 @@ // UNSUPPORTED: libcpp-has-no-incomplete-ranges // constexpr auto begin(); -// constexpr auto begin() const; +// constexpr auto begin() const +// requires input_­range && +// is_reference_v>; #include #include -#include "test_macros.h" #include "types.h" struct NonSimpleParentView : std::ranges::view_base { @@ -29,6 +30,16 @@ const ChildView* end() const; }; +struct ConstNotRange : std::ranges::view_base { + const ChildView* begin(); + const ChildView* end(); +}; +static_assert(std::ranges::range); +static_assert(!std::ranges::range); + +template +concept HasConstBegin = requires(const T& t) { t.begin(); }; + constexpr bool test() { int buffer[4][4] = {{1111, 2222, 3333, 4444}, {555, 666, 777, 888}, {99, 1010, 1111, 1212}, {13, 14, 15, 16}}; @@ -39,49 +50,61 @@ } { - CopyableChild children[4] = {CopyableChild(buffer[0], 4), CopyableChild(buffer[1], 0), CopyableChild(buffer[2], 1), CopyableChild(buffer[3], 0)}; + CopyableChild children[4] = {CopyableChild(buffer[0], 4), CopyableChild(buffer[1], 0), CopyableChild(buffer[2], 1), + CopyableChild(buffer[3], 0)}; auto jv = std::ranges::join_view(ParentView{children}); assert(*jv.begin() == 1111); } + // Parent is empty. { - CopyableChild children[4] = {CopyableChild(buffer[0]), CopyableChild(buffer[1]), CopyableChild(buffer[2]), CopyableChild(buffer[3])}; + CopyableChild children[4] = {CopyableChild(buffer[0]), CopyableChild(buffer[1]), CopyableChild(buffer[2]), + CopyableChild(buffer[3])}; std::ranges::join_view jv(ParentView(children, 0)); assert(jv.begin() == jv.end()); } + // Parent size is one. { CopyableChild children[1] = {CopyableChild(buffer[0])}; std::ranges::join_view jv(ParentView(children, 1)); assert(*jv.begin() == 1111); } + // Parent and child size is one. { CopyableChild children[1] = {CopyableChild(buffer[0], 1)}; std::ranges::join_view jv(ParentView(children, 1)); assert(*jv.begin() == 1111); } + // Parent size is one child is empty { CopyableChild children[1] = {CopyableChild(buffer[0], 0)}; std::ranges::join_view jv(ParentView(children, 1)); assert(jv.begin() == jv.end()); } + // Has all empty children. { - CopyableChild children[4] = {CopyableChild(buffer[0], 0), CopyableChild(buffer[1], 0), CopyableChild(buffer[2], 0), CopyableChild(buffer[3], 0)}; + CopyableChild children[4] = {CopyableChild(buffer[0], 0), CopyableChild(buffer[1], 0), CopyableChild(buffer[2], 0), + CopyableChild(buffer[3], 0)}; auto jv = std::ranges::join_view(ParentView{children}); assert(jv.begin() == jv.end()); } + // First child is empty, others are not. { - CopyableChild children[4] = {CopyableChild(buffer[0], 4), CopyableChild(buffer[1], 0), CopyableChild(buffer[2], 0), CopyableChild(buffer[3], 0)}; + CopyableChild children[4] = {CopyableChild(buffer[0], 4), CopyableChild(buffer[1], 0), CopyableChild(buffer[2], 0), + CopyableChild(buffer[3], 0)}; auto jv = std::ranges::join_view(ParentView{children}); assert(*jv.begin() == 1111); } + // Last child is empty, others are not. { - CopyableChild children[4] = {CopyableChild(buffer[0], 4), CopyableChild(buffer[1], 4), CopyableChild(buffer[2], 4), CopyableChild(buffer[3], 0)}; + CopyableChild children[4] = {CopyableChild(buffer[0], 4), CopyableChild(buffer[1], 4), CopyableChild(buffer[2], 4), + CopyableChild(buffer[3], 0)}; auto jv = std::ranges::join_view(ParentView{children}); assert(*jv.begin() == 1111); } @@ -94,20 +117,33 @@ { const std::ranges::join_view jv(buffer); assert(*jv.begin() == 1111); + static_assert(HasConstBegin); + } + + // !input_­range + { + std::ranges::join_view jv{ConstNotRange{}}; + static_assert(!HasConstBegin); + } + + // !is_reference_v> + { + auto innerRValueRange = std::views::iota(0, 5) | std::views::transform([](int) { return ChildView{}; }); + static_assert(!std::is_reference_v>); + std::ranges::join_view jv{innerRValueRange}; + static_assert(!HasConstBegin); } // !simple-view { std::ranges::join_view jv; - static_assert(!std::same_as); + static_assert(!std::same_as); } // simple-view && is_reference_v>; { std::ranges::join_view jv; - static_assert(std::same_as); + static_assert(std::same_as); } return true; diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/ctad.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/ctad.compile.pass.cpp --- a/libcxx/test/std/ranges/range.adaptors/range.join.view/ctad.compile.pass.cpp +++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/ctad.compile.pass.cpp @@ -37,6 +37,11 @@ template<> inline constexpr bool std::ranges::enable_borrowed_range = true; +struct NestedChildren : std::ranges::view_base { + View* begin() const; + View* end() const; +}; + void testCTAD() { View v; Range r; @@ -66,4 +71,13 @@ decltype(std::ranges::join_view(std::move(br))), std::ranges::join_view> >); + + NestedChildren n; + std::ranges::join_view jv(n); + + // CTAD generated from the copy constructor instead of joining the join_view + static_assert(std::same_as< decltype(std::ranges::join_view(jv)), decltype(jv) >); + + // CTAD generated from the move constructor instead of joining the join_view + static_assert(std::same_as< decltype(std::ranges::join_view(std::move(jv))), decltype(jv) >); } diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/ctor.default.pass.cpp --- a/libcxx/test/std/ranges/range.adaptors/range.join.view/ctor.default.pass.cpp +++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/ctor.default.pass.cpp @@ -14,13 +14,26 @@ #include #include -#include "test_macros.h" #include "types.h" +struct DefaultView : std::ranges::view_base { + int i; // deliberately uninitialised + + ChildView* begin() const; + ChildView* end() const; +}; constexpr bool test() { - std::ranges::join_view> jv; - assert(std::move(jv).base().ptr_ == globalChildren); + { + std::ranges::join_view> jv; + assert(std::move(jv).base().ptr_ == globalChildren); + } + + // Default constructor should value initialise underlying view + { + std::ranges::join_view jv; + assert(jv.base().i == 0); + } static_assert( std::default_initializable>>); static_assert(!std::default_initializable>); diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/end.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/end.pass.cpp --- a/libcxx/test/std/ranges/range.adaptors/range.join.view/end.pass.cpp +++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/end.pass.cpp @@ -19,44 +19,217 @@ #include "test_macros.h" #include "types.h" +template +concept HasConstEnd = requires (const T& t){ + t.end(); +}; + + +// | ID | outer | outer | outer | inner | inner | inner | end() | end() | +// | | simple | forward | common | l_ref | forward | common | | const | +// |----|--------|---------|--------|-------|---------|--------|---------------|--------------| +// | 1 | Y | Y | Y | Y | Y | Y |iterator |iterator| +// | 2 | Y | Y | Y | Y | Y | N |sentinel |sentinel| +// | 3 | Y | Y | Y | Y | N | Y |sentinel |sentinel| +// | 4 | Y | Y | Y | N | Y | Y |sentinel | - | +// | 5 | Y | Y | N | Y | Y | Y |sentinel |sentinel| +// | 6 | Y | N | Y | Y | Y | Y |sentinel |sentinel| +// | 7 | N | Y | Y | Y | Y | Y |iterator|iterator| +// | 8 | N | Y | Y | Y | Y | N |sentinel|sentinel| +// | 9 | N | Y | Y | Y | N | Y |sentinel|sentinel| +// | 10 | N | Y | Y | N | Y | Y |sentinel| - | +// | 11 | N | Y | N | Y | Y | Y |sentinel|sentinel| +// | 12 | N | N | Y | Y | Y | Y |sentinel|sentinel| +// +// + +struct ConstNotRange : std::ranges::view_base { + const ChildView* begin(); + const ChildView* end(); +}; constexpr bool test() { int buffer[4][4] = {{1111, 2222, 3333, 4444}, {555, 666, 777, 888}, {99, 1010, 1111, 1212}, {13, 14, 15, 16}}; - // Non const common, forward range. { - std::ranges::join_view jv(buffer); + // test ID 1 + ForwardCommonInner inners[4] = {buffer[0], buffer[1], buffer[2], buffer[3]}; + SimpleForwardCommonOuter outer{inners}; + + std::ranges::join_view jv(outer); assert(jv.end() == std::ranges::next(jv.begin(), 16)); + assert(std::as_const(jv).end() == std::ranges::next(std::as_const(jv).begin(), 16)); - static_assert(std::same_as); + static_assert(HasConstEnd); + static_assert(std::same_as); + static_assert(std::ranges::common_range); + static_assert(std::ranges::common_range); } - // Non const not common, input range. { - ChildView children[4] = {ChildView(buffer[0]), ChildView(buffer[1]), ChildView(buffer[2]), ChildView(buffer[3])}; - auto jv = std::ranges::join_view(ParentView(children)); + // test ID 2 + ForwardNonCommonInner inners[3] = {buffer[0], buffer[1], buffer[2]}; + SimpleForwardCommonOuter outer{inners}; + + std::ranges::join_view jv(outer); + assert(jv.end() == std::ranges::next(jv.begin(), 12)); + assert(std::as_const(jv).end() == std::ranges::next(std::as_const(jv).begin(), 12)); + + static_assert(HasConstEnd); + static_assert(std::same_as); + static_assert(!std::ranges::common_range); + static_assert(!std::ranges::common_range); + } + + { + // test ID 3 + InputCommonInner inners[3] = {buffer[0], buffer[1], buffer[2]}; + SimpleForwardCommonOuter outer{inners}; + + std::ranges::join_view jv(outer); + assert(jv.end() == std::ranges::next(jv.begin(), 12)); + assert(std::as_const(jv).end() == std::ranges::next(std::as_const(jv).begin(), 12)); + + static_assert(HasConstEnd); + static_assert(std::same_as); + static_assert(!std::ranges::common_range); + static_assert(!std::ranges::common_range); + } + + { + // test ID 4 + ForwardCommonInner inners[2] = {buffer[0], buffer[1]}; + InnerRValue> outer{inners}; + + std::ranges::join_view jv(outer); + assert(jv.end() == std::ranges::next(jv.begin(), 8)); + + static_assert(!HasConstEnd); + static_assert(!std::ranges::common_range); + static_assert(!std::ranges::common_range); + } + + { + // test ID 5 + ForwardCommonInner inners[4] = {buffer[0], buffer[1], buffer[2], buffer[3]}; + SimpleForwardNonCommonOuter outer{inners}; + + std::ranges::join_view jv(outer); assert(jv.end() == std::ranges::next(jv.begin(), 16)); + assert(std::as_const(jv).end() == std::ranges::next(std::as_const(jv).begin(), 16)); - static_assert(!std::same_as); + static_assert(HasConstEnd); + static_assert(std::same_as); + static_assert(!std::ranges::common_range); + static_assert(!std::ranges::common_range); } - // Const common, forward range. { - const std::ranges::join_view jv(buffer); + // test ID 6 + ForwardCommonInner inners[4] = {buffer[0], buffer[1], buffer[2], buffer[3]}; + SimpleInputCommonOuter outer{inners}; + + std::ranges::join_view jv(outer); assert(jv.end() == std::ranges::next(jv.begin(), 16)); + assert(std::as_const(jv).end() == std::ranges::next(std::as_const(jv).begin(), 16)); - static_assert(std::same_as); + static_assert(HasConstEnd); + static_assert(std::same_as); + static_assert(!std::ranges::common_range); + static_assert(!std::ranges::common_range); } - // Const not common, input range. { - static_assert(std::is_reference_v>); + // test ID 7 + ForwardCommonInner inners[1] = {buffer[0]}; + NonSimpleForwardCommonOuter outer{inners}; - CopyableChild children[4] = {CopyableChild(buffer[0]), CopyableChild(buffer[1]), CopyableChild(buffer[2]), CopyableChild(buffer[3])}; - const auto jv = std::ranges::join_view(ParentView(children)); + std::ranges::join_view jv(outer); + assert(jv.end() == std::ranges::next(jv.begin(), 4)); + assert(std::as_const(jv).end() == std::ranges::next(std::as_const(jv).begin(), 4)); + + static_assert(HasConstEnd); + static_assert(!std::same_as); + static_assert(std::ranges::common_range); + static_assert(std::ranges::common_range); + } + + { + // test ID 8 + ForwardNonCommonInner inners[3] = {buffer[0], buffer[1], buffer[2]}; + NonSimpleForwardCommonOuter outer{inners}; + + std::ranges::join_view jv(outer); + assert(jv.end() == std::ranges::next(jv.begin(), 12)); + assert(std::as_const(jv).end() == std::ranges::next(std::as_const(jv).begin(), 12)); + + static_assert(HasConstEnd); + static_assert(!std::same_as); + static_assert(!std::ranges::common_range); + static_assert(!std::ranges::common_range); + } + + { + // test ID 9 + InputCommonInner inners[3] = {buffer[0], buffer[1], buffer[2]}; + NonSimpleForwardCommonOuter outer{inners}; + + std::ranges::join_view jv(outer); + assert(jv.end() == std::ranges::next(jv.begin(), 12)); + assert(std::as_const(jv).end() == std::ranges::next(std::as_const(jv).begin(), 12)); + + static_assert(HasConstEnd); + static_assert(!std::same_as); + static_assert(!std::ranges::common_range); + static_assert(!std::ranges::common_range); + } + + { + // test ID 10 + ForwardCommonInner inners[2] = {buffer[0], buffer[1]}; + InnerRValue> outer{inners}; + + std::ranges::join_view jv(outer); + assert(jv.end() == std::ranges::next(jv.begin(), 8)); + + static_assert(!HasConstEnd); + static_assert(!std::ranges::common_range); + static_assert(!std::ranges::common_range); + } + + { + // test ID 11 + ForwardCommonInner inners[4] = {buffer[0], buffer[1], buffer[2], buffer[3]}; + NonSimpleForwardNonCommonOuter outer{inners}; + + std::ranges::join_view jv(outer); assert(jv.end() == std::ranges::next(jv.begin(), 16)); + assert(std::as_const(jv).end() == std::ranges::next(std::as_const(jv).begin(), 16)); - static_assert(!std::same_as); + static_assert(HasConstEnd); + static_assert(!std::same_as); + static_assert(!std::ranges::common_range); + static_assert(!std::ranges::common_range); + } + + { + // test ID 12 + ForwardCommonInner inners[4] = {buffer[0], buffer[1], buffer[2], buffer[3]}; + NonSimpleInputCommonOuter outer{inners}; + + std::ranges::join_view jv(outer); + assert(jv.end() == std::ranges::next(jv.begin(), 16)); + assert(std::as_const(jv).end() == std::ranges::next(std::as_const(jv).begin(), 16)); + + static_assert(HasConstEnd); + static_assert(!std::same_as); + static_assert(!std::ranges::common_range); + static_assert(!std::ranges::common_range); + } + + { + std::ranges::join_view jv(ConstNotRange{}); + static_assert(!HasConstEnd); } // Has some empty children. @@ -65,42 +238,49 @@ auto jv = std::ranges::join_view(ParentView(children)); assert(jv.end() == std::ranges::next(jv.begin(), 5)); } + // Parent is empty. { CopyableChild children[4] = {CopyableChild(buffer[0]), CopyableChild(buffer[1]), CopyableChild(buffer[2]), CopyableChild(buffer[3])}; std::ranges::join_view jv(ParentView(children, 0)); assert(jv.end() == jv.begin()); } + // Parent size is one. { CopyableChild children[1] = {CopyableChild(buffer[0])}; std::ranges::join_view jv(ParentView(children, 1)); assert(jv.end() == std::ranges::next(jv.begin(), 4)); } + // Parent and child size is one. { CopyableChild children[1] = {CopyableChild(buffer[0], 1)}; std::ranges::join_view jv(ParentView(children, 1)); assert(jv.end() == std::ranges::next(jv.begin())); } + // Parent size is one child is empty { CopyableChild children[1] = {CopyableChild(buffer[0], 0)}; std::ranges::join_view jv(ParentView(children, 1)); assert(jv.end() == jv.begin()); } + // Has all empty children. { CopyableChild children[4] = {CopyableChild(buffer[0], 0), CopyableChild(buffer[1], 0), CopyableChild(buffer[2], 0), CopyableChild(buffer[3], 0)}; auto jv = std::ranges::join_view(ParentView(children)); assert(jv.end() == jv.begin()); } + // First child is empty, others are not. { CopyableChild children[4] = {CopyableChild(buffer[0], 4), CopyableChild(buffer[1], 0), CopyableChild(buffer[2], 0), CopyableChild(buffer[3], 0)}; auto jv = std::ranges::join_view(ParentView(children)); assert(jv.end() == std::ranges::next(jv.begin(), 4)); } + // Last child is empty, others are not. { CopyableChild children[4] = {CopyableChild(buffer[0], 4), CopyableChild(buffer[1], 4), CopyableChild(buffer[2], 4), CopyableChild(buffer[3], 0)}; diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/general.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/general.pass.cpp --- a/libcxx/test/std/ranges/range.adaptors/range.join.view/general.pass.cpp +++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/general.pass.cpp @@ -17,12 +17,10 @@ #include #include -#include "test_macros.h" #include "types.h" - -template -bool isEqual(R &r, I i) { +template +bool isEqual(R& r, I i) { for (auto e : r) if (e != *i++) return false; @@ -32,7 +30,7 @@ int main(int, char**) { { int buffer[4][4] = {{1111, 2222, 3333, 4444}, {555, 666, 777, 888}, {99, 1010, 1111, 1212}, {13, 14, 15, 16}}; - int *flattened = reinterpret_cast(buffer); + int* flattened = reinterpret_cast(buffer); ChildView children[4] = {ChildView(buffer[0]), ChildView(buffer[1]), ChildView(buffer[2]), ChildView(buffer[3])}; auto jv = std::ranges::join_view(ParentView(children)); @@ -46,5 +44,22 @@ assert(isEqual(jv, check.begin())); } + { + // P2328R1 join_view should join all views of ranges + // join a range of prvalue containers + std::vector x{1, 2, 3, 4}; + auto y = x | std::views::transform([](auto i) { + std::vector v(i); + for (int& ii : v) { + ii = i; + } + return v; + }); + + std::ranges::join_view jv(y); + std::vector check{1, 2, 2, 3, 3, 3, 4, 4, 4, 4}; + assert(isEqual(jv, check.begin())); + } + return 0; } diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/arrow.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/arrow.pass.cpp --- a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/arrow.pass.cpp +++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/arrow.pass.cpp @@ -15,29 +15,147 @@ #include #include -#include "test_macros.h" #include "../types.h" +template +concept HasArrow = std::input_iterator && (std::is_pointer_v || requires(T i) { i.operator->(); }); + +template +struct move_only_input_iter_with_arrow { + Base it_; + + using value_type = std::iter_value_t; + using difference_type = std::intptr_t; + using iterator_concept = std::input_iterator_tag; + + constexpr move_only_input_iter_with_arrow(Base it) : it_(std::move(it)) {} + constexpr move_only_input_iter_with_arrow(move_only_input_iter_with_arrow&&) = default; + constexpr move_only_input_iter_with_arrow(const move_only_input_iter_with_arrow&) = delete; + constexpr move_only_input_iter_with_arrow& operator=(move_only_input_iter_with_arrow&&) = default; + constexpr move_only_input_iter_with_arrow& operator=(const move_only_input_iter_with_arrow&) = delete; + + constexpr move_only_input_iter_with_arrow& operator++() { + ++it_; + return *this; + } + constexpr void operator++(int) { ++it_; } + + constexpr std::iter_reference_t operator*() const { return *it_; } + constexpr auto operator->() const + requires(HasArrow && std::copyable) { + return it_; + } +}; +static_assert(!std::copyable>); +static_assert(std::input_iterator>); + +template +struct move_iter_sentinel { + Base it_; + explicit move_iter_sentinel() = default; + constexpr move_iter_sentinel(Base it) : it_(std::move(it)) {} + constexpr bool operator==(const move_only_input_iter_with_arrow& other) const { return it_ == other.it_; } +}; +static_assert(std::sentinel_for, move_only_input_iter_with_arrow>); + +struct MoveOnlyIterInner : BufferView, move_iter_sentinel> { + using BufferView::BufferView; + + using iterator = move_only_input_iter_with_arrow; + using sentinel = move_iter_sentinel; + + iterator begin() const { return data_; } + sentinel end() const { return sentinel{data_ + size_}; } +}; +static_assert(std::ranges::input_range); + +template +struct arrow_input_iter { + Base it_; + + using value_type = std::iter_value_t; + using difference_type = std::intptr_t; + using iterator_concept = std::input_iterator_tag; + + arrow_input_iter() = default; + constexpr arrow_input_iter(Base it) : it_(std::move(it)) {} + + constexpr arrow_input_iter& operator++() { + ++it_; + return *this; + } + constexpr void operator++(int) { ++it_; } + + constexpr std::iter_reference_t operator*() const { return *it_; } + constexpr auto operator->() const { return it_; } + + friend constexpr bool operator==(const arrow_input_iter& x, const arrow_input_iter& y) = default; +}; + +using ArrowInner = BufferView>; +static_assert(std::ranges::input_range); +static_assert(HasArrow>); + constexpr bool test() { - Box buffer[4][4] = {{{1111}, {2222}, {3333}, {4444}}, {{555}, {666}, {777}, {888}}, {{99}, {1010}, {1111}, {1212}}, {{13}, {14}, {15}, {16}}}; + Box buffer[4][4] = {{{1111}, {2222}, {3333}, {4444}}, + {{555}, {666}, {777}, {888}}, + {{99}, {1010}, {1111}, {1212}}, + {{13}, {14}, {15}, {16}}}; { // Copyable input iterator with arrow. - ValueView children[4] = {ValueView(buffer[0]), ValueView(buffer[1]), ValueView(buffer[2]), ValueView(buffer[3])}; + ValueView children[4] = {ValueView(buffer[0]), ValueView(buffer[1]), ValueView(buffer[2]), + ValueView(buffer[3])}; std::ranges::join_view jv(ValueView>{children}); assert(jv.begin()->x == 1111); + static_assert(HasArrow); } { std::ranges::join_view jv(buffer); assert(jv.begin()->x == 1111); + static_assert(HasArrow); } { const std::ranges::join_view jv(buffer); assert(jv.begin()->x == 1111); + static_assert(HasArrow); + } + + { + // LWG3500 `join_view::iterator::operator->()` is bogus + // `operator->` should not be defined if inner iterator is not copyable + // has-arrow && !copyable + static_assert(HasArrow>); + MoveOnlyIterInner inners[2] = {buffer[0], buffer[1]}; + std::ranges::join_view jv{inners}; + static_assert(HasArrow); + static_assert(!HasArrow); + } + + { + // LWG3500 `join_view::iterator::operator->()` is bogus + // `operator->` should not be defined if inner iterator does not have `operator->` + // !has-arrow && copyable + using Inner = BufferView>; + Inner inners[2] = {buffer[0], buffer[1]}; + std::ranges::join_view jv{inners}; + static_assert(!HasArrow); + static_assert(!HasArrow); } + { + // arrow returns inner iterator + ArrowInner inners[2] = {buffer[0], buffer[1]}; + std::ranges::join_view jv{inners}; + static_assert(HasArrow); + static_assert(HasArrow); + + auto jv_it = jv.begin(); + std::same_as> auto arrow_it = jv_it.operator->(); + assert(arrow_it->x == 1111); + } return true; } diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.other.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.other.pass.cpp --- a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.other.pass.cpp +++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.other.pass.cpp @@ -9,7 +9,10 @@ // UNSUPPORTED: c++03, c++11, c++14, c++17 // UNSUPPORTED: libcpp-has-no-incomplete-ranges -// constexpr iterator(iterator i); +// constexpr iterator(iterator i) +// requires Const && +// convertible_­to, OuterIter> && +// convertible_­to, InnerIter>; #include #include @@ -17,17 +20,60 @@ #include "test_macros.h" #include "../types.h" +using ConstCompatibleInner = BufferView; + +using ConstIncompatibleInner = BufferView, forward_iterator, + bidirectional_iterator, bidirectional_iterator>; + +template +using ConstCompatibleOuter = BufferView; + +template +using ConstIncompatibleOuter = BufferView, forward_iterator, + bidirectional_iterator, bidirectional_iterator>; + constexpr bool test() { int buffer[4][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}}; + { + CopyableChild children[4] = {CopyableChild(buffer[0]), CopyableChild(buffer[1]), CopyableChild(buffer[2]), + CopyableChild(buffer[3])}; + std::ranges::join_view jv(CopyableParent{children}); + auto iter1 = jv.begin(); + using iterator = decltype(iter1); + using const_iterator = decltype(std::as_const(jv).begin()); + static_assert(!std::is_same_v); + const_iterator iter2 = iter1; + assert(iter1 == iter2); + + // We cannot create a non-const iterator from a const iterator. + static_assert(!std::constructible_from); + } + + // !convertible_to>; + { + ConstIncompatibleInner inners[2] = {buffer[0], buffer[1]}; + ConstCompatibleOuter outer{inners}; + std::ranges::join_view jv(outer); + using iterator = decltype(jv.begin()); + using const_iterator = decltype(std::as_const(jv).begin()); + static_assert(!std::is_same_v); + + static_assert(!std::constructible_from); + static_assert(!std::constructible_from); + } - CopyableChild children[4] = {CopyableChild(buffer[0]), CopyableChild(buffer[1]), CopyableChild(buffer[2]), CopyableChild(buffer[3])}; - std::ranges::join_view jv(CopyableParent{children}); - auto iter1 = jv.begin(); - std::ranges::iterator_t iter2 = iter1; - assert(iter1 == iter2); + // !convertible_to>; + { + ConstCompatibleInner inners[2] = {buffer[0], buffer[1]}; + ConstIncompatibleOuter outer{inners}; + std::ranges::join_view jv(outer); + using iterator = decltype(jv.begin()); + using const_iterator = decltype(std::as_const(jv).begin()); + static_assert(!std::is_same_v); - // We cannot create a non-const iterator from a const iterator. - static_assert(!std::constructible_from); + static_assert(!std::constructible_from); + static_assert(!std::constructible_from); + } return true; } diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.parent.outer.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.parent.outer.pass.cpp --- a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.parent.outer.pass.cpp +++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.parent.outer.pass.cpp @@ -14,17 +14,37 @@ #include #include -#include "test_macros.h" #include "../types.h" +using NonDefaultCtrIter = cpp20_input_iterator; +static_assert(!std::default_initializable); + +using NonDefaultCtrIterView = BufferView>; +static_assert(std::ranges::input_range); + constexpr bool test() { int buffer[4][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}}; - - CopyableChild children[4] = {CopyableChild(buffer[0]), CopyableChild(buffer[1]), CopyableChild(buffer[2]), CopyableChild(buffer[3])}; - CopyableParent parent{children}; - std::ranges::join_view jv(parent); - std::ranges::iterator_t iter(jv, std::ranges::begin(parent)); - assert(*iter == 1); + { + CopyableChild children[4] = {CopyableChild(buffer[0]), CopyableChild(buffer[1]), CopyableChild(buffer[2]), + CopyableChild(buffer[3])}; + CopyableParent parent{children}; + std::ranges::join_view jv(parent); + std::ranges::iterator_t iter(jv, std::ranges::begin(parent)); + assert(*iter == 1); + } + + { + // LWG3569 Inner iterator not default_initializable + // With the current spec, the constructor under test invokes Inner iterator's default constructor + // even if it is not default constructible + // This test is checking that this constructor can be invoked with an inner range with non default + // constructible iterator + NonDefaultCtrIterView inners[] = {buffer[0], buffer[1]}; + auto outer = std::views::all(inners); + std::ranges::join_view jv(outer); + std::ranges::iterator_t iter(jv, std::ranges::begin(outer)); + assert(*iter == 1); + } return true; } diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/decrement.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/decrement.pass.cpp --- a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/decrement.pass.cpp +++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/decrement.pass.cpp @@ -10,14 +10,32 @@ // UNSUPPORTED: libcpp-has-no-incomplete-ranges // constexpr iterator& operator--(); +// requires ref-is-glvalue && bidirectional_­range && +// bidirectional_­range> && +// common_­range>; // constexpr iterator operator--(int); +// requires ref-is-glvalue && bidirectional_­range && +// bidirectional_­range> && +// common_­range>; #include #include +#include -#include "test_macros.h" #include "../types.h" +template +concept CanPreDecrement = requires(T& t) { --t; }; + +template +concept CanPostDecrement = requires(T& t) { t--; }; + +constexpr void noDecrementTest(auto&& jv) { + auto iter = jv.begin(); + static_assert(!CanPreDecrement); + static_assert(!CanPostDecrement); +} + constexpr bool test() { int buffer[4][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}}; @@ -29,6 +47,7 @@ assert(*--iter == i); } } + { // outer == ranges::end std::ranges::join_view jv(buffer); @@ -37,6 +56,7 @@ assert(*--iter == i); } } + { // outer != ranges::end std::ranges::join_view jv(buffer); @@ -45,6 +65,7 @@ assert(*--iter == i); } } + { // outer != ranges::end std::ranges::join_view jv(buffer); @@ -53,6 +74,7 @@ assert(*--iter == i); } } + { int small[2][1] = {{1}, {2}}; std::ranges::join_view jv(small); @@ -62,6 +84,72 @@ } } + { +#if defined(__GNUG__) && !defined(__clang__) + // This seems to be a gcc bug where evaluating the following code + // at compile time results in wrong array index + if (!std::is_constant_evaluated()) { +#endif + // skip empty inner + BidiCommonInner inners[4] = {buffer[0], {nullptr, 0}, {nullptr, 0}, buffer[1]}; + std::ranges::join_view jv(inners); + auto iter = jv.end(); + for (int i = 8; i != 0; --i) { + assert(*--iter == i); + } +#if defined(__GNUG__) && !defined(__clang__) + } +#endif + } + + { + // basic type checking + std::ranges::join_view jv(buffer); + auto iter1 = std::ranges::next(jv.begin(), 4); + using iterator = decltype(iter1); + + decltype(auto) iter2 = --iter1; + static_assert(std::same_as); + assert(&iter1 == &iter2); + + std::same_as decltype(auto) iter3 = iter1--; + assert(iter3 == std::next(iter1)); + } + + { + // !ref-is-glvalue + BidiCommonInner inners[2] = {buffer[0], buffer[1]}; + InnerRValue> outer{inners}; + std::ranges::join_view jv(outer); + noDecrementTest(jv); + } + + { + // !bidirectional_­range + BidiCommonInner inners[2] = {buffer[0], buffer[1]}; + SimpleForwardCommonOuter outer{inners}; + std::ranges::join_view jv(outer); + noDecrementTest(jv); + } + + { + // !bidirectional_­range> + ForwardCommonInner inners[2] = {buffer[0], buffer[1]}; + std::ranges::join_view jv(inners); + noDecrementTest(jv); + } + + { + // LWG3313 `join_view::iterator::operator--` is incorrectly constrained + // `join_view::iterator` should not have `operator--` if + // !common_­range> + BidiNonCommonInner inners[2] = {buffer[0], buffer[1]}; + std::ranges::join_view jv(inners); + auto iter = jv.begin(); + static_assert(!CanPreDecrement); + static_assert(!CanPostDecrement); + } + return true; } diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/eq.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/eq.pass.cpp --- a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/eq.pass.cpp +++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/eq.pass.cpp @@ -10,24 +10,61 @@ // UNSUPPORTED: libcpp-has-no-incomplete-ranges // friend constexpr bool operator==(const iterator& x, const iterator& y); +// requires ref-is-glvalue && equality_comparable> && +// equality_comparable>>; #include #include -#include "test_macros.h" #include "../types.h" constexpr bool test() { int buffer[4][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}}; + { + std::ranges::join_view jv(buffer); + auto iter1 = jv.begin(); + auto iter2 = jv.begin(); + assert(iter1 == iter2); + iter1++; + assert(iter1 != iter2); + iter2++; + assert(iter1 == iter2); - std::ranges::join_view jv(buffer); - auto iter1 = jv.begin(); - auto iter2 = jv.begin(); - assert(iter1 == iter2); - iter1++; - assert(iter1 != iter2); - iter2++; - assert(iter1 == iter2); + assert(jv.begin() == std::as_const(jv).begin()); + } + + { + // !ref-is-glvalue + BidiCommonInner inners[2] = {buffer[0], buffer[1]}; + InnerRValue> outer{inners}; + std::ranges::join_view jv(outer); + auto iter = jv.begin(); + static_assert(!std::equality_comparable); + } + + { + // !equality_comparable> + using Inner = BufferView; + using Outer = BufferView, sentinel_wrapper>>; + static_assert(!std::equality_comparable>); + Inner inners[2] = {buffer[0], buffer[1]}; + std::ranges::join_view jv(Outer{inners}); + auto iter = jv.begin(); + static_assert(!std::equality_comparable); + auto const_iter = std::as_const(jv).begin(); + static_assert(!std::equality_comparable); + } + + { + // !equality_comparable>>; + using Inner = BufferView, sentinel_wrapper>>; + Inner inners[1] = {buffer[0]}; + std::ranges::join_view jv{inners}; + auto iter = jv.begin(); + static_assert(!std::equality_comparable); + auto const_iter = std::as_const(jv).begin(); + static_assert(!std::equality_comparable); + } return true; } diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/increment.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/increment.pass.cpp --- a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/increment.pass.cpp +++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/increment.pass.cpp @@ -11,7 +11,9 @@ // constexpr iterator& operator++(); // constexpr void operator++(int); -// constexpr iterator operator++(int); +// constexpr iterator operator++(int) +// requires ref-is-glvalue && forward_­range && +// forward_range>; #include #include @@ -34,6 +36,7 @@ assert(*iter++ == i); } } + { ValueView children[4] = {ValueView(buffer1[0]), ValueView(buffer1[1]), ValueView(buffer2[0]), ValueView(buffer2[1])}; std::ranges::join_view jv(ValueView>{children}); @@ -45,12 +48,14 @@ ASSERT_SAME_TYPE(decltype(iter++), void); } + { std::ranges::join_view jv(buffer1); auto iter = std::next(jv.begin(), 7); assert(*iter++ == 8); assert(iter == jv.end()); } + { int small[2][1] = {{1}, {2}}; std::ranges::join_view jv(small); @@ -59,6 +64,7 @@ assert(*iter++ == i); } } + // Has some empty children. { CopyableChild children[4] = {CopyableChild(buffer1[0], 4), CopyableChild(buffer1[1], 0), CopyableChild(buffer2[0], 1), CopyableChild(buffer2[1], 0)}; @@ -71,12 +77,14 @@ assert(*iter == 9); iter++; assert(iter == jv.end()); } + // Parent is empty. { CopyableChild children[4] = {CopyableChild(buffer1[0]), CopyableChild(buffer1[1]), CopyableChild(buffer2[0]), CopyableChild(buffer2[1])}; std::ranges::join_view jv(ParentView(children, 0)); assert(jv.begin() == jv.end()); } + // Parent size is one. { CopyableChild children[1] = {CopyableChild(buffer1[0])}; @@ -88,6 +96,7 @@ assert(*iter == 4); iter++; assert(iter == jv.end()); } + // Parent and child size is one. { CopyableChild children[1] = {CopyableChild(buffer1[0], 1)}; @@ -96,18 +105,21 @@ assert(*iter == 1); iter++; assert(iter == jv.end()); } + // Parent size is one child is empty { CopyableChild children[1] = {CopyableChild(buffer1[0], 0)}; std::ranges::join_view jv(ParentView(children, 1)); assert(jv.begin() == jv.end()); } + // Has all empty children. { CopyableChild children[4] = {CopyableChild(buffer1[0], 0), CopyableChild(buffer1[1], 0), CopyableChild(buffer2[0], 0), CopyableChild(buffer2[1], 0)}; auto jv = std::ranges::join_view(ParentView(children)); assert(jv.begin() == jv.end()); } + // First child is empty, others are not. { CopyableChild children[4] = {CopyableChild(buffer1[0], 4), CopyableChild(buffer1[1], 0), CopyableChild(buffer2[0], 0), CopyableChild(buffer2[1], 0)}; @@ -119,6 +131,7 @@ assert(*iter == 4); iter++; assert(iter == jv.end()); } + // Last child is empty, others are not. { CopyableChild children[4] = {CopyableChild(buffer1[0], 4), CopyableChild(buffer1[1], 4), CopyableChild(buffer2[0], 4), CopyableChild(buffer2[1], 0)}; @@ -129,6 +142,7 @@ iter++; } } + // operator++(); { std::ranges::join_view jv(buffer1); @@ -137,6 +151,7 @@ assert(*++iter == i); } } + { ValueView children[4] = {ValueView(buffer1[0]), ValueView(buffer1[1]), ValueView(buffer2[0]), ValueView(buffer2[1])}; std::ranges::join_view jv(ValueView>{children}); @@ -148,6 +163,46 @@ ASSERT_SAME_TYPE(decltype(++iter), decltype(iter)&); } + { + // check return value + std::ranges::join_view jv(buffer1); + auto iter = jv.begin(); + using iterator = decltype(iter); + + decltype(auto) iter2 = ++iter; + static_assert(std::is_same_v); + assert(&iter2 == &iter); + + std::same_as decltype(auto) iter3 = iter++; + assert(std::next(iter3) == iter); + } + + { + // !ref-is-glvalue + BidiCommonInner inners[2] = {buffer1[0], buffer1[1]}; + InnerRValue> outer{inners}; + std::ranges::join_view jv(outer); + auto iter = jv.begin(); + static_assert(std::is_void_v); + } + + { + // !forward_­range + BufferView inners[2] = {buffer1[0], buffer1[1]}; + using Outer = SimpleInputCommonOuter>; + std::ranges::join_view jv{Outer(inners)}; + auto iter = jv.begin(); + static_assert(std::is_void_v); + } + + { + // !forward_range> + InputCommonInner inners[1] = {buffer1[0]}; + std::ranges::join_view jv{inners}; + auto iter = jv.begin(); + static_assert(std::is_void_v); + } + return true; } diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/iter.move.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/iter.move.pass.cpp --- a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/iter.move.pass.cpp +++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/iter.move.pass.cpp @@ -14,18 +14,43 @@ #include #include -#include "test_macros.h" #include "../types.h" constexpr bool test() { int buffer[4][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}}; - std::ranges::join_view jv(buffer); - assert(std::ranges::iter_move(jv.begin()) == 1); - ASSERT_SAME_TYPE(decltype(std::ranges::iter_move(jv.begin())), int&&); - - static_assert(noexcept(std::ranges::iter_move(std::declval()))); - + { + std::ranges::join_view jv(buffer); + assert(std::ranges::iter_move(jv.begin()) == 1); + static_assert(std::is_same_v); + + static_assert(noexcept(std::ranges::iter_move(std::declval()))); + } + + { + // iter_move calls inner's iter_move and calls + // iter_move on the correct inner iterator + IterMoveSwapAwareView inners[2] = {buffer[0], buffer[1]}; + std::ranges::join_view jv(inners); + auto it = jv.begin(); + + const auto& iter_move_called_times1 = jv.base().begin()->iter_move_called; + const auto& iter_move_called_times2 = std::next(jv.base().begin())->iter_move_called; + assert(iter_move_called_times1 == 0); + assert(iter_move_called_times2 == 0); + + std::same_as> decltype(auto) x = std::ranges::iter_move(it); + assert(std::get<0>(x) == 1); + assert(iter_move_called_times1 == 1); + assert(iter_move_called_times2 == 0); + + auto it2 = std::ranges::next(it, 4); + + std::same_as> decltype(auto) y = std::ranges::iter_move(it2); + assert(std::get<0>(y) == 5); + assert(iter_move_called_times1 == 1); + assert(iter_move_called_times2 == 1); + } return true; } diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/iter.swap.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/iter.swap.pass.cpp --- a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/iter.swap.pass.cpp +++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/iter.swap.pass.cpp @@ -14,23 +14,56 @@ #include #include -#include "test_macros.h" #include "../types.h" +using NonSwappableView = BufferView>; +static_assert(std::ranges::input_range); +static_assert(!std::indirectly_swappable>); + constexpr bool test() { int buffer[4][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}}; - std::ranges::join_view jv(buffer); - auto iter1 = jv.begin(); - auto iter2 = std::next(jv.begin()); - assert(*iter1 == 1); - assert(*iter2 == 2); - std::ranges::swap(iter1, iter2); - assert(*iter1 == 2); - assert(*iter2 == 1); + { + std::ranges::join_view jv(buffer); + auto iter1 = jv.begin(); + auto iter2 = std::next(jv.begin()); + assert(buffer[0][0] == 1); + assert(buffer[0][1] == 2); + std::ranges::iter_swap(iter1, iter2); + assert(buffer[0][0] == 2); + assert(buffer[0][1] == 1); + + static_assert(noexcept(std::ranges::iter_swap(iter1, iter2))); + } + + { + // iter_move calls inner's iter_swap + IterMoveSwapAwareView inners[1] = {buffer[0]}; + std::ranges::join_view jv(inners); + auto it1 = jv.begin(); + auto it2 = std::ranges::next(it1); + + const auto& iter_swap_called_times = jv.base().begin()->iter_swap_called; + + assert(iter_swap_called_times == 0); + assert(buffer[0][0] == 2); + assert(buffer[0][1] == 1); + + std::ranges::iter_swap(it1, it2); - static_assert(noexcept(std::ranges::iter_swap(iter1, iter2))); + assert(buffer[0][0] == 1); + assert(buffer[0][1] == 2); + assert(iter_swap_called_times == 1); + } + { + // LWG3517 `join_view::iterator`'s `iter_swap` is underconstrained + // `iter_swap` should not be defined if Inner's iterator does not indirectly_swappable + NonSwappableView inners[2] = {buffer[0], buffer[1]}; + std::ranges::join_view jv(inners); + using Iter = std::ranges::iterator_t; + static_assert(!std::indirectly_swappable); + } return true; } diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/member_types.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/member_types.compile.pass.cpp --- a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/member_types.compile.pass.cpp +++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/member_types.compile.pass.cpp @@ -29,6 +29,24 @@ sentinel_wrapper> end() const; }; +template +struct diff_type_iter { + using iterator_category = std::input_iterator_tag; + using value_type = V; + using difference_type = T; + + V& operator*() const; + diff_type_iter& operator++(); + void operator++(int); + friend constexpr bool operator==(diff_type_iter, diff_type_iter) = default; +}; + +template +struct DiffTypeRange : std::ranges::view_base { + diff_type_iter begin() const; + diff_type_iter end() const; +}; + template concept HasIterCategory = requires { typename T::iterator_category; }; @@ -38,25 +56,70 @@ std::ranges::join_view jv(buffer); using Iter = std::ranges::iterator_t; - ASSERT_SAME_TYPE(Iter::iterator_concept, std::bidirectional_iterator_tag); - ASSERT_SAME_TYPE(Iter::iterator_category, std::bidirectional_iterator_tag); - ASSERT_SAME_TYPE(Iter::difference_type, std::ptrdiff_t); - ASSERT_SAME_TYPE(Iter::value_type, int); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(HasIterCategory); } + { using Iter = std::ranges::iterator_t>>>; - ASSERT_SAME_TYPE(Iter::iterator_concept, std::forward_iterator_tag); - ASSERT_SAME_TYPE(Iter::iterator_category, std::forward_iterator_tag); - ASSERT_SAME_TYPE(Iter::difference_type, std::ptrdiff_t); - ASSERT_SAME_TYPE(Iter::value_type, int); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(HasIterCategory); } + { using Iter = std::ranges::iterator_t>>>; - ASSERT_SAME_TYPE(Iter::iterator_concept, std::input_iterator_tag); + static_assert(std::is_same_v); static_assert(!HasIterCategory); - ASSERT_SAME_TYPE(Iter::difference_type, std::ptrdiff_t); - ASSERT_SAME_TYPE(Iter::value_type, int); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + + { + // LWG3535 `join_view::iterator::iterator_category` and `::iterator_concept` lie + // Bidi non common inner range should not have bidirectional_iterator_tag + using Base = BidiCommonOuter; + using Iter = std::ranges::iterator_t>; + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(HasIterCategory); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + + { + // !ref-is-glvalue + using Outer = InnerRValue>; + using Iter = std::ranges::iterator_t>; + static_assert(!HasIterCategory); + static_assert(std::is_same_v); + } + + { + // value_type == inner's value_type + using Inner = IterMoveSwapAwareView; + using InnerValue = std::ranges::range_value_t; + using InnerReference = std::ranges::range_reference_t; + static_assert(!std::is_same_v>); + + using Outer = BidiCommonOuter; + using Iter = std::ranges::iterator_t>; + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + } + + { + // difference_type + using Inner = DiffTypeRange; + using Outer = DiffTypeRange; + using Iter = std::ranges::iterator_t>; + static_assert(std::is_same_v>); } } diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.other.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.other.pass.cpp --- a/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.other.pass.cpp +++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.other.pass.cpp @@ -10,25 +10,72 @@ // UNSUPPORTED: libcpp-has-no-incomplete-ranges // constexpr sentinel(sentinel s); +// requires Const && convertible_­to, sentinel_t>; #include #include -#include "test_macros.h" #include "../types.h" +template +struct convertible_sentinel_wrapper { + explicit convertible_sentinel_wrapper() = default; + constexpr convertible_sentinel_wrapper(const T& it) : it_(it) {} + + template + requires std::convertible_to + constexpr convertible_sentinel_wrapper(const convertible_sentinel_wrapper& other) : it_(other.it_) {} + + constexpr friend bool operator==(convertible_sentinel_wrapper const& self, const T& other) { + return self.it_ == other; + } + T it_; +}; + +struct ConstConveritbleView : BufferView*> { + using BufferView*>::BufferView; + + using sentinel = convertible_sentinel_wrapper*>; + using const_sentinel = convertible_sentinel_wrapper*>; + + constexpr BufferView* begin() { return data_; } + constexpr const BufferView* begin() const { return data_; } + constexpr sentinel end() { return sentinel(data_ + size_); } + constexpr const_sentinel end() const { return const_sentinel(data_ + size_); } +}; +static_assert(!std::ranges::common_range); +static_assert(std::convertible_to, + std::ranges::sentinel_t>); +LIBCPP_STATIC_ASSERT(!std::ranges::__simple_view); + constexpr bool test() { int buffer[4][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}}; + { + BufferView inners[] = {buffer[0], buffer[1], buffer[2]}; + ConstConveritbleView outer(inners); + std::ranges::join_view jv(outer); + auto sent1 = jv.end(); + std::ranges::sentinel_t sent2 = sent1; + assert(std::as_const(jv).begin() != sent2); + assert(std::ranges::next(std::as_const(jv).begin(), 12) == sent2); - CopyableChild children[4] = {CopyableChild(buffer[0]), CopyableChild(buffer[1]), CopyableChild(buffer[2]), CopyableChild(buffer[3])}; - std::ranges::join_view jv(CopyableParent{children}); - auto sent1 = jv.end(); - std::ranges::sentinel_t sent2 = sent1; - (void) sent2; // We can't really do anything with these sentinels now :/ - - // We cannot create a non-const iterator from a const iterator. - static_assert(!std::constructible_from); + // We cannot create a non-const sentinel from a const sentinel. + static_assert(!std::constructible_from); + } + { + // cannot create a const sentinel from a non-const sentinel if the underlying + // const sentinel cannot be created from the underlying non-const sentinel + using Inner = BufferView; + using ConstInconvertibleOuter = + BufferView, sentinel_wrapper>, + bidirectional_iterator, sentinel_wrapper>>; + using JoinView = std::ranges::join_view; + using sentinel = std::ranges::sentinel_t; + using const_sentinel = std::ranges::sentinel_t; + static_assert(!std::constructible_from); + static_assert(!std::constructible_from); + } return true; } diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/eq.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/eq.pass.cpp --- a/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/eq.pass.cpp +++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/eq.pass.cpp @@ -14,11 +14,49 @@ // friend constexpr bool operator==(const iterator& x, const sentinel& y); #include +#include #include -#include "test_macros.h" #include "../types.h" +template +concept EqualityComparable = std::invocable, const Iter&, const Sent&> ; + +using Iterator = random_access_iterator*>; +using ConstIterator = random_access_iterator*>; + +template +struct ConstComparableSentinel { + + using Iter = std::conditional_t; + Iter iter_; + + explicit ConstComparableSentinel() = default; + constexpr explicit ConstComparableSentinel(const Iter& it) : iter_(it) {} + + constexpr friend bool operator==(const Iterator& i, const ConstComparableSentinel& s) { + return base(i) == base(s.iter_); + } + + constexpr friend bool operator==(const ConstIterator& i, const ConstComparableSentinel& s) { + return base(i) == base(s.iter_); + } +}; + +struct ConstComparableView : BufferView*> { + using BufferView*>::BufferView; + + constexpr auto begin() { return Iterator(data_); } + constexpr auto begin() const { return ConstIterator(data_); } + constexpr auto end() { return ConstComparableSentinel(Iterator(data_ + size_)); } + constexpr auto end() const { return ConstComparableSentinel(ConstIterator(data_ + size_)); } +}; + +static_assert(EqualityComparable, + std::ranges::sentinel_t>); +static_assert(EqualityComparable, + std::ranges::sentinel_t>); + constexpr bool test() { int buffer[4][4] = {{1111, 2222, 3333, 4444}, {555, 666, 777, 888}, {99, 1010, 1111, 1212}, {13, 14, 15, 16}}; @@ -26,18 +64,25 @@ ChildView children[4] = {ChildView(buffer[0]), ChildView(buffer[1]), ChildView(buffer[2]), ChildView(buffer[3])}; auto jv = std::ranges::join_view(ParentView(children)); assert(jv.end() == std::ranges::next(jv.begin(), 16)); + static_assert(!EqualityComparable); + static_assert(!EqualityComparable); } + { - CopyableChild children[4] = {CopyableChild(buffer[0]), CopyableChild(buffer[1]), CopyableChild(buffer[2]), CopyableChild(buffer[3])}; + CopyableChild children[4] = {CopyableChild(buffer[0]), CopyableChild(buffer[1]), CopyableChild(buffer[2]), + CopyableChild(buffer[3])}; const auto jv = std::ranges::join_view(ParentView(children)); assert(jv.end() == std::ranges::next(jv.begin(), 16)); } + + // test iterator == sentinel { - CopyableChild children[4] = {CopyableChild(buffer[0]), CopyableChild(buffer[1]), CopyableChild(buffer[2]), CopyableChild(buffer[3])}; - const std::ranges::join_view jvc(CopyableParent{children}); - std::ranges::join_view jv(CopyableParent{children}); - assert(jvc.end() == std::ranges::next(jv.begin(), 16)); - assert( jv.end() == std::ranges::next(jvc.begin(), 16)); + BufferView inners[] = {buffer[0], buffer[1]}; + ConstComparableView outer(inners); + auto jv = std::ranges::join_view(outer); + assert(jv.end() == std::ranges::next(jv.begin(), 8)); + assert(std::as_const(jv).end() == std::ranges::next(jv.begin(), 8)); + assert(jv.end() == std::ranges::next(std::as_const(jv).begin(), 8)); } return true; diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/types.h b/libcxx/test/std/ranges/range.adaptors/range.join.view/types.h --- a/libcxx/test/std/ranges/range.adaptors/range.join.view/types.h +++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/types.h @@ -10,120 +10,141 @@ #define TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_JOIN_TYPES_H #include +#include #include "test_macros.h" #include "test_iterators.h" -int globalBuffer[4][4] = {{1111, 2222, 3333, 4444}, {555, 666, 777, 888}, {99, 1010, 1111, 1212}, {13, 14, 15, 16}}; +inline int globalBuffer[4][4] = { + {1111, 2222, 3333, 4444}, + {555, 666, 777, 888}, + {99, 1010, 1111, 1212}, + {13, 14, 15, 16}, +}; struct ChildView : std::ranges::view_base { - int *ptr_; + int* ptr_; + + using iterator = cpp20_input_iterator; + using const_iterator = cpp20_input_iterator; + using sentinel = sentinel_wrapper; + using const_sentinel = sentinel_wrapper; - constexpr ChildView(int *ptr = globalBuffer[0]) : ptr_(ptr) {} + constexpr ChildView(int* ptr = globalBuffer[0]) : ptr_(ptr) {} ChildView(const ChildView&) = delete; ChildView(ChildView&&) = default; ChildView& operator=(const ChildView&) = delete; ChildView& operator=(ChildView&&) = default; - constexpr cpp20_input_iterator begin() { return cpp20_input_iterator(ptr_); } - constexpr cpp20_input_iterator begin() const { return cpp20_input_iterator(ptr_); } - constexpr int *end() { return ptr_ + 4; } - constexpr const int *end() const { return ptr_ + 4; } + constexpr iterator begin() { return iterator(ptr_); } + constexpr const_iterator begin() const { return const_iterator(ptr_); } + constexpr sentinel end() { return sentinel(iterator(ptr_ + 4)); } + constexpr const_sentinel end() const { return const_sentinel(const_iterator(ptr_ + 4)); } }; -constexpr bool operator==(const cpp20_input_iterator &lhs, int* rhs) { return base(lhs) == rhs; } -constexpr bool operator==(int* lhs, const cpp20_input_iterator &rhs) { return base(rhs) == lhs; } - -ChildView globalChildren[4] = {ChildView(globalBuffer[0]), ChildView(globalBuffer[1]), ChildView(globalBuffer[2]), ChildView(globalBuffer[3])}; +inline ChildView globalChildren[4] = { + ChildView(globalBuffer[0]), + ChildView(globalBuffer[1]), + ChildView(globalBuffer[2]), + ChildView(globalBuffer[3]), +}; -template +template struct ParentView : std::ranges::view_base { - T *ptr_; + T* ptr_; unsigned size_; - constexpr ParentView(T *ptr, unsigned size = 4) - : ptr_(ptr), size_(size) {} - constexpr ParentView(ChildView *ptr = globalChildren, unsigned size = 4) + using iterator = cpp20_input_iterator; + using const_iterator = cpp20_input_iterator; + using sentinel = sentinel_wrapper; + using const_sentinel = sentinel_wrapper; + + constexpr ParentView(T* ptr, unsigned size = 4) : ptr_(ptr), size_(size) {} + constexpr ParentView(ChildView* ptr = globalChildren, unsigned size = 4) requires std::same_as - : ptr_(ptr), size_(size) {} + : ptr_(ptr), size_(size) {} ParentView(const ParentView&) = delete; ParentView(ParentView&&) = default; ParentView& operator=(const ParentView&) = delete; ParentView& operator=(ParentView&&) = default; - constexpr cpp20_input_iterator begin() { return cpp20_input_iterator(ptr_); } - constexpr cpp20_input_iterator begin() const { return cpp20_input_iterator(ptr_); } - constexpr T *end() { return ptr_ + size_; } - constexpr const T *end() const { return ptr_ + size_; } + constexpr iterator begin() { return iterator(ptr_); } + constexpr const_iterator begin() const { return const_iterator(ptr_); } + constexpr sentinel end() { return sentinel(iterator(ptr_ + size_)); } + constexpr const_sentinel end() const { return const_sentinel(const_iterator(ptr_ + size_)); } }; -// TODO: remove these bogus operators -template -constexpr bool operator==(const cpp20_input_iterator &lhs, T *rhs) { return base(lhs) == rhs; } -template -constexpr bool operator==(T *lhs, const cpp20_input_iterator &rhs) { return base(rhs) == lhs; } struct CopyableChild : std::ranges::view_base { - int *ptr_; + int* ptr_; unsigned size_; - constexpr CopyableChild(int *ptr = globalBuffer[0], unsigned size = 4) - : ptr_(ptr), size_(size) {} - constexpr cpp17_input_iterator begin() { return cpp17_input_iterator(ptr_); } - constexpr cpp17_input_iterator begin() const { return cpp17_input_iterator(ptr_); } - constexpr int *end() { return ptr_ + size_; } - constexpr const int *end() const { return ptr_ + size_; } + using iterator = cpp17_input_iterator; + using const_iterator = cpp17_input_iterator; + using sentinel = sentinel_wrapper; + using const_sentinel = sentinel_wrapper; + + constexpr CopyableChild(int* ptr = globalBuffer[0], unsigned size = 4) : ptr_(ptr), size_(size) {} + + constexpr iterator begin() { return iterator(ptr_); } + constexpr const_iterator begin() const { return const_iterator(ptr_); } + constexpr sentinel end() { return sentinel(iterator(ptr_ + size_)); } + constexpr const_sentinel end() const { return const_sentinel(const_iterator(ptr_ + size_)); } }; -// TODO: remove these bogus operators -constexpr bool operator==(const cpp17_input_iterator &lhs, const int* rhs) { return base(lhs) == rhs; } -constexpr bool operator==(const int* lhs, const cpp17_input_iterator &rhs) { return base(rhs) == lhs; } struct CopyableParent : std::ranges::view_base { - CopyableChild *ptr_; - constexpr CopyableParent(CopyableChild *ptr) : ptr_(ptr) {} + CopyableChild* ptr_; + + using iterator = cpp17_input_iterator; + using const_iterator = cpp17_input_iterator; + using sentinel = sentinel_wrapper; + using const_sentinel = sentinel_wrapper; + + constexpr CopyableParent(CopyableChild* ptr) : ptr_(ptr) {} - constexpr cpp17_input_iterator begin() { return cpp17_input_iterator(ptr_); } - constexpr cpp17_input_iterator begin() const { return cpp17_input_iterator(ptr_); } - constexpr CopyableChild *end() { return ptr_ + 4; } - constexpr const CopyableChild *end() const { return ptr_ + 4; } + constexpr iterator begin() { return iterator(ptr_); } + constexpr const_iterator begin() const { return const_iterator(ptr_); } + constexpr sentinel end() { return sentinel(iterator(ptr_ + 4)); } + constexpr const_sentinel end() const { return const_sentinel(const_iterator(ptr_ + 4)); } }; -// TODO: remove these bogus operators -constexpr bool operator==(const cpp17_input_iterator &lhs, const CopyableChild *rhs) { return base(lhs) == rhs; } -constexpr bool operator==(const CopyableChild *lhs, const cpp17_input_iterator &rhs) { return base(rhs) == lhs; } -struct Box { int x; }; +struct Box { + int x; +}; -template +template struct InputValueIter { typedef std::input_iterator_tag iterator_category; typedef T value_type; typedef int difference_type; typedef T reference; - T *ptr_; - constexpr InputValueIter(T *ptr) : ptr_(ptr) {} + T* ptr_ = nullptr; + constexpr InputValueIter() = default; + constexpr InputValueIter(T* ptr) : ptr_(ptr) {} constexpr T operator*() const { return std::move(*ptr_); } constexpr void operator++(int) { ++ptr_; } - constexpr InputValueIter& operator++() { ++ptr_; return *this; } + constexpr InputValueIter& operator++() { + ++ptr_; + return *this; + } - constexpr T *operator->() { return ptr_; } -}; + constexpr T* operator->() { return ptr_; } -template -constexpr bool operator==(const InputValueIter &lhs, const T* rhs) { return lhs.ptr_ == rhs; } -template -constexpr bool operator==(const T* lhs, const InputValueIter &rhs) { return rhs.ptr_ == lhs; } + constexpr friend bool operator==(const InputValueIter&, const InputValueIter&) = default; +}; -template +template struct ValueView : std::ranges::view_base { InputValueIter ptr_; - constexpr ValueView(T *ptr) : ptr_(ptr) {} + using sentinel = sentinel_wrapper>; - constexpr ValueView(ValueView &&other) - : ptr_(other.ptr_) { other.ptr_.ptr_ = nullptr; } + constexpr ValueView(T* ptr) : ptr_(ptr) {} - constexpr ValueView& operator=(ValueView &&other) { + constexpr ValueView(ValueView&& other) : ptr_(other.ptr_) { other.ptr_.ptr_ = nullptr; } + + constexpr ValueView& operator=(ValueView&& other) { ptr_ = other.ptr_; other.ptr_ = InputValueIter(nullptr); return *this; @@ -132,10 +153,260 @@ ValueView(const ValueView&) = delete; ValueView& operator=(const ValueView&) = delete; - constexpr InputValueIter begin() { return ptr_; } constexpr InputValueIter begin() const { return ptr_; } - constexpr T *end() { return ptr_.ptr_ + 4; } - constexpr const T *end() const { return ptr_.ptr_ + 4; } + constexpr sentinel end() const { return sentinel(InputValueIter(ptr_.ptr_ + 4)); } +}; + +template +struct BufferView : std::ranges::view_base { + + using T = std::iter_value_t; + T* data_; + std::size_t size_; + + template + constexpr BufferView(T (&b)[N]) : data_(b), size_(N) {} + constexpr BufferView(T* p, std::size_t s) : data_(p), size_(s) {} + + constexpr NonConstIter begin() + requires(!std::is_same_v) { + return NonConstIter(this->data_); + } + constexpr Iter begin() const { return Iter(this->data_); } + + constexpr NonConstSent end() + requires(!std::is_same_v) { + if constexpr (std::is_same_v) { + return NonConstIter(this->data_ + this->size_); + } else { + return NonConstSent(NonConstIter(this->data_ + this->size_)); + } + } + + constexpr Sent end() const { + if constexpr (std::is_same_v) { + return Iter(this->data_ + this->size_); + } else { + return Sent(Iter(this->data_ + this->size_)); + } + } +}; + +// an `input_iterator` that can be used in a `common_range` +template +struct common_input_iterator { + Base it_; + + using value_type = std::iter_value_t; + using difference_type = std::intptr_t; + using iterator_concept = std::input_iterator_tag; + + constexpr common_input_iterator() = default; + constexpr explicit common_input_iterator(Base it) : it_(it) {} + + constexpr common_input_iterator& operator++() { + ++it_; + return *this; + } + constexpr void operator++(int) { ++it_; } + + constexpr std::iter_reference_t operator*() const { return *it_; } + + friend constexpr bool operator==(common_input_iterator const&, common_input_iterator const&) = default; +}; + +using InputCommonInner = BufferView>; +static_assert(std::ranges::input_range); +static_assert(!std::ranges::forward_range); +static_assert(std::ranges::common_range); + +using InputNonCommonInner = BufferView, sentinel_wrapper>>; +static_assert(std::ranges::input_range); +static_assert(!std::ranges::forward_range); +static_assert(!std::ranges::common_range); + +using ForwardCommonInner = BufferView>; +static_assert(std::ranges::forward_range); +static_assert(!std::ranges::bidirectional_range); +static_assert(std::ranges::common_range); + +using ForwardNonCommonInner = BufferView, sentinel_wrapper>>; +static_assert(std::ranges::forward_range); +static_assert(!std::ranges::bidirectional_range); +static_assert(!std::ranges::common_range); + +using BidiCommonInner = BufferView>; +static_assert(std::ranges::bidirectional_range); +static_assert(std::ranges::common_range); + +using BidiNonCommonInner = BufferView, sentinel_wrapper>>; +static_assert(std::ranges::bidirectional_range); +static_assert(!std::ranges::common_range); + +template > +using SimpleInputCommonOuter = BufferView>; +static_assert(!std::ranges::forward_range>); +static_assert(!std::ranges::bidirectional_range>); +static_assert(std::ranges::common_range>); +LIBCPP_STATIC_ASSERT(std::ranges::__simple_view>); + +template > +using NonSimpleInputCommonOuter = BufferView, common_input_iterator, + common_input_iterator< Inner*>, common_input_iterator< Inner*>>; +static_assert(!std::ranges::forward_range>); +static_assert(!std::ranges::bidirectional_range>); +static_assert(std::ranges::common_range>); +LIBCPP_STATIC_ASSERT(!std::ranges::__simple_view>); + +template > +using SimpleForwardCommonOuter = BufferView>; +static_assert(std::ranges::forward_range>); +static_assert(!std::ranges::bidirectional_range>); +static_assert(std::ranges::common_range>); +LIBCPP_STATIC_ASSERT(std::ranges::__simple_view>); + +template > +using NonSimpleForwardCommonOuter = BufferView, forward_iterator, + forward_iterator, forward_iterator>; +static_assert(std::ranges::forward_range>); +static_assert(!std::ranges::bidirectional_range>); +static_assert(std::ranges::common_range>); +LIBCPP_STATIC_ASSERT(!std::ranges::__simple_view>); + +template > +using SimpleForwardNonCommonOuter = BufferView, sentinel_wrapper>>; +static_assert(std::ranges::forward_range>); +static_assert(!std::ranges::bidirectional_range>); +static_assert(!std::ranges::common_range>); +LIBCPP_STATIC_ASSERT(std::ranges::__simple_view>); + +template > +using NonSimpleForwardNonCommonOuter = + BufferView, sentinel_wrapper>, + forward_iterator, sentinel_wrapper>>; +static_assert(std::ranges::forward_range>); +static_assert(!std::ranges::bidirectional_range>); +static_assert(!std::ranges::common_range>); +LIBCPP_STATIC_ASSERT(!std::ranges::__simple_view>); + +template > +using BidiCommonOuter = BufferView>; +static_assert(std::ranges::bidirectional_range>); +static_assert(std::ranges::common_range>); +LIBCPP_STATIC_ASSERT(std::ranges::__simple_view>); + +// an iterator where its operator* makes a copy of underlying operator* +template +struct copying_iterator { + It it_ = It(); + + using value_type = typename std::iterator_traits::value_type; + using difference_type = typename std::iterator_traits::difference_type; + using pointer = typename std::iterator_traits::pointer; + + copying_iterator() requires std::default_initializable = default; + constexpr copying_iterator(It it) : it_(std::move(it)) {} + + // makes a copy of underlying operator* to create a PRValue + constexpr value_type operator*() const { return *it_; } + + constexpr copying_iterator& operator++() { + ++it_; + return *this; + } + constexpr copying_iterator& operator--() + requires std::bidirectional_iterator { + --it_; + return *this; + } + constexpr copying_iterator operator++(int) + requires std::forward_iterator { + return copying_iterator(it_++); + } + constexpr void operator++(int) { return it_++; } + constexpr copying_iterator operator--(int) + requires std::bidirectional_iterator { + return copying_iterator(it_--); + } + + friend constexpr bool operator==(const copying_iterator& x, const copying_iterator& y) = default; +}; + +template +struct InnerRValue : Outer { + + using iterator = copying_iterator>; + using const_iterator = copying_iterator>; + using sentinel = copying_iterator>; + using const_sentinel = copying_iterator>; + + using Outer::Outer; + static_assert(std::ranges::common_range, "non-common range is not supported yet"); + + constexpr iterator begin() { return Outer::begin(); } + constexpr const_iterator begin() const + requires std::ranges::range { + return Outer::begin(); + } + + constexpr auto end() { return iterator{Outer::end()}; } + constexpr auto end() const + requires std::ranges::range { + return const_iterator{Outer::end()}; + } +}; +static_assert(std::ranges::forward_range>>); +static_assert(!std::ranges::bidirectional_range>>); +static_assert(std::ranges::common_range>>); +LIBCPP_STATIC_ASSERT(std::ranges::__simple_view>>); +static_assert(!std::is_lvalue_reference_v>>>); + +struct move_swap_aware_iter { + + // This is a proxy-like iterator where `reference` is a prvalue, and + // `reference` and `value_type` are distinct types (similar to `zip_view::iterator`). + using value_type = std::pair; + using reference = std::pair; + using rvalue_reference = std::pair; + + using difference_type = std::intptr_t; + using iterator_concept = std::input_iterator_tag; + + int* iter_move_called = nullptr; + int* iter_swap_called = nullptr; + int* i_ = nullptr; + + constexpr move_swap_aware_iter& operator++() { + ++i_; + return *this; + } + constexpr void operator++(int) { ++i_; } + + constexpr reference operator*() const { return reference(*i_, *i_); } + constexpr friend bool operator==(const move_swap_aware_iter& x, const move_swap_aware_iter& y) { + return x.i_ == y.i_; + } + + constexpr friend rvalue_reference iter_move(const move_swap_aware_iter& x) noexcept { + ++(*x.iter_move_called); + return rvalue_reference{std::move(*x.i_), std::move(*x.i_)}; + } + + constexpr friend void iter_swap(const move_swap_aware_iter& x, const move_swap_aware_iter& y) noexcept { + ++(*x.iter_swap_called); + std::swap(*x.i_, *y.i_); + } +}; + +struct IterMoveSwapAwareView : BufferView { + int iter_move_called = 0; + int iter_swap_called = 0; + using BufferView::BufferView; + + constexpr auto begin() { return move_swap_aware_iter{&iter_move_called, &iter_swap_called, data_}; } + + constexpr auto end() { return move_swap_aware_iter{&iter_move_called, &iter_swap_called, data_ + size_}; } }; +static_assert(std::ranges::input_range); #endif // TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_JOIN_TYPES_H