[libcxx-commits] [libcxx] 3d3103b - [libcxx][ranges] add views::join adaptor object. added test coverage to join_view

Nikolas Klauser via libcxx-commits libcxx-commits at lists.llvm.org
Thu Apr 21 04:12:06 PDT 2022


Author: Hui Xie
Date: 2022-04-21T13:10:46+02:00
New Revision: 3d3103b733d4346d583a3ada3aabdaa9de4f0446

URL: https://github.com/llvm/llvm-project/commit/3d3103b733d4346d583a3ada3aabdaa9de4f0446
DIFF: https://github.com/llvm/llvm-project/commit/3d3103b733d4346d583a3ada3aabdaa9de4f0446.diff

LOG: [libcxx][ranges] add views::join adaptor object. added test coverage to join_view

- added views::join adaptor object
- added test for the adaptor object
- fixed some join_view's tests. e.g iter_swap test
- added some negative tests for join_view to test that operations do not exist when constraints aren't met
- added tests that locks down issues that were already addressed in previous change
  - LWG3500 `join_view::iterator::operator->()` is bogus
  - LWG3313 `join_view::iterator::operator--` is incorrectly constrained
  - LWG3517 `join_view::iterator`'s `iter_swap` is underconstrained
  - P2328R1 join_view should join all views of ranges
- fixed some issues in join_view and added tests
  - LWG3535 `join_view::iterator::iterator_category` and `::iterator_concept` lie
  - LWG3474 Nesting ``join_views`` is broken because of CTAD
- added tests for an LWG issue that isn't resolved in the standard yet, but the previous code has workaround.
  - LWG3569 Inner iterator not default_initializable

Reviewed By: #libc, var-const

Spies: var-const, libcxx-commits

Differential Revision: https://reviews.llvm.org/D123466

Added: 
    libcxx/test/std/ranges/range.adaptors/range.join.view/adaptor.pass.cpp

Modified: 
    libcxx/docs/Status/Cxx20Issues.csv
    libcxx/docs/Status/Cxx2bIssues.csv
    libcxx/docs/Status/RangesIssues.csv
    libcxx/include/__ranges/join_view.h
    libcxx/test/std/ranges/range.adaptors/range.join.view/begin.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join.view/ctad.compile.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join.view/ctor.default.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join.view/end.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join.view/general.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/arrow.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.other.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.parent.outer.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/decrement.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/eq.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/increment.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/iter.move.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/iter.swap.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/member_types.compile.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.other.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/eq.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join.view/types.h

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/Status/Cxx20Issues.csv b/libcxx/docs/Status/Cxx20Issues.csv
index 9d34066e50d9d..d307fb17a4359 100644
--- a/libcxx/docs/Status/Cxx20Issues.csv
+++ b/libcxx/docs/Status/Cxx20Issues.csv
@@ -234,7 +234,7 @@
 "`3304 <https://wg21.link/LWG3304>`__","Allocate functions of ``std::polymorphic_allocator``\  should require ``[[nodiscard]]``\ ","Prague","",""
 "`3307 <https://wg21.link/LWG3307>`__","``std::allocator<void>().allocate(n)``\ ","Prague","",""
 "`3310 <https://wg21.link/LWG3310>`__","Replace ``SIZE_MAX``\  with ``numeric_limits<size_t>::max()``\ ","Prague","",""
-"`3313 <https://wg21.link/LWG3313>`__","``join_view::iterator::operator--``\  is incorrectly constrained","Prague","","","|ranges|"
+"`3313 <https://wg21.link/LWG3313>`__","``join_view::iterator::operator--``\  is incorrectly constrained","Prague","|Complete|","14.0","|ranges|"
 "`3314 <https://wg21.link/LWG3314>`__","Is stream insertion behavior locale dependent when ``Period::type``\  is ``micro``\ ?","Prague","","","|chrono|"
 "`3315 <https://wg21.link/LWG3315>`__","Correct Allocator Default Behavior","Prague","",""
 "`3316 <https://wg21.link/LWG3316>`__","Correctly define epoch for ``utc_clock``\  / ``utc_timepoint``\ ","Prague","","","|chrono|"

diff  --git a/libcxx/docs/Status/Cxx2bIssues.csv b/libcxx/docs/Status/Cxx2bIssues.csv
index 7adfbf251ceb7..9047a63b6dd65 100644
--- a/libcxx/docs/Status/Cxx2bIssues.csv
+++ b/libcxx/docs/Status/Cxx2bIssues.csv
@@ -44,7 +44,7 @@
 "`3467 <https://wg21.link/LWG3467>`__","``bool`` can't be an integer-like type","November 2020","|Complete|","14.0"
 "`3472 <https://wg21.link/LWG3472>`__","``counted_iterator`` is missing preconditions","November 2020","|Complete|","14.0","|ranges|"
 "`3473 <https://wg21.link/LWG3473>`__","Normative encouragement in non-normative note","November 2020","|Nothing To Do|","","|format|"
-"`3474 <https://wg21.link/LWG3474>`__","Nesting ``join_views`` is broken because of CTAD","November 2020","","","|ranges|"
+"`3474 <https://wg21.link/LWG3474>`__","Nesting ``join_views`` is broken because of CTAD","November 2020","|Complete|","15.0","|ranges|"
 "`3476 <https://wg21.link/LWG3476>`__","``thread`` and ``jthread`` constructors require that the parameters be move-constructible but never move construct the parameters","November 2020","",""
 "`3477 <https://wg21.link/LWG3477>`__","Simplify constraints for ``semiregular-box``","November 2020","","","|ranges|"
 "`3482 <https://wg21.link/LWG3482>`__","``drop_view``'s const begin should additionally require ``sized_range``","November 2020","|Complete|","14.0","|ranges|"
@@ -56,7 +56,7 @@
 "`3492 <https://wg21.link/LWG3492>`__","Minimal improvements to ``elements_view::iterator``","February 2021","","","|ranges|"
 "`3494 <https://wg21.link/LWG3494>`__","Allow ranges to be conditionally borrowed","February 2021","Superseded by `P2017R1 <https://wg21.link/P2017R1>`__","","|ranges|"
 "`3495 <https://wg21.link/LWG3495>`__","``constexpr launder`` makes pointers to inactive members of unions usable","February 2021","|Nothing To Do|",""
-"`3500 <https://wg21.link/LWG3500>`__","``join_view::iterator::operator->()`` is bogus","February 2021","","","|ranges|"
+"`3500 <https://wg21.link/LWG3500>`__","``join_view::iterator::operator->()`` is bogus","February 2021","|Complete|","14.0","|ranges|"
 "`3502 <https://wg21.link/LWG3502>`__","``elements_view`` should not be allowed to return dangling reference","February 2021","","","|ranges|"
 "`3505 <https://wg21.link/LWG3505>`__","``split_view::outer-iterator::operator++`` misspecified","February 2021","","","|ranges|"
 "","","","",""
@@ -68,7 +68,7 @@
 `3462 <https://wg21.link/LWG3462>`__,"§[formatter.requirements]: Formatter requirements forbid use of ``fc.arg()``","June 2021","","","|format|"
 `3481 <https://wg21.link/LWG3481>`__,"``viewable_range`` mishandles lvalue move-only views","June 2021","Superseded by `P2415R2 <https://wg21.link/P2415R2>`__","","|ranges|"
 `3506 <https://wg21.link/LWG3506>`__,"Missing allocator-extended constructors for ``priority_queue``","June 2021","|Complete|","14.0"
-`3517 <https://wg21.link/LWG3517>`__,"``join_view::iterator``'s ``iter_swap`` is underconstrained","June 2021","","","|ranges|"
+`3517 <https://wg21.link/LWG3517>`__,"``join_view::iterator``'s ``iter_swap`` is underconstrained","June 2021","|Complete|","14.0","|ranges|"
 `3518 <https://wg21.link/LWG3518>`__,"Exception requirements on char trait operations unclear","June 2021","|Nothing To Do|",""
 `3519 <https://wg21.link/LWG3519>`__,"Incomplete synopses for ``<random>`` classes","June 2021","",""
 `3520 <https://wg21.link/LWG3520>`__,"``iter_move`` and ``iter_swap`` are inconsistent for ``transform_view::iterator``","June 2021","|Complete|","14.0","|ranges|"
@@ -112,7 +112,7 @@
 `3470 <https://wg21.link/LWG3470>`__,"``convertible-to-non-slicing`` seems to reject valid case","October 2021","|Complete|","14.0","|ranges|"
 `3480 <https://wg21.link/LWG3480>`__,"``directory_iterator`` and ``recursive_directory_iterator`` are not C++20 ranges","October 2021","|Complete|","14.0","|ranges|"
 `3498 <https://wg21.link/LWG3498>`__,"Inconsistent ``noexcept``-specifiers for ``basic_syncbuf``","October 2021","",""
-`3535 <https://wg21.link/LWG3535>`__,"``join_view::iterator::iterator_category`` and ``::iterator_concept`` lie","October 2021","","","|ranges|"
+`3535 <https://wg21.link/LWG3535>`__,"``join_view::iterator::iterator_category`` and ``::iterator_concept`` lie","October 2021","|Complete|","15.0","|ranges|"
 `3554 <https://wg21.link/LWG3554>`__,"``chrono::parse`` needs ``const charT*`` and ``basic_string_view<charT>`` overloads","October 2021","","","|chrono|"
 `3557 <https://wg21.link/LWG3557>`__,"The ``static_cast`` expression in ``convertible_to`` has the wrong operand","October 2021","|Complete|","14.0"
 `3559 <https://wg21.link/LWG3559>`__,"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
index 728742d5b9d6c..12d40e32fa734 100644
--- a/libcxx/docs/Status/RangesIssues.csv
+++ b/libcxx/docs/Status/RangesIssues.csv
@@ -24,7 +24,7 @@
 `P2106R0 <https://wg21.link/P2106R0>`__,Range Algorithm Result Types,,
 
 `P2325R3 <https://wg21.link/P2325R3>`__,Views should not be required to be default constructible ,,
-`P2328R1 <https://wg21.link/P2328R1>`__,join_view should join all views of ranges,,
+`P2328R1 <https://wg21.link/P2328R1>`__,join_view should join all views of ranges,|Complete|,14.0
 `P2210R2 <https://wg21.link/P2210R2>`__,Superior String Splitting,,
 `P2281R1 <https://wg21.link/P2281R1>`__,Clarifying range adaptor objects,|Complete|,14.0
 `P2367R0 <https://wg21.link/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
index b95db5bb30fe6..b6fcce95aeda9 100644
--- 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 <optional>
 #include <type_traits>
@@ -52,7 +50,8 @@ namespace ranges {
     using _InnerC = typename iterator_traits<iterator_t<range_reference_t<_View>>>::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<range_reference_t<_View>>,
       bidirectional_iterator_tag,
       _If<
         derived_from<_OuterC, forward_iterator_tag> && derived_from<_InnerC, forward_iterator_tag>,
@@ -211,7 +210,8 @@ namespace ranges {
 
   public:
     using iterator_concept = _If<
-      __ref_is_glvalue && bidirectional_range<_Base> && bidirectional_range<range_reference_t<_Base>>,
+      __ref_is_glvalue && bidirectional_range<_Base> && bidirectional_range<range_reference_t<_Base>> && 
+          common_range<range_reference_t<_Base>>,
       bidirectional_iterator_tag,
       _If<
         __ref_is_glvalue && forward_range<_Base> && forward_range<range_reference_t<_Base>>,
@@ -345,7 +345,22 @@ namespace ranges {
 
   template<class _Range>
   explicit join_view(_Range&&) -> join_view<views::all_t<_Range>>;
-
+  
+namespace views {
+namespace __join_view {
+struct __fn : __range_adaptor_closure<__fn> {
+  template<class _Range>
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI
+  constexpr auto operator()(_Range&& __range) const
+    noexcept(noexcept(join_view<all_t<_Range&&>>(std::forward<_Range>(__range))))
+    -> decltype(      join_view<all_t<_Range&&>>(std::forward<_Range>(__range)))
+    { return          join_view<all_t<_Range&&>>(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
index 0000000000000..c28cc8413e017
--- /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 <ranges>
+
+#include <cassert>
+#include <type_traits>
+
+#include "types.h"
+
+struct MoveOnlyOuter : SimpleForwardCommonOuter<ForwardCommonInner> {
+  using SimpleForwardCommonOuter<ForwardCommonInner>::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 <class View, class T>
+concept CanBePiped = requires(View&& view, T&& t) {
+  { std::forward<View>(view) | std::forward<T>(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::ranges::ref_view<ForwardCommonInner[3]>>;
+    std::same_as<Result> 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<MoveOnlyOuter>;
+    std::same_as<Result> 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<decltype(std::views::join), MoveOnlyOuter>);
+    static_assert(!std::invocable<decltype(std::views::join), MoveOnlyOuter&>);
+  }
+
+  {
+    // 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<decltype(jv)>, Foo(&)[3]);
+
+    auto jv2 = std::views::join(jv);
+    ASSERT_SAME_TYPE(std::ranges::range_reference_t<decltype(jv2)>, 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::ranges::ref_view<ForwardCommonInner[3]>>;
+    std::same_as<Result> decltype(auto) v = inners | std::views::join;
+    assert(std::ranges::next(v.begin(), 9) == v.end());
+    assert(&(*v.begin()) == buffer1);
+    static_assert(CanBePiped<decltype((inners)), decltype((std::views::join))>);
+  }
+
+  {
+    // Test `move-only-view | views::join`
+    ForwardCommonInner inners[3] = {buffer1, buffer2, buffer3};
+    using Result = std::ranges::join_view<MoveOnlyOuter>;
+    std::same_as<Result> decltype(auto) v = MoveOnlyOuter{inners} | std::views::join;
+    assert(std::ranges::next(v.begin(), 9) == v.end());
+    assert(&(*v.begin()) == buffer1);
+
+    static_assert(CanBePiped<MoveOnlyOuter, decltype((std::views::join))>);
+    static_assert(!CanBePiped<MoveOnlyOuter&, decltype((std::views::join))>);
+  }
+
+  {
+    // 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<decltype(jv)>, Foo&);
+
+    assert(&(*jv.begin()) == &nested[0][0][0]);
+    static_assert(CanBePiped<decltype((nested)), decltype((std::views::join))>);
+  }
+
+  {
+    // 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<decltype(jv)>, Foo&);
+
+    assert(&(*jv.begin()) == &nested[0][0][0]);
+    static_assert(CanBePiped<decltype((nested)), decltype((join_twice))>);
+  }
+
+  {
+    static_assert(!CanBePiped<int, decltype((std::views::join))>);
+    static_assert(!CanBePiped<Foo, decltype((std::views::join))>);
+    static_assert(!CanBePiped<int(&)[2], decltype((std::views::join))>);
+    static_assert(CanBePiped<int(&)[2][2], decltype((std::views::join))>);
+  }
+
+  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
index 10d18a8f92abc..8482b780f16cf 100644
--- 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<const V> &&
+//             is_reference_v<range_reference_t<const V>>;
 
 #include <cassert>
 #include <ranges>
 
-#include "test_macros.h"
 #include "types.h"
 
 struct NonSimpleParentView : std::ranges::view_base {
@@ -29,6 +30,16 @@ struct SimpleParentView : std::ranges::view_base {
   const ChildView* end() const;
 };
 
+struct ConstNotRange : std::ranges::view_base {
+  const ChildView* begin();
+  const ChildView* end();
+};
+static_assert(std::ranges::range<ConstNotRange>);
+static_assert(!std::ranges::range<const ConstNotRange>);
+
+template <class T>
+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 @@ constexpr bool test() {
   }
 
   {
-    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 @@ constexpr bool test() {
   {
     const std::ranges::join_view jv(buffer);
     assert(*jv.begin() == 1111);
+    static_assert(HasConstBegin<decltype(jv)>);
+  }
+
+  // !input_­range<const V>
+  {
+    std::ranges::join_view jv{ConstNotRange{}};
+    static_assert(!HasConstBegin<decltype(jv)>);
+  }
+
+  // !is_reference_v<range_reference_t<const V>>
+  {
+    auto innerRValueRange = std::views::iota(0, 5) | std::views::transform([](int) { return ChildView{}; });
+    static_assert(!std::is_reference_v<std::ranges::range_reference_t<const decltype(innerRValueRange)>>);
+    std::ranges::join_view jv{innerRValueRange};
+    static_assert(!HasConstBegin<decltype(jv)>);
   }
 
   // !simple-view<V>
   {
     std::ranges::join_view<NonSimpleParentView> jv;
-    static_assert(!std::same_as<decltype(jv.begin()),
-                                decltype(std::as_const(jv).begin())>);
+    static_assert(!std::same_as<decltype(jv.begin()), decltype(std::as_const(jv).begin())>);
   }
 
   // simple-view<V> && is_reference_v<range_reference_t<V>>;
   {
     std::ranges::join_view<SimpleParentView> jv;
-    static_assert(std::same_as<decltype(jv.begin()),
-                               decltype(std::as_const(jv).begin())>);
+    static_assert(std::same_as<decltype(jv.begin()), decltype(std::as_const(jv).begin())>);
   }
 
   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
index 95c1ef3c399a6..7a7d31dd7203b 100644
--- 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 @@ struct BorrowedRange {
 template<>
 inline constexpr bool std::ranges::enable_borrowed_range<BorrowedRange> = true;
 
+struct NestedChildren : std::ranges::view_base {
+  View* begin() const;
+  View* end() const;
+};
+
 void testCTAD() {
     View v;
     Range r;
@@ -66,4 +71,13 @@ void testCTAD() {
         decltype(std::ranges::join_view(std::move(br))),
         std::ranges::join_view<std::ranges::owning_view<BorrowedRange>>
     >);
+
+    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
index a5a24570c776c..a89ade02628a8 100644
--- 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 <cassert>
 #include <ranges>
 
-#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<ParentView<ChildView>> jv;
-  assert(std::move(jv).base().ptr_ == globalChildren);
+  {
+    std::ranges::join_view<ParentView<ChildView>> jv;
+    assert(std::move(jv).base().ptr_ == globalChildren);
+  }
+
+  // Default constructor should value initialise underlying view
+  {
+    std::ranges::join_view<DefaultView> jv;
+    assert(jv.base().i == 0);
+  }
 
   static_assert( std::default_initializable<std::ranges::join_view<ParentView<ChildView>>>);
   static_assert(!std::default_initializable<std::ranges::join_view<CopyableParent>>);

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
index 9ac91e6bd016e..d36514a81df8f 100644
--- 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 <class T>
+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<true> |iterator<true>|
+// | 2  |   Y    |   Y     |   Y    |   Y   |    Y    |   N    |sentinel<true> |sentinel<true>|
+// | 3  |   Y    |   Y     |   Y    |   Y   |    N    |   Y    |sentinel<true> |sentinel<true>|
+// | 4  |   Y    |   Y     |   Y    |   N   |    Y    |   Y    |sentinel<true> |      -       |
+// | 5  |   Y    |   Y     |   N    |   Y   |    Y    |   Y    |sentinel<true> |sentinel<true>|
+// | 6  |   Y    |   N     |   Y    |   Y   |    Y    |   Y    |sentinel<true> |sentinel<true>|
+// | 7  |   N    |   Y     |   Y    |   Y   |    Y    |   Y    |iterator<false>|iterator<true>|
+// | 8  |   N    |   Y     |   Y    |   Y   |    Y    |   N    |sentinel<false>|sentinel<true>|
+// | 9  |   N    |   Y     |   Y    |   Y   |    N    |   Y    |sentinel<false>|sentinel<true>|
+// | 10 |   N    |   Y     |   Y    |   N   |    Y    |   Y    |sentinel<false>|      -       |
+// | 11 |   N    |   Y     |   N    |   Y   |    Y    |   Y    |sentinel<false>|sentinel<true>|
+// | 12 |   N    |   N     |   Y    |   Y   |    Y    |   Y    |sentinel<false>|sentinel<true>|
+//
+//
+
+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<ForwardCommonInner> 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<decltype(jv.end()), decltype(jv.begin())>);
+    static_assert(HasConstEnd<decltype(jv)>);
+    static_assert(std::same_as<decltype(jv.end()), decltype(std::as_const(jv).end())>);
+    static_assert(std::ranges::common_range<decltype(jv)>);
+    static_assert(std::ranges::common_range<const decltype(jv)>);
   }
 
-  // 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<ForwardNonCommonInner> 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<decltype(jv)>);
+    static_assert(std::same_as<decltype(jv.end()), decltype(std::as_const(jv).end())>);
+    static_assert(!std::ranges::common_range<decltype(jv)>);
+    static_assert(!std::ranges::common_range<const decltype(jv)>);
+  }
+
+  {
+    // test ID 3
+    InputCommonInner inners[3] = {buffer[0], buffer[1], buffer[2]};
+    SimpleForwardCommonOuter<InputCommonInner> 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<decltype(jv)>);
+    static_assert(std::same_as<decltype(jv.end()), decltype(std::as_const(jv).end())>);
+    static_assert(!std::ranges::common_range<decltype(jv)>);
+    static_assert(!std::ranges::common_range<const decltype(jv)>);
+  }
+
+  {
+    // test ID 4
+    ForwardCommonInner inners[2] = {buffer[0], buffer[1]};
+    InnerRValue<SimpleForwardCommonOuter<ForwardCommonInner>> outer{inners};
+
+    std::ranges::join_view jv(outer);
+    assert(jv.end() == std::ranges::next(jv.begin(), 8));
+
+    static_assert(!HasConstEnd<decltype(jv)>);
+    static_assert(!std::ranges::common_range<decltype(jv)>);
+    static_assert(!std::ranges::common_range<const decltype(jv)>);
+  }
+
+  {
+    // test ID 5
+    ForwardCommonInner inners[4] = {buffer[0], buffer[1], buffer[2], buffer[3]};
+    SimpleForwardNonCommonOuter<ForwardCommonInner> 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<decltype(jv.end()), decltype(jv.begin())>);
+    static_assert(HasConstEnd<decltype(jv)>);
+    static_assert(std::same_as<decltype(jv.end()), decltype(std::as_const(jv).end())>);
+    static_assert(!std::ranges::common_range<decltype(jv)>);
+    static_assert(!std::ranges::common_range<const decltype(jv)>);
   }
 
-  // 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<ForwardCommonInner> 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<decltype(jv.end()), decltype(jv.begin())>);
+    static_assert(HasConstEnd<decltype(jv)>);
+    static_assert(std::same_as<decltype(jv.end()), decltype(std::as_const(jv).end())>);
+    static_assert(!std::ranges::common_range<decltype(jv)>);
+    static_assert(!std::ranges::common_range<const decltype(jv)>);
   }
 
-  // Const not common, input range.
   {
-    static_assert(std::is_reference_v<std::ranges::range_reference_t<const CopyableParent>>);
+    // test ID 7
+    ForwardCommonInner inners[1] = {buffer[0]};
+    NonSimpleForwardCommonOuter<ForwardCommonInner> 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<decltype(jv)>);
+    static_assert(!std::same_as<decltype(jv.end()), decltype(std::as_const(jv).end())>);
+    static_assert(std::ranges::common_range<decltype(jv)>);
+    static_assert(std::ranges::common_range<const decltype(jv)>);
+  }
+
+  {
+    // test ID 8
+    ForwardNonCommonInner inners[3] = {buffer[0], buffer[1], buffer[2]};
+    NonSimpleForwardCommonOuter<ForwardNonCommonInner> 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<decltype(jv)>);
+    static_assert(!std::same_as<decltype(jv.end()), decltype(std::as_const(jv).end())>);
+    static_assert(!std::ranges::common_range<decltype(jv)>);
+    static_assert(!std::ranges::common_range<const decltype(jv)>);
+  }
+
+  {
+    // test ID 9
+    InputCommonInner inners[3] = {buffer[0], buffer[1], buffer[2]};
+    NonSimpleForwardCommonOuter<InputCommonInner> 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<decltype(jv)>);
+    static_assert(!std::same_as<decltype(jv.end()), decltype(std::as_const(jv).end())>);
+    static_assert(!std::ranges::common_range<decltype(jv)>);
+    static_assert(!std::ranges::common_range<const decltype(jv)>);
+  }
+
+  {
+    // test ID 10
+    ForwardCommonInner inners[2] = {buffer[0], buffer[1]};
+    InnerRValue<NonSimpleForwardCommonOuter<ForwardCommonInner>> outer{inners};
+
+    std::ranges::join_view jv(outer);
+    assert(jv.end() == std::ranges::next(jv.begin(), 8));
+
+    static_assert(!HasConstEnd<decltype(jv)>);
+    static_assert(!std::ranges::common_range<decltype(jv)>);
+    static_assert(!std::ranges::common_range<const decltype(jv)>);
+  }
+
+  {
+    // test ID 11
+    ForwardCommonInner inners[4] = {buffer[0], buffer[1], buffer[2], buffer[3]};
+    NonSimpleForwardNonCommonOuter<ForwardCommonInner> 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<decltype(jv.end()), decltype(jv.begin())>);
+    static_assert(HasConstEnd<decltype(jv)>);
+    static_assert(!std::same_as<decltype(jv.end()), decltype(std::as_const(jv).end())>);
+    static_assert(!std::ranges::common_range<decltype(jv)>);
+    static_assert(!std::ranges::common_range<const decltype(jv)>);
+  }
+
+  {
+    // test ID 12
+    ForwardCommonInner inners[4] = {buffer[0], buffer[1], buffer[2], buffer[3]};
+    NonSimpleInputCommonOuter<ForwardCommonInner> 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<decltype(jv)>);
+    static_assert(!std::same_as<decltype(jv.end()), decltype(std::as_const(jv).end())>);
+    static_assert(!std::ranges::common_range<decltype(jv)>);
+    static_assert(!std::ranges::common_range<const decltype(jv)>);
+  }
+
+  {
+    std::ranges::join_view jv(ConstNotRange{});
+    static_assert(!HasConstEnd<decltype(jv)>);
   }
 
   // Has some empty children.
@@ -65,42 +238,49 @@ constexpr bool test() {
     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
index c9c60b7f8d558..030cc62632285 100644
--- 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 <string>
 #include <vector>
 
-#include "test_macros.h"
 #include "types.h"
 
-
-template<class R, class I>
-bool isEqual(R &r, I i) {
+template <class R, class I>
+bool isEqual(R& r, I i) {
   for (auto e : r)
     if (e != *i++)
       return false;
@@ -32,7 +30,7 @@ bool isEqual(R &r, I i) {
 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<int*>(buffer);
+    int* flattened = reinterpret_cast<int*>(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 @@ int main(int, char**) {
     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<int> v(i);
+               for (int& ii : v) {
+                 ii = i;
+               }
+               return v;
+             });
+
+    std::ranges::join_view jv(y);
+    std::vector<int> 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
index ac9624f6f90aa..d37bd91a74898 100644
--- 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 <cassert>
 #include <ranges>
 
-#include "test_macros.h"
 #include "../types.h"
 
+template <class T>
+concept HasArrow = std::input_iterator<T> && (std::is_pointer_v<T> || requires(T i) { i.operator->(); });
+
+template <class Base>
+struct move_only_input_iter_with_arrow {
+  Base it_;
+
+  using value_type = std::iter_value_t<Base>;
+  using 
diff erence_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<Base> operator*() const { return *it_; }
+  constexpr auto operator->() const
+    requires(HasArrow<Base> && std::copyable<Base>) {
+    return it_;
+  }
+};
+static_assert(!std::copyable<move_only_input_iter_with_arrow<int*>>);
+static_assert(std::input_iterator<move_only_input_iter_with_arrow<int*>>);
+
+template <class Base>
+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<Base>& other) const { return it_ == other.it_; }
+};
+static_assert(std::sentinel_for<move_iter_sentinel<int*>, move_only_input_iter_with_arrow<int*>>);
+
+struct MoveOnlyIterInner : BufferView<move_only_input_iter_with_arrow<Box*>, move_iter_sentinel<Box*>> {
+  using BufferView::BufferView;
+
+  using iterator = move_only_input_iter_with_arrow<Box*>;
+  using sentinel = move_iter_sentinel<Box*>;
+
+  iterator begin() const { return data_; }
+  sentinel end() const { return sentinel{data_ + size_}; }
+};
+static_assert(std::ranges::input_range<MoveOnlyIterInner>);
+
+template <class Base>
+struct arrow_input_iter {
+  Base it_;
+
+  using value_type = std::iter_value_t<Base>;
+  using 
diff erence_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<Base> 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<arrow_input_iter<Box*>>;
+static_assert(std::ranges::input_range<ArrowInner>);
+static_assert(HasArrow<std::ranges::iterator_t<ArrowInner>>);
+
 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<Box> children[4] = {ValueView(buffer[0]), ValueView(buffer[1]), ValueView(buffer[2]), ValueView(buffer[3])};
+    ValueView<Box> children[4] = {ValueView(buffer[0]), ValueView(buffer[1]), ValueView(buffer[2]),
+                                  ValueView(buffer[3])};
     std::ranges::join_view jv(ValueView<ValueView<Box>>{children});
     assert(jv.begin()->x == 1111);
+    static_assert(HasArrow<decltype(jv.begin())>);
   }
 
   {
     std::ranges::join_view jv(buffer);
     assert(jv.begin()->x == 1111);
+    static_assert(HasArrow<decltype(jv.begin())>);
   }
 
   {
     const std::ranges::join_view jv(buffer);
     assert(jv.begin()->x == 1111);
+    static_assert(HasArrow<decltype(jv.begin())>);
+  }
+
+  {
+    // LWG3500 `join_view::iterator::operator->()` is bogus
+    // `operator->` should not be defined if inner iterator is not copyable
+    // has-arrow<InnerIter> && !copyable<InnerIter>
+    static_assert(HasArrow<move_only_input_iter_with_arrow<int*>>);
+    MoveOnlyIterInner inners[2] = {buffer[0], buffer[1]};
+    std::ranges::join_view jv{inners};
+    static_assert(HasArrow<decltype(std::ranges::begin(inners[0]))>);
+    static_assert(!HasArrow<decltype(jv.begin())>);
+  }
+
+  {
+    // LWG3500 `join_view::iterator::operator->()` is bogus
+    // `operator->` should not be defined if inner iterator does not have `operator->`
+    // !has-arrow<InnerIter> && copyable<InnerIter>
+    using Inner = BufferView<forward_iterator<Box*>>;
+    Inner inners[2] = {buffer[0], buffer[1]};
+    std::ranges::join_view jv{inners};
+    static_assert(!HasArrow<decltype(std::ranges::begin(inners[0]))>);
+    static_assert(!HasArrow<decltype(jv.begin())>);
   }
 
+  {
+    // arrow returns inner iterator
+    ArrowInner inners[2] = {buffer[0], buffer[1]};
+    std::ranges::join_view jv{inners};
+    static_assert(HasArrow<decltype(std::ranges::begin(inners[0]))>);
+    static_assert(HasArrow<decltype(jv.begin())>);
+
+    auto jv_it = jv.begin();
+    std::same_as<arrow_input_iter<Box*>> 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
index dba4dd403a88a..28632086d01c8 100644
--- 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<!Const> i);
+// constexpr iterator(iterator<!Const> i)
+//             requires Const &&
+//                      convertible_­to<iterator_t<V>, OuterIter> &&
+//                      convertible_­to<iterator_t<InnerRng>, InnerIter>;
 
 #include <cassert>
 #include <ranges>
@@ -17,17 +20,60 @@
 #include "test_macros.h"
 #include "../types.h"
 
+using ConstCompatibleInner = BufferView<int*>;
+
+using ConstIncompatibleInner = BufferView<forward_iterator<const int*>, forward_iterator<const int*>,
+                                          bidirectional_iterator<int*>, bidirectional_iterator<int*>>;
+
+template <class Inner>
+using ConstCompatibleOuter = BufferView<const Inner*, const Inner*, Inner*, Inner*>;
+
+template <class Inner>
+using ConstIncompatibleOuter = BufferView<forward_iterator<const Inner*>, forward_iterator<const Inner*>,
+                                          bidirectional_iterator<Inner*>, bidirectional_iterator<Inner*>>;
+
 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<iterator, const_iterator>);
+    const_iterator iter2 = iter1;
+    assert(iter1 == iter2);
+
+    // We cannot create a non-const iterator from a const iterator.
+    static_assert(!std::constructible_from<iterator, const_iterator>);
+  }
+
+  // !convertible_to<inner_iterator, inner_const_iterator>>;
+  {
+    ConstIncompatibleInner inners[2] = {buffer[0], buffer[1]};
+    ConstCompatibleOuter<ConstIncompatibleInner> 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<iterator, const_iterator>);
+
+    static_assert(!std::constructible_from<const_iterator, iterator>);
+    static_assert(!std::constructible_from<iterator, const_iterator>);
+  }
 
-  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<const decltype(jv)> iter2 = iter1;
-  assert(iter1 == iter2);
+  // !convertible_to<outer_iterator, outer_const_iterator>>;
+  {
+    ConstCompatibleInner inners[2] = {buffer[0], buffer[1]};
+    ConstIncompatibleOuter<ConstCompatibleInner> 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<iterator, const_iterator>);
 
-  // We cannot create a non-const iterator from a const iterator.
-  static_assert(!std::constructible_from<decltype(iter1), decltype(iter2)>);
+    static_assert(!std::constructible_from<const_iterator, iterator>);
+    static_assert(!std::constructible_from<iterator, const_iterator>);
+  }
 
   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
index 2973a3d1907bb..ced15797cf1aa 100644
--- 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 <cassert>
 #include <ranges>
 
-#include "test_macros.h"
 #include "../types.h"
 
+using NonDefaultCtrIter = cpp20_input_iterator<int*>;
+static_assert(!std::default_initializable<NonDefaultCtrIter>);
+
+using NonDefaultCtrIterView = BufferView<NonDefaultCtrIter, sentinel_wrapper<NonDefaultCtrIter>>;
+static_assert(std::ranges::input_range<NonDefaultCtrIterView>);
+
 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<decltype(jv)> 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<decltype(jv)> 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<decltype(jv)> 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
index b15363a4d28fe..ed171327b693c 100644
--- 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<Base> &&
+//                       bidirectional_­range<range_reference_t<Base>> &&
+//                       common_­range<range_reference_t<Base>>;
 // constexpr iterator operator--(int);
+//              requires ref-is-glvalue && bidirectional_­range<Base> &&
+//                       bidirectional_­range<range_reference_t<Base>> &&
+//                       common_­range<range_reference_t<Base>>;
 
 #include <cassert>
 #include <ranges>
+#include <type_traits>
 
-#include "test_macros.h"
 #include "../types.h"
 
+template <class T>
+concept CanPreDecrement = requires(T& t) { --t; };
+
+template <class T>
+concept CanPostDecrement = requires(T& t) { t--; };
+
+constexpr void noDecrementTest(auto&& jv) {
+  auto iter = jv.begin();
+  static_assert(!CanPreDecrement<decltype(iter)>);
+  static_assert(!CanPostDecrement<decltype(iter)>);
+}
+
 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 @@ constexpr bool test() {
       assert(*--iter == i);
     }
   }
+
   {
     // outer == ranges::end
     std::ranges::join_view jv(buffer);
@@ -37,6 +56,7 @@ constexpr bool test() {
       assert(*--iter == i);
     }
   }
+
   {
     // outer != ranges::end
     std::ranges::join_view jv(buffer);
@@ -45,6 +65,7 @@ constexpr bool test() {
       assert(*--iter == i);
     }
   }
+
   {
     // outer != ranges::end
     std::ranges::join_view jv(buffer);
@@ -53,6 +74,7 @@ constexpr bool test() {
       assert(*--iter == i);
     }
   }
+
   {
     int small[2][1] = {{1}, {2}};
     std::ranges::join_view jv(small);
@@ -62,6 +84,72 @@ constexpr bool test() {
     }
   }
 
+  {
+#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<decltype(iter2), iterator&>);
+    assert(&iter1 == &iter2);
+
+    std::same_as<iterator> decltype(auto) iter3 = iter1--;
+    assert(iter3 == std::next(iter1));
+  }
+
+  {
+    // !ref-is-glvalue
+    BidiCommonInner inners[2] = {buffer[0], buffer[1]};
+    InnerRValue<BidiCommonOuter<BidiCommonInner>> outer{inners};
+    std::ranges::join_view jv(outer);
+    noDecrementTest(jv);
+  }
+
+  {
+    // !bidirectional_­range<Base>
+    BidiCommonInner inners[2] = {buffer[0], buffer[1]};
+    SimpleForwardCommonOuter<BidiCommonInner> outer{inners};
+    std::ranges::join_view jv(outer);
+    noDecrementTest(jv);
+  }
+
+  {
+    // !bidirectional_­range<range_reference_t<Base>>
+    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<range_reference_t<Base>>
+    BidiNonCommonInner inners[2] = {buffer[0], buffer[1]};
+    std::ranges::join_view jv(inners);
+    auto iter = jv.begin();
+    static_assert(!CanPreDecrement<decltype(iter)>);
+    static_assert(!CanPostDecrement<decltype(iter)>);
+  }
+
   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
index e8598d2fd228a..204a0568e1286 100644
--- 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<iterator_t<Base>> &&
+//                   equality_comparable<iterator_t<range_reference_t<Base>>>;
 
 #include <cassert>
 #include <ranges>
 
-#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<BidiCommonOuter<BidiCommonInner>> outer{inners};
+    std::ranges::join_view jv(outer);
+    auto iter = jv.begin();
+    static_assert(!std::equality_comparable<decltype(iter)>);
+  }
+
+  {
+    // !equality_comparable<iterator_t<Base>>
+    using Inner = BufferView<int*>;
+    using Outer = BufferView<cpp20_input_iterator<Inner*>, sentinel_wrapper<cpp20_input_iterator<Inner*>>>;
+    static_assert(!std::equality_comparable<std::ranges::iterator_t<Outer>>);
+    Inner inners[2] = {buffer[0], buffer[1]};
+    std::ranges::join_view jv(Outer{inners});
+    auto iter = jv.begin();
+    static_assert(!std::equality_comparable<decltype(iter)>);
+    auto const_iter = std::as_const(jv).begin();
+    static_assert(!std::equality_comparable<decltype(const_iter)>);
+  }
+
+  {
+    // !equality_comparable<iterator_t<range_reference_t<Base>>>;
+    using Inner = BufferView<cpp20_input_iterator<int*>, sentinel_wrapper<cpp20_input_iterator<int*>>>;
+    Inner inners[1] = {buffer[0]};
+    std::ranges::join_view jv{inners};
+    auto iter = jv.begin();
+    static_assert(!std::equality_comparable<decltype(iter)>);
+    auto const_iter = std::as_const(jv).begin();
+    static_assert(!std::equality_comparable<decltype(const_iter)>);
+  }
 
   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
index 8c3f6fc7f9861..8478db511d8dc 100644
--- 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<Base> &&
+//                     forward_range<range_reference_t<Base>>;
 
 #include <cassert>
 #include <ranges>
@@ -34,6 +36,7 @@ constexpr bool test() {
       assert(*iter++ == i);
     }
   }
+
   {
     ValueView<int> children[4] = {ValueView(buffer1[0]), ValueView(buffer1[1]), ValueView(buffer2[0]), ValueView(buffer2[1])};
     std::ranges::join_view jv(ValueView<ValueView<int>>{children});
@@ -45,12 +48,14 @@ constexpr bool test() {
 
     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 @@ constexpr bool test() {
       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 @@ constexpr bool test() {
     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 @@ constexpr bool test() {
     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 @@ constexpr bool test() {
     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 @@ constexpr bool test() {
     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 @@ constexpr bool test() {
       iter++;
     }
   }
+
   // operator++();
   {
     std::ranges::join_view jv(buffer1);
@@ -137,6 +151,7 @@ constexpr bool test() {
       assert(*++iter == i);
     }
   }
+
   {
     ValueView<int> children[4] = {ValueView(buffer1[0]), ValueView(buffer1[1]), ValueView(buffer2[0]), ValueView(buffer2[1])};
     std::ranges::join_view jv(ValueView<ValueView<int>>{children});
@@ -148,6 +163,46 @@ constexpr bool test() {
     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<decltype(iter2), iterator&>);
+    assert(&iter2 == &iter);
+
+    std::same_as<iterator> decltype(auto) iter3 = iter++;
+    assert(std::next(iter3) == iter);
+  }
+
+  {
+    // !ref-is-glvalue
+    BidiCommonInner inners[2] = {buffer1[0], buffer1[1]};
+    InnerRValue<BidiCommonOuter<BidiCommonInner>> outer{inners};
+    std::ranges::join_view jv(outer);
+    auto iter = jv.begin();
+    static_assert(std::is_void_v<decltype(iter++)>);
+  }
+
+  {
+    // !forward_­range<Base>
+    BufferView<int*> inners[2] = {buffer1[0], buffer1[1]};
+    using Outer = SimpleInputCommonOuter<BufferView<int*>>;
+    std::ranges::join_view jv{Outer(inners)};
+    auto iter = jv.begin();
+    static_assert(std::is_void_v<decltype(iter++)>);
+  }
+
+  {
+    // !forward_range<range_reference_t<Base>>
+    InputCommonInner inners[1] = {buffer1[0]};
+    std::ranges::join_view jv{inners};
+    auto iter = jv.begin();
+    static_assert(std::is_void_v<decltype(iter++)>);
+  }
+
   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
index a66b87a71312f..01a3105ab6452 100644
--- 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 <cassert>
 #include <ranges>
 
-#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<decltype(jv.begin())>())));
-
+  {
+    std::ranges::join_view jv(buffer);
+    assert(std::ranges::iter_move(jv.begin()) == 1);
+    static_assert(std::is_same_v<decltype(std::ranges::iter_move(jv.begin())), int&&>);
+
+    static_assert(noexcept(std::ranges::iter_move(std::declval<decltype(jv.begin())>())));
+  }
+
+  {
+    // 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<std::pair<int&&, int&&>> 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<std::pair<int&&, int&&>> 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
index df6bc69cb17fc..d286bd829b933 100644
--- 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 <cassert>
 #include <ranges>
 
-#include "test_macros.h"
 #include "../types.h"
 
+using NonSwappableView = BufferView<copying_iterator<int*>>;
+static_assert(std::ranges::input_range<NonSwappableView>);
+static_assert(!std::indirectly_swappable<std::ranges::iterator_t<NonSwappableView>>);
+
 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<decltype(jv)>;
+    static_assert(!std::indirectly_swappable<Iter>);
+  }
   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
index 370fa026b086b..102999d567903 100644
--- 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 @@ struct InputView : std::ranges::view_base {
   sentinel_wrapper<cpp17_input_iterator<T*>> end() const;
 };
 
+template <class T, class V>
+struct 
diff _type_iter {
+  using iterator_category = std::input_iterator_tag;
+  using value_type = V;
+  using 
diff erence_type = T;
+
+  V& operator*() const;
+  
diff _type_iter& operator++();
+  void operator++(int);
+  friend constexpr bool operator==(
diff _type_iter, 
diff _type_iter) = default;
+};
+
+template <class T, class V = int>
+struct DiffTypeRange : std::ranges::view_base {
+  
diff _type_iter<T, V> begin() const;
+  
diff _type_iter<T, V> end() const;
+};
+
 template<class T>
 concept HasIterCategory = requires { typename T::iterator_category; };
 
@@ -38,25 +56,70 @@ void test() {
     std::ranges::join_view jv(buffer);
     using Iter = std::ranges::iterator_t<decltype(jv)>;
 
-    ASSERT_SAME_TYPE(Iter::iterator_concept, std::bidirectional_iterator_tag);
-    ASSERT_SAME_TYPE(Iter::iterator_category, std::bidirectional_iterator_tag);
-    ASSERT_SAME_TYPE(Iter::
diff erence_type, std::ptr
diff _t);
-    ASSERT_SAME_TYPE(Iter::value_type, int);
+    static_assert(std::is_same_v<Iter::iterator_concept, std::bidirectional_iterator_tag>);
+    static_assert(std::is_same_v<Iter::iterator_category, std::bidirectional_iterator_tag>);
+    static_assert(std::is_same_v<Iter::
diff erence_type, std::ptr
diff _t>);
+    static_assert(std::is_same_v<Iter::value_type, int>);
+    static_assert(HasIterCategory<Iter>);
   }
+
   {
     using Iter = std::ranges::iterator_t<std::ranges::join_view<ForwardView<ForwardView<int>>>>;
 
-    ASSERT_SAME_TYPE(Iter::iterator_concept, std::forward_iterator_tag);
-    ASSERT_SAME_TYPE(Iter::iterator_category, std::forward_iterator_tag);
-    ASSERT_SAME_TYPE(Iter::
diff erence_type, std::ptr
diff _t);
-    ASSERT_SAME_TYPE(Iter::value_type, int);
+    static_assert(std::is_same_v<Iter::iterator_concept, std::forward_iterator_tag>);
+    static_assert(std::is_same_v<Iter::iterator_category, std::forward_iterator_tag>);
+    static_assert(std::is_same_v<Iter::
diff erence_type, std::ptr
diff _t>);
+    static_assert(std::is_same_v<Iter::value_type, int>);
+    static_assert(HasIterCategory<Iter>);
   }
+
   {
     using Iter = std::ranges::iterator_t<std::ranges::join_view<InputView<InputView<int>>>>;
 
-    ASSERT_SAME_TYPE(Iter::iterator_concept, std::input_iterator_tag);
+    static_assert(std::is_same_v<Iter::iterator_concept, std::input_iterator_tag>);
     static_assert(!HasIterCategory<Iter>);
-    ASSERT_SAME_TYPE(Iter::
diff erence_type, std::ptr
diff _t);
-    ASSERT_SAME_TYPE(Iter::value_type, int);
+    static_assert(std::is_same_v<Iter::
diff erence_type, std::ptr
diff _t>);
+    static_assert(std::is_same_v<Iter::value_type, int>);
+  }
+
+  {
+    // LWG3535 `join_view::iterator::iterator_category` and `::iterator_concept` lie
+    // Bidi non common inner range should not have bidirectional_iterator_tag
+    using Base = BidiCommonOuter<BidiNonCommonInner>;
+    using Iter = std::ranges::iterator_t<std::ranges::join_view<Base>>;
+    static_assert(std::is_same_v<Iter::iterator_concept, std::forward_iterator_tag>);
+    static_assert(std::is_same_v<Iter::iterator_category, std::forward_iterator_tag>);
+    static_assert(HasIterCategory<Iter>);
+    static_assert(std::is_same_v<Iter::
diff erence_type, std::ptr
diff _t>);
+    static_assert(std::is_same_v<Iter::value_type, int>);
+  }
+
+  {
+    // !ref-is-glvalue
+    using Outer = InnerRValue<BidiCommonOuter<BidiCommonInner>>;
+    using Iter = std::ranges::iterator_t<std::ranges::join_view<Outer>>;
+    static_assert(!HasIterCategory<Iter>);
+    static_assert(std::is_same_v<Iter::iterator_concept, std::input_iterator_tag>);
+  }
+
+  {
+    // value_type == inner's value_type
+    using Inner = IterMoveSwapAwareView;
+    using InnerValue = std::ranges::range_value_t<Inner>;
+    using InnerReference = std::ranges::range_reference_t<Inner>;
+    static_assert(!std::is_same_v<InnerValue, std::remove_cvref<InnerReference>>);
+
+    using Outer = BidiCommonOuter<Inner>;
+    using Iter = std::ranges::iterator_t<std::ranges::join_view<Outer>>;
+    static_assert(std::is_same_v<InnerValue, std::pair<int, int>>);
+    static_assert(std::is_same_v<Iter::value_type, std::pair<int, int>>);
+  }
+
+  {
+    // 
diff erence_type
+    using Inner = DiffTypeRange<std::intptr_t>;
+    using Outer = DiffTypeRange<std::ptr
diff _t, Inner>;
+    using Iter = std::ranges::iterator_t<std::ranges::join_view<Outer>>;
+    static_assert(std::is_same_v<Iter::
diff erence_type, std::common_type_t<std::intptr_t, std::ptr
diff _t>>);
   }
 }

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
index 806b45fae6474..ffb2e7a6f7b57 100644
--- 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<!Const> s);
+//             requires Const && convertible_­to<sentinel_t<V>, sentinel_t<Base>>;
 
 #include <cassert>
 #include <ranges>
 
-#include "test_macros.h"
 #include "../types.h"
 
+template <class T>
+struct convertible_sentinel_wrapper {
+  explicit convertible_sentinel_wrapper() = default;
+  constexpr convertible_sentinel_wrapper(const T& it) : it_(it) {}
+
+  template <class U>
+    requires std::convertible_to<const U&, T>
+  constexpr convertible_sentinel_wrapper(const convertible_sentinel_wrapper<U>& 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<BufferView<int*>*> {
+  using BufferView<BufferView<int*>*>::BufferView;
+
+  using sentinel = convertible_sentinel_wrapper<BufferView<int*>*>;
+  using const_sentinel = convertible_sentinel_wrapper<const BufferView<int*>*>;
+
+  constexpr BufferView<int*>* begin() { return data_; }
+  constexpr const BufferView<int*>* 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<ConstConveritbleView>);
+static_assert(std::convertible_to<std::ranges::sentinel_t<ConstConveritbleView>,
+                                  std::ranges::sentinel_t<ConstConveritbleView const>>);
+LIBCPP_STATIC_ASSERT(!std::ranges::__simple_view<ConstConveritbleView>);
+
 constexpr bool test() {
   int buffer[4][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}};
+  {
+    BufferView<int*> inners[] = {buffer[0], buffer[1], buffer[2]};
+    ConstConveritbleView outer(inners);
+    std::ranges::join_view jv(outer);
+    auto sent1 = jv.end();
+    std::ranges::sentinel_t<const decltype(jv)> 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<const decltype(jv)> 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<decltype(sent1), decltype(sent2)>);
+    // We cannot create a non-const sentinel from a const sentinel.
+    static_assert(!std::constructible_from<decltype(sent1), decltype(sent2)>);
+  }
 
+  {
+    // 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<int*>;
+    using ConstInconvertibleOuter =
+        BufferView<forward_iterator<const Inner*>, sentinel_wrapper<forward_iterator<const Inner*>>,
+                   bidirectional_iterator<Inner*>, sentinel_wrapper<bidirectional_iterator<Inner*>>>;
+    using JoinView = std::ranges::join_view<ConstInconvertibleOuter>;
+    using sentinel = std::ranges::sentinel_t<JoinView>;
+    using const_sentinel = std::ranges::sentinel_t<const JoinView>;
+    static_assert(!std::constructible_from<sentinel, const_sentinel>);
+    static_assert(!std::constructible_from<const_sentinel, sentinel>);
+  }
   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
index 5ba90be166e56..7f1ce0b45250c 100644
--- 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<OtherConst>& x, const sentinel& y);
 
 #include <cassert>
+#include <concepts>
 #include <ranges>
 
-#include "test_macros.h"
 #include "../types.h"
 
+template <class Iter, class Sent>
+concept EqualityComparable = std::invocable<std::equal_to<>, const Iter&, const Sent&> ;
+
+using Iterator = random_access_iterator<BufferView<int*>*>;
+using ConstIterator = random_access_iterator<const BufferView<int*>*>;
+
+template <bool Const>
+struct ConstComparableSentinel {
+
+  using Iter = std::conditional_t<Const, ConstIterator, Iterator>;
+  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<BufferView<int*>*> {
+  using BufferView<BufferView<int*>*>::BufferView;
+
+  constexpr auto begin() { return Iterator(data_); }
+  constexpr auto begin() const { return ConstIterator(data_); }
+  constexpr auto end() { return ConstComparableSentinel<false>(Iterator(data_ + size_)); }
+  constexpr auto end() const { return ConstComparableSentinel<true>(ConstIterator(data_ + size_)); }
+};
+
+static_assert(EqualityComparable<std::ranges::iterator_t<ConstComparableView>,
+                                 std::ranges::sentinel_t<const ConstComparableView>>);
+static_assert(EqualityComparable<std::ranges::iterator_t<const ConstComparableView>,
+                                 std::ranges::sentinel_t<ConstComparableView>>);
+
 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 @@ constexpr bool test() {
     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<decltype(std::as_const(jv).begin()), decltype(jv.end())>);
+    static_assert(!EqualityComparable<decltype(jv.begin()), decltype(std::as_const(jv).end())>);
   }
+
   {
-    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<Const> == sentinel<!Const>
   {
-    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<int*> 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
index 7a3dafb4aa1cd..4dd3cf7cfda43 100644
--- 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 <concepts>
+#include <tuple>
 
 #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<int*>;
+  using const_iterator = cpp20_input_iterator<const int*>;
+  using sentinel = sentinel_wrapper<iterator>;
+  using const_sentinel = sentinel_wrapper<const_iterator>;
 
-  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<int *> begin() { return cpp20_input_iterator<int *>(ptr_); }
-  constexpr cpp20_input_iterator<const int *> begin() const { return cpp20_input_iterator<const int *>(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<int*> &lhs, int* rhs) { return base(lhs) == rhs; }
-constexpr bool operator==(int* lhs, const cpp20_input_iterator<int*> &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<class T>
+template <class T>
 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<T*>;
+  using const_iterator = cpp20_input_iterator<const T*>;
+  using sentinel = sentinel_wrapper<iterator>;
+  using const_sentinel = sentinel_wrapper<const_iterator>;
+
+  constexpr ParentView(T* ptr, unsigned size = 4) : ptr_(ptr), size_(size) {}
+  constexpr ParentView(ChildView* ptr = globalChildren, unsigned size = 4)
     requires std::same_as<ChildView, T>
-    : 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<T *> begin() { return cpp20_input_iterator<T *>(ptr_); }
-  constexpr cpp20_input_iterator<const T *> begin() const { return cpp20_input_iterator<const T *>(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<class T>
-constexpr bool operator==(const cpp20_input_iterator<T*> &lhs, T *rhs) { return base(lhs) == rhs; }
-template<class T>
-constexpr bool operator==(T *lhs, const cpp20_input_iterator<T*> &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<int *> begin() { return cpp17_input_iterator<int *>(ptr_); }
-  constexpr cpp17_input_iterator<const int *> begin() const { return cpp17_input_iterator<const int *>(ptr_); }
-  constexpr int *end() { return ptr_ + size_; }
-  constexpr const int *end() const { return ptr_ + size_; }
+  using iterator = cpp17_input_iterator<int*>;
+  using const_iterator = cpp17_input_iterator<const int*>;
+  using sentinel = sentinel_wrapper<iterator>;
+  using const_sentinel = sentinel_wrapper<const_iterator>;
+
+  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<const int*> &lhs, const int* rhs) { return base(lhs) == rhs; }
-constexpr bool operator==(const int* lhs, const cpp17_input_iterator<const int*> &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<CopyableChild*>;
+  using const_iterator = cpp17_input_iterator<const CopyableChild*>;
+  using sentinel = sentinel_wrapper<iterator>;
+  using const_sentinel = sentinel_wrapper<const_iterator>;
+
+  constexpr CopyableParent(CopyableChild* ptr) : ptr_(ptr) {}
 
-  constexpr cpp17_input_iterator<CopyableChild *> begin() { return cpp17_input_iterator<CopyableChild *>(ptr_); }
-  constexpr cpp17_input_iterator<const CopyableChild *> begin() const { return cpp17_input_iterator<const CopyableChild *>(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<const CopyableChild*> &lhs, const CopyableChild *rhs) { return base(lhs) == rhs; }
-constexpr bool operator==(const CopyableChild *lhs, const cpp17_input_iterator<const CopyableChild*> &rhs) { return base(rhs) == lhs; }
 
-struct Box { int x; };
+struct Box {
+  int x;
+};
 
-template<class T>
+template <class T>
 struct InputValueIter {
   typedef std::input_iterator_tag iterator_category;
   typedef T value_type;
   typedef int 
diff erence_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<class T>
-constexpr bool operator==(const InputValueIter<T> &lhs, const T* rhs) { return lhs.ptr_ == rhs; }
-template<class T>
-constexpr bool operator==(const T* lhs, const InputValueIter<T> &rhs) { return rhs.ptr_ == lhs; }
+  constexpr friend bool operator==(const InputValueIter&, const InputValueIter&) = default;
+};
 
-template<class T>
+template <class T>
 struct ValueView : std::ranges::view_base {
   InputValueIter<T> ptr_;
 
-  constexpr ValueView(T *ptr) : ptr_(ptr) {}
+  using sentinel = sentinel_wrapper<InputValueIter<T>>;
 
-  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<T>(nullptr);
     return *this;
@@ -132,10 +153,260 @@ struct ValueView : std::ranges::view_base {
   ValueView(const ValueView&) = delete;
   ValueView& operator=(const ValueView&) = delete;
 
-  constexpr InputValueIter<T> begin() { return ptr_; }
   constexpr InputValueIter<T> 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<T>(ptr_.ptr_ + 4)); }
+};
+
+template <class Iter, class Sent = Iter, class NonConstIter = Iter, class NonConstSent = Sent>
+struct BufferView : std::ranges::view_base {
+
+  using T = std::iter_value_t<Iter>;
+  T* data_;
+  std::size_t size_;
+
+  template <std::size_t N>
+  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<Iter, NonConstIter>) {
+    return NonConstIter(this->data_);
+  }
+  constexpr Iter begin() const { return Iter(this->data_); }
+
+  constexpr NonConstSent end()
+    requires(!std::is_same_v<Sent, NonConstSent>) {
+    if constexpr (std::is_same_v<NonConstIter, NonConstSent>) {
+      return NonConstIter(this->data_ + this->size_);
+    } else {
+      return NonConstSent(NonConstIter(this->data_ + this->size_));
+    }
+  }
+
+  constexpr Sent end() const {
+    if constexpr (std::is_same_v<Iter, Sent>) {
+      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 <class Base>
+struct common_input_iterator {
+  Base it_;
+
+  using value_type = std::iter_value_t<Base>;
+  using 
diff erence_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<Base> operator*() const { return *it_; }
+
+  friend constexpr bool operator==(common_input_iterator const&, common_input_iterator const&) = default;
+};
+
+using InputCommonInner = BufferView<common_input_iterator<int*>>;
+static_assert(std::ranges::input_range<InputCommonInner>);
+static_assert(!std::ranges::forward_range<InputCommonInner>);
+static_assert(std::ranges::common_range<InputCommonInner>);
+
+using InputNonCommonInner = BufferView<common_input_iterator<int*>, sentinel_wrapper<common_input_iterator<int*>>>;
+static_assert(std::ranges::input_range<InputNonCommonInner>);
+static_assert(!std::ranges::forward_range<InputNonCommonInner>);
+static_assert(!std::ranges::common_range<InputNonCommonInner>);
+
+using ForwardCommonInner = BufferView<forward_iterator<int*>>;
+static_assert(std::ranges::forward_range<ForwardCommonInner>);
+static_assert(!std::ranges::bidirectional_range<ForwardCommonInner>);
+static_assert(std::ranges::common_range<ForwardCommonInner>);
+
+using ForwardNonCommonInner = BufferView<forward_iterator<int*>, sentinel_wrapper<forward_iterator<int*>>>;
+static_assert(std::ranges::forward_range<ForwardNonCommonInner>);
+static_assert(!std::ranges::bidirectional_range<ForwardNonCommonInner>);
+static_assert(!std::ranges::common_range<ForwardNonCommonInner>);
+
+using BidiCommonInner = BufferView<bidirectional_iterator<int*>>;
+static_assert(std::ranges::bidirectional_range<BidiCommonInner>);
+static_assert(std::ranges::common_range<BidiCommonInner>);
+
+using BidiNonCommonInner = BufferView<bidirectional_iterator<int*>, sentinel_wrapper<bidirectional_iterator<int*>>>;
+static_assert(std::ranges::bidirectional_range<BidiNonCommonInner>);
+static_assert(!std::ranges::common_range<BidiNonCommonInner>);
+
+template <class Inner = BufferView<int*>>
+using SimpleInputCommonOuter = BufferView<common_input_iterator<Inner*>>;
+static_assert(!std::ranges::forward_range<SimpleInputCommonOuter<>>);
+static_assert(!std::ranges::bidirectional_range<SimpleInputCommonOuter<>>);
+static_assert(std::ranges::common_range<SimpleInputCommonOuter<>>);
+LIBCPP_STATIC_ASSERT(std::ranges::__simple_view<SimpleInputCommonOuter<>>);
+
+template <class Inner = BufferView<int*>>
+using NonSimpleInputCommonOuter = BufferView<common_input_iterator<const Inner*>, common_input_iterator<const Inner*>,
+                                             common_input_iterator< Inner*>, common_input_iterator< Inner*>>;
+static_assert(!std::ranges::forward_range<NonSimpleInputCommonOuter<>>);
+static_assert(!std::ranges::bidirectional_range<NonSimpleInputCommonOuter<>>);
+static_assert(std::ranges::common_range<NonSimpleInputCommonOuter<>>);
+LIBCPP_STATIC_ASSERT(!std::ranges::__simple_view<NonSimpleInputCommonOuter<>>);
+
+template <class Inner = BufferView<int*>>
+using SimpleForwardCommonOuter = BufferView<forward_iterator<Inner*>>;
+static_assert(std::ranges::forward_range<SimpleForwardCommonOuter<>>);
+static_assert(!std::ranges::bidirectional_range<SimpleForwardCommonOuter<>>);
+static_assert(std::ranges::common_range<SimpleForwardCommonOuter<>>);
+LIBCPP_STATIC_ASSERT(std::ranges::__simple_view<SimpleForwardCommonOuter<>>);
+
+template <class Inner = BufferView<int*>>
+using NonSimpleForwardCommonOuter = BufferView<forward_iterator<const Inner*>, forward_iterator<const Inner*>,
+                                               forward_iterator<Inner*>, forward_iterator<Inner*>>;
+static_assert(std::ranges::forward_range<NonSimpleForwardCommonOuter<>>);
+static_assert(!std::ranges::bidirectional_range<NonSimpleForwardCommonOuter<>>);
+static_assert(std::ranges::common_range<NonSimpleForwardCommonOuter<>>);
+LIBCPP_STATIC_ASSERT(!std::ranges::__simple_view<NonSimpleForwardCommonOuter<>>);
+
+template <class Inner = BufferView<int*>>
+using SimpleForwardNonCommonOuter = BufferView<forward_iterator<Inner*>, sentinel_wrapper<forward_iterator<Inner*>>>;
+static_assert(std::ranges::forward_range<SimpleForwardNonCommonOuter<>>);
+static_assert(!std::ranges::bidirectional_range<SimpleForwardNonCommonOuter<>>);
+static_assert(!std::ranges::common_range<SimpleForwardNonCommonOuter<>>);
+LIBCPP_STATIC_ASSERT(std::ranges::__simple_view<SimpleForwardNonCommonOuter<>>);
+
+template <class Inner = BufferView<int*>>
+using NonSimpleForwardNonCommonOuter =
+    BufferView<forward_iterator<const Inner*>, sentinel_wrapper<forward_iterator<const Inner*>>,
+               forward_iterator<Inner*>, sentinel_wrapper<forward_iterator<Inner*>>>;
+static_assert(std::ranges::forward_range<NonSimpleForwardNonCommonOuter<>>);
+static_assert(!std::ranges::bidirectional_range<NonSimpleForwardNonCommonOuter<>>);
+static_assert(!std::ranges::common_range<NonSimpleForwardNonCommonOuter<>>);
+LIBCPP_STATIC_ASSERT(!std::ranges::__simple_view<NonSimpleForwardNonCommonOuter<>>);
+
+template <class Inner = BufferView<int*>>
+using BidiCommonOuter = BufferView<bidirectional_iterator<Inner*>>;
+static_assert(std::ranges::bidirectional_range<BidiCommonOuter<>>);
+static_assert(std::ranges::common_range<BidiCommonOuter<>>);
+LIBCPP_STATIC_ASSERT(std::ranges::__simple_view<BidiCommonOuter<>>);
+
+// an iterator where its operator* makes a copy of underlying operator*
+template <class It>
+struct copying_iterator {
+  It it_ = It();
+
+  using value_type = typename std::iterator_traits<It>::value_type;
+  using 
diff erence_type = typename std::iterator_traits<It>::
diff erence_type;
+  using pointer = typename std::iterator_traits<It>::pointer;
+
+  copying_iterator() requires std::default_initializable<It> = 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> {
+    --it_;
+    return *this;
+  }
+  constexpr copying_iterator operator++(int)
+    requires std::forward_iterator<It> {
+    return copying_iterator(it_++);
+  }
+  constexpr void operator++(int) { return it_++; }
+  constexpr copying_iterator operator--(int)
+    requires std::bidirectional_iterator<It> {
+    return copying_iterator(it_--);
+  }
+
+  friend constexpr bool operator==(const copying_iterator& x, const copying_iterator& y) = default;
+};
+
+template <class Outer>
+struct InnerRValue : Outer {
+
+  using iterator = copying_iterator<std::ranges::iterator_t<Outer>>;
+  using const_iterator = copying_iterator<std::ranges::iterator_t<const Outer>>;
+  using sentinel = copying_iterator<std::ranges::sentinel_t<Outer>>;
+  using const_sentinel = copying_iterator<std::ranges::sentinel_t<const Outer>>;
+
+  using Outer::Outer;
+  static_assert(std::ranges::common_range<Outer>, "non-common range is not supported yet");
+
+  constexpr iterator begin() { return Outer::begin(); }
+  constexpr const_iterator begin() const
+    requires std::ranges::range<const Outer> {
+    return Outer::begin();
+  }
+
+  constexpr auto end() { return iterator{Outer::end()}; }
+  constexpr auto end() const
+    requires std::ranges::range<const Outer> {
+    return const_iterator{Outer::end()};
+  }
+};
+static_assert(std::ranges::forward_range<InnerRValue<SimpleForwardCommonOuter<>>>);
+static_assert(!std::ranges::bidirectional_range<InnerRValue<SimpleForwardCommonOuter<>>>);
+static_assert(std::ranges::common_range<InnerRValue<SimpleForwardCommonOuter<>>>);
+LIBCPP_STATIC_ASSERT(std::ranges::__simple_view<InnerRValue<SimpleForwardCommonOuter<>>>);
+static_assert(!std::is_lvalue_reference_v<std::ranges::range_reference_t<InnerRValue<SimpleForwardCommonOuter<>>>>);
+
+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<int, int>;
+  using reference = std::pair<int&, int&>;
+  using rvalue_reference = std::pair<int&&, int&&>;
+
+  using 
diff erence_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*> {
+  int iter_move_called = 0;
+  int iter_swap_called = 0;
+  using BufferView<int*>::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<IterMoveSwapAwareView>);
 
 #endif // TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_JOIN_TYPES_H


        


More information about the libcxx-commits mailing list