[libcxx-commits] [libcxx] df324bb - [libcxx][ranges] Add `ranges::join_view`.

via libcxx-commits libcxx-commits at lists.llvm.org
Fri Aug 13 11:31:20 PDT 2021


Author: zoecarver
Date: 2021-08-13T11:31:08-07:00
New Revision: df324bba5c4cc0309ef4bc756fab4ebb6d67dfba

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

LOG: [libcxx][ranges] Add `ranges::join_view`.

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

Added: 
    libcxx/include/__ranges/join_view.h
    libcxx/test/libcxx/diagnostics/detail.headers/ranges/join_view.module.verify.cpp
    libcxx/test/std/ranges/range.adaptors/range.join.view/base.pass.cpp
    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/ctad.verify.cpp
    libcxx/test/std/ranges/range.adaptors/range.join.view/ctor.base.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.default.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/iterator/star.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.default.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/ctor.parent.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

Modified: 
    libcxx/docs/Status/RangesPaper.csv
    libcxx/include/CMakeLists.txt
    libcxx/include/__ranges/non_propagating_cache.h
    libcxx/include/module.modulemap
    libcxx/include/ranges
    libcxx/test/support/test_iterators.h
    libcxx/test/support/test_range.h

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/Status/RangesPaper.csv b/libcxx/docs/Status/RangesPaper.csv
index 77239c314f787..fcdbe8fb61dd1 100644
--- a/libcxx/docs/Status/RangesPaper.csv
+++ b/libcxx/docs/Status/RangesPaper.csv
@@ -140,7 +140,7 @@ Section,Description,Dependencies,Assignee,Complete
 `[range.transform] <http://wg21.link/range.transform>`_,`transform_view <https://llvm.org/D103056>`_,[range.all],Zoe Carver,✅
 `[range.iota] <http://wg21.link/range.iota>`_,iota_view,[range.all],Zoe Carver,✅
 `[range.take] <http://wg21.link/range.take>`_,take_view,[range.all],Zoe Carver,✅
-`[range.join] <http://wg21.link/range.join>`_,join_view,[range.all],Zoe Carver,In Progress
+`[range.join] <http://wg21.link/range.join>`_,join_view,[range.all],Zoe Carver,✅
 `[range.empty] <http://wg21.link/range.empty>`_,`empty_view <https://llvm.org/D103208>`_,[view.interface],Zoe Carver,✅
 `[range.single] <http://wg21.link/range.single>`_,single_view,[view.interface],Zoe Carver,✅
 `[range.split] <http://wg21.link/range.split>`_,split_view,[range.all],Zoe Carver,In Progress

diff  --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 914c74c7ceb60..f30580e5e8aa5 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -224,6 +224,7 @@ set(files
   __ranges/enable_borrowed_range.h
   __ranges/enable_view.h
   __ranges/iota_view.h
+  __ranges/join_view.h
   __ranges/non_propagating_cache.h
   __ranges/ref_view.h
   __ranges/reverse_view.h

diff  --git a/libcxx/include/__ranges/join_view.h b/libcxx/include/__ranges/join_view.h
new file mode 100644
index 0000000000000..44aa1d0264e6d
--- /dev/null
+++ b/libcxx/include/__ranges/join_view.h
@@ -0,0 +1,350 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+#ifndef _LIBCPP___RANGES_JOIN_VIEW_H
+#define _LIBCPP___RANGES_JOIN_VIEW_H
+
+#include <__config>
+#include <__iterator/concepts.h>
+#include <__iterator/iterator_traits.h>
+#include <__ranges/access.h>
+#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/view_interface.h>
+#include <__utility/declval.h>
+#include <__utility/forward.h>
+#include <optional>
+#include <type_traits>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if !defined(_LIBCPP_HAS_NO_RANGES)
+
+namespace ranges {
+  template<class>
+  struct __join_view_iterator_category {};
+
+  template<class _View>
+    requires is_reference_v<range_reference_t<_View>> &&
+             forward_range<_View> &&
+             forward_range<range_reference_t<_View>>
+  struct __join_view_iterator_category<_View> {
+    using _OuterC = typename iterator_traits<iterator_t<_View>>::iterator_category;
+    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>,
+      bidirectional_iterator_tag,
+      _If<
+        derived_from<_OuterC, forward_iterator_tag> && derived_from<_InnerC, forward_iterator_tag>,
+        forward_iterator_tag,
+        input_iterator_tag
+      >
+    >;
+  };
+
+  template<input_range _View>
+    requires view<_View> && input_range<range_reference_t<_View>>
+  class join_view
+    : public view_interface<join_view<_View>> {
+  private:
+    using _InnerRange = range_reference_t<_View>;
+
+    template<bool> struct __iterator;
+    template<bool> struct __sentinel;
+
+    static constexpr bool _UseCache = !is_reference_v<_InnerRange>;
+    using _Cache = _If<_UseCache, __non_propagating_cache<remove_cvref_t<_InnerRange>>, __empty_cache>;
+    [[no_unique_address]] _Cache __cache_;
+    _View __base_ = _View(); // TODO: [[no_unique_address]] makes clang crash! File a bug :)
+
+  public:
+    _LIBCPP_HIDE_FROM_ABI
+    join_view() requires default_initializable<_View> = default;
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr explicit join_view(_View __base)
+      : __base_(_VSTD::move(__base)) {}
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr _View base() const& requires copy_constructible<_View> { return __base_; }
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr _View base() && { return _VSTD::move(__base_); }
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr auto begin() {
+      constexpr bool __use_const = __simple_view<_View> &&
+                                   is_reference_v<range_reference_t<_View>>;
+      return __iterator<__use_const>{*this, ranges::begin(__base_)};
+    }
+
+    template<class _V2 = _View>
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr auto begin() const
+      requires input_range<const _V2> &&
+               is_reference_v<range_reference_t<const _V2>>
+    {
+      return __iterator<true>{*this, ranges::begin(__base_)};
+    }
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr auto end() {
+      if constexpr (forward_range<_View> &&
+                    is_reference_v<_InnerRange> &&
+                    forward_range<_InnerRange> &&
+                    common_range<_View> &&
+                    common_range<_InnerRange>)
+        return __iterator<__simple_view<_View>>{*this, ranges::end(__base_)};
+      else
+        return __sentinel<__simple_view<_View>>{*this};
+    }
+
+    template<class _V2 = _View>
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr auto end() const
+      requires input_range<const _V2> &&
+               is_reference_v<range_reference_t<const _V2>>
+    {
+      using _ConstInnerRange = range_reference_t<const _View>;
+      if constexpr (forward_range<const _View> &&
+                    is_reference_v<_ConstInnerRange> &&
+                    forward_range<_ConstInnerRange> &&
+                    common_range<const _View> &&
+                    common_range<_ConstInnerRange>) {
+        return __iterator<true>{*this, ranges::end(__base_)};
+      } else {
+        return __sentinel<true>{*this};
+      }
+    }
+  };
+
+  template<input_range _View>
+    requires view<_View> && input_range<range_reference_t<_View>>
+  template<bool _Const> struct join_view<_View>::__sentinel {
+    template<bool> friend struct __sentinel;
+
+  private:
+    using _Parent = __maybe_const<_Const, join_view>;
+    using _Base = __maybe_const<_Const, _View>;
+    sentinel_t<_Base> __end_ = sentinel_t<_Base>();
+
+  public:
+    _LIBCPP_HIDE_FROM_ABI
+    __sentinel() = default;
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr explicit __sentinel(_Parent& __parent)
+      : __end_(ranges::end(__parent.__base_)) {}
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr __sentinel(__sentinel<!_Const> __s)
+      requires _Const && convertible_to<sentinel_t<_View>, sentinel_t<_Base>>
+      : __end_(_VSTD::move(__s.__end_)) {}
+
+    template<bool _OtherConst>
+      requires sentinel_for<sentinel_t<_Base>, iterator_t<__maybe_const<_OtherConst, _View>>>
+    _LIBCPP_HIDE_FROM_ABI
+    friend constexpr bool operator==(const __iterator<_OtherConst>& __x, const __sentinel& __y) {
+      return __x.__outer_ == __y.__end_;
+    }
+  };
+
+  template<input_range _View>
+    requires view<_View> && input_range<range_reference_t<_View>>
+  template<bool _Const> struct join_view<_View>::__iterator
+    : public __join_view_iterator_category<__maybe_const<_Const, _View>> {
+
+    template<bool> friend struct __iterator;
+
+  private:
+    using _Parent = __maybe_const<_Const, join_view>;
+    using _Base = __maybe_const<_Const, _View>;
+    using _Outer = iterator_t<_Base>;
+    using _Inner = iterator_t<range_reference_t<_Base>>;
+
+    static constexpr bool __ref_is_glvalue = is_reference_v<range_reference_t<_Base>>;
+
+  public:
+    _Outer __outer_ = _Outer();
+
+  private:
+    optional<_Inner> __inner_;
+    _Parent *__parent_ = nullptr;
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr void __satisfy() {
+      for (; __outer_ != ranges::end(__parent_->__base_); ++__outer_) {
+        auto&& __inner = [&]() -> auto&& {
+          if constexpr (__ref_is_glvalue)
+            return *__outer_;
+          else
+            return __parent_->__cache_.__emplace_deref(__outer_);
+        }();
+        __inner_ = ranges::begin(__inner);
+        if (*__inner_ != ranges::end(__inner))
+          return;
+      }
+
+      if constexpr (__ref_is_glvalue)
+        __inner_.reset();
+    }
+
+  public:
+    using iterator_concept = _If<
+      __ref_is_glvalue && bidirectional_range<_Base> && bidirectional_range<range_reference_t<_Base>>,
+      bidirectional_iterator_tag,
+      _If<
+        __ref_is_glvalue && forward_range<_Base> && forward_range<range_reference_t<_Base>>,
+        forward_iterator_tag,
+        input_iterator_tag
+      >
+    >;
+
+    using value_type = range_value_t<range_reference_t<_Base>>;
+
+    using 
diff erence_type = common_type_t<
+      range_
diff erence_t<_Base>, range_
diff erence_t<range_reference_t<_Base>>>;
+
+    _LIBCPP_HIDE_FROM_ABI
+    __iterator() requires default_initializable<_Outer> = default;
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr __iterator(_Parent& __parent, _Outer __outer)
+      : __outer_(_VSTD::move(__outer))
+      , __parent_(_VSTD::addressof(__parent)) {
+      __satisfy();
+    }
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr __iterator(__iterator<!_Const> __i)
+      requires _Const &&
+               convertible_to<iterator_t<_View>, _Outer> &&
+               convertible_to<iterator_t<_InnerRange>, _Inner>
+      : __outer_(_VSTD::move(__i.__outer_))
+      , __inner_(_VSTD::move(__i.__inner_))
+      , __parent_(__i.__parent_) {}
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr decltype(auto) operator*() const {
+      return **__inner_;
+    }
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr _Inner operator->() const
+      requires __has_arrow<_Inner> && copyable<_Inner>
+    {
+      return *__inner_;
+    }
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr __iterator& operator++() {
+      auto&& __inner = [&]() -> auto&& {
+        if constexpr (__ref_is_glvalue)
+          return *__outer_;
+        else
+          return *__parent_->__cache_;
+      }();
+      if (++*__inner_ == ranges::end(__inner)) {
+        ++__outer_;
+        __satisfy();
+      }
+      return *this;
+    }
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr void operator++(int) {
+      ++*this;
+    }
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr __iterator operator++(int)
+      requires __ref_is_glvalue &&
+               forward_range<_Base> &&
+               forward_range<range_reference_t<_Base>>
+    {
+      auto __tmp = *this;
+      ++*this;
+      return __tmp;
+    }
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr __iterator& operator--()
+      requires __ref_is_glvalue &&
+               bidirectional_range<_Base> &&
+               bidirectional_range<range_reference_t<_Base>> &&
+               common_range<range_reference_t<_Base>>
+    {
+      if (__outer_ == ranges::end(__parent_->__base_))
+        __inner_ = ranges::end(*--__outer_);
+
+      // Skip empty inner ranges when going backwards.
+      while (*__inner_ == ranges::begin(*__outer_)) {
+        __inner_ = ranges::end(*--__outer_);
+      }
+
+      --*__inner_;
+      return *this;
+    }
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr __iterator operator--(int)
+      requires __ref_is_glvalue &&
+               bidirectional_range<_Base> &&
+               bidirectional_range<range_reference_t<_Base>> &&
+               common_range<range_reference_t<_Base>>
+    {
+      auto __tmp = *this;
+      --*this;
+      return __tmp;
+    }
+
+    _LIBCPP_HIDE_FROM_ABI
+    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>>>
+    {
+      return __x.__outer_ == __y.__outer_ && __x.__inner_ == __y.__inner_;
+    }
+
+    _LIBCPP_HIDE_FROM_ABI
+    friend constexpr decltype(auto) iter_move(const __iterator& __i)
+      noexcept(noexcept(ranges::iter_move(*__i.__inner_)))
+    {
+      return ranges::iter_move(*__i.__inner_);
+    }
+
+    _LIBCPP_HIDE_FROM_ABI
+    friend constexpr void iter_swap(const __iterator& __x, const __iterator& __y)
+      noexcept(noexcept(ranges::iter_swap(*__x.__inner_, *__y.__inner_)))
+      requires indirectly_swappable<_Inner>
+    {
+      return ranges::iter_swap(*__x.__inner_, *__y.__inner_);
+    }
+  };
+
+  template<class _Range>
+  explicit join_view(_Range&&) -> join_view<views::all_t<_Range>>;
+
+} // namespace ranges
+
+#undef _CONSTEXPR_TERNARY
+
+#endif // !defined(_LIBCPP_HAS_NO_RANGES)
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___RANGES_JOIN_VIEW_H

diff  --git a/libcxx/include/__ranges/non_propagating_cache.h b/libcxx/include/__ranges/non_propagating_cache.h
index 878f7070a07f4..76577f47a5ad4 100644
--- a/libcxx/include/__ranges/non_propagating_cache.h
+++ b/libcxx/include/__ranges/non_propagating_cache.h
@@ -85,6 +85,14 @@ namespace ranges {
     constexpr void __set(_Tp const& __value) { __value_.emplace(__value); }
     _LIBCPP_HIDE_FROM_ABI
     constexpr void __set(_Tp&& __value) { __value_.emplace(_VSTD::move(__value)); }
+
+    template<class _Other>
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr _Tp& __emplace_deref(const _Other& __value) {
+      __value_.reset();
+      __value_.emplace(*__value);
+      return *__value_;
+    }
   };
 
   struct __empty_cache { };

diff  --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap
index 079dff201804b..f9955a3cd0c3b 100644
--- a/libcxx/include/module.modulemap
+++ b/libcxx/include/module.modulemap
@@ -659,6 +659,7 @@ module std [system] {
       module enable_borrowed_range  { private header "__ranges/enable_borrowed_range.h" }
       module enable_view            { private header "__ranges/enable_view.h"           }
       module iota_view              { private header "__ranges/iota_view.h"             }
+      module join_view              { private header "__ranges/join_view.h"             }
       module non_propagating_cache  { private header "__ranges/non_propagating_cache.h" }
       module ref_view               { private header "__ranges/ref_view.h"              }
       module reverse_view           { private header "__ranges/reverse_view.h"          }

diff  --git a/libcxx/include/ranges b/libcxx/include/ranges
index df8d4194ffa14..014260aaee15b 100644
--- a/libcxx/include/ranges
+++ b/libcxx/include/ranges
@@ -184,6 +184,11 @@ namespace std::ranges {
 
   template<class W, class Bound>
     inline constexpr bool enable_borrowed_range<iota_view<W, Bound>> = true;
+
+  // [range.join], join view
+  template<input_range V>
+    requires view<V> && input_range<range_reference_t<V>>
+  class join_view;
 }
 
 */
@@ -207,6 +212,7 @@ namespace std::ranges {
 #include <__ranges/enable_borrowed_range.h>
 #include <__ranges/enable_view.h>
 #include <__ranges/iota_view.h>
+#include <__ranges/join_view.h>
 #include <__ranges/ref_view.h>
 #include <__ranges/reverse_view.h>
 #include <__ranges/take_view.h>

diff  --git a/libcxx/test/libcxx/diagnostics/detail.headers/ranges/join_view.module.verify.cpp b/libcxx/test/libcxx/diagnostics/detail.headers/ranges/join_view.module.verify.cpp
new file mode 100644
index 0000000000000..ff48b744d5d93
--- /dev/null
+++ b/libcxx/test/libcxx/diagnostics/detail.headers/ranges/join_view.module.verify.cpp
@@ -0,0 +1,16 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: modules-build
+
+// WARNING: This test was generated by 'generate_private_header_tests.py'
+// and should not be edited manually.
+
+// expected-error@*:* {{use of private header from outside its module: '__ranges/join_view.h'}}
+#include <__ranges/join_view.h>

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/base.pass.cpp
new file mode 100644
index 0000000000000..60cbb26f2502d
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/base.pass.cpp
@@ -0,0 +1,62 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr V base() const& requires copy_constructible<V>;
+// constexpr V base() &&;
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "types.h"
+
+constexpr bool hasLValueQualifiedBase(auto&& view) {
+    return requires { view.base(); };
+}
+
+constexpr bool test() {
+  int buffer[4][4] = {{1111, 2222, 3333, 4444}, {555, 666, 777, 888}, {99, 1010, 1111, 1212}, {13, 14, 15, 16}};
+
+  {
+    ChildView children[4] = {ChildView(buffer[0]), ChildView(buffer[1]), ChildView(buffer[2]), ChildView(buffer[3])};
+    auto jv = std::ranges::join_view(ParentView{children});
+    assert(std::move(jv).base().ptr_ == children);
+
+    static_assert(!hasLValueQualifiedBase(jv));
+    ASSERT_SAME_TYPE(decltype(std::move(jv).base()), ParentView<ChildView>);
+  }
+
+  {
+    std::ranges::join_view jv(buffer);
+    assert(jv.base().base() == buffer + 0);
+
+    static_assert(hasLValueQualifiedBase(jv));
+    ASSERT_SAME_TYPE(decltype(jv.base()), std::ranges::ref_view<int [4][4]>);
+  }
+
+  {
+    const std::ranges::join_view jv(buffer);
+    assert(jv.base().base() == buffer + 0);
+
+    static_assert(hasLValueQualifiedBase(jv));
+    ASSERT_SAME_TYPE(decltype(jv.base()), std::ranges::ref_view<int [4][4]>);
+  }
+
+  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
new file mode 100644
index 0000000000000..2441f6787f2ba
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/begin.pass.cpp
@@ -0,0 +1,97 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr auto begin();
+// constexpr auto begin() const;
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "types.h"
+
+constexpr bool test() {
+  int buffer[4][4] = {{1111, 2222, 3333, 4444}, {555, 666, 777, 888}, {99, 1010, 1111, 1212}, {13, 14, 15, 16}};
+
+  {
+    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.begin() == 1111);
+  }
+
+  {
+    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])};
+    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)};
+    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)};
+    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)};
+    auto jv = std::ranges::join_view(ParentView{children});
+    assert(*jv.begin() == 1111);
+  }
+
+  {
+    std::ranges::join_view jv(buffer);
+    assert(*jv.begin() == 1111);
+  }
+
+  {
+    const std::ranges::join_view jv(buffer);
+    assert(*jv.begin() == 1111);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

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
new file mode 100644
index 0000000000000..a81fa03e15c21
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/ctad.compile.pass.cpp
@@ -0,0 +1,72 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// template<class R>
+//   explicit join_view(R&&) -> join_view<views::all_t<R>>;
+
+#include <ranges>
+
+#include "test_iterators.h"
+
+template<class T>
+struct View : std::ranges::view_base {
+  // All friends here are defined to prevent GCC warnings.
+  friend T* begin(View&) { return nullptr; }
+  friend T* begin(View const&) { return nullptr; }
+  friend sentinel_wrapper<T*> end(View&) { return sentinel_wrapper<T*>(nullptr); }
+  friend sentinel_wrapper<T*> end(View const&) { return sentinel_wrapper<T*>(nullptr); }
+};
+
+template<class T>
+struct Range {
+  friend T* begin(Range&) { return nullptr; }
+  friend T* begin(Range const&) { return nullptr; }
+  friend sentinel_wrapper<T*> end(Range&) { return sentinel_wrapper<T*>(nullptr); }
+  friend sentinel_wrapper<T*> end(Range const&) { return sentinel_wrapper<T*>(nullptr); }
+};
+
+template<class T>
+struct BorrowedRange {
+  friend T* begin(BorrowedRange&) { return nullptr; }
+  friend T* begin(BorrowedRange const&) { return nullptr; }
+  friend sentinel_wrapper<T*> end(BorrowedRange&) { return sentinel_wrapper<T*>(nullptr); }
+  friend sentinel_wrapper<T*> end(BorrowedRange const&) { return sentinel_wrapper<T*>(nullptr); }
+};
+
+template<>
+inline constexpr bool std::ranges::enable_borrowed_range<BorrowedRange<BorrowedRange<int>>> = true;
+
+void testCTAD() {
+    View<View<int>> v;
+    Range<Range<int>> r;
+    BorrowedRange<BorrowedRange<int>> br;
+
+    static_assert(std::same_as<
+        decltype(std::ranges::join_view(v)),
+        std::ranges::join_view<View<View<int>>>
+    >);
+    static_assert(std::same_as<
+        decltype(std::ranges::join_view(r)),
+        std::ranges::join_view<std::ranges::ref_view<Range<Range<int>>>>
+    >);
+    // std::ranges::join_view(std::move(r)) invalid. RValue range must be borrowed.
+    static_assert(std::same_as<
+        decltype(std::ranges::join_view(br)),
+        std::ranges::join_view<std::ranges::ref_view<BorrowedRange<BorrowedRange<int>>>>
+    >);
+    static_assert(std::same_as<
+        decltype(std::ranges::join_view(std::move(br))),
+        std::ranges::join_view<std::ranges::subrange<BorrowedRange<int> *,
+                               sentinel_wrapper<BorrowedRange<int> *>,
+                               std::ranges::subrange_kind::unsized>>
+    >);
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/ctad.verify.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/ctad.verify.cpp
new file mode 100644
index 0000000000000..1bdd1e62eeb64
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/ctad.verify.cpp
@@ -0,0 +1,33 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// template<class R>
+//   explicit join_view(R&&) -> join_view<views::all_t<R>>;
+
+// Tests that the deduction guide is explicit.
+
+#include <ranges>
+
+#include "test_iterators.h"
+
+template<class T>
+struct Range {
+  friend T* begin(Range&) { return nullptr; }
+  friend T* begin(Range const&) { return nullptr; }
+  friend sentinel_wrapper<T*> end(Range&) { return sentinel_wrapper<T*>(nullptr); }
+  friend sentinel_wrapper<T*> end(Range const&) { return sentinel_wrapper<T*>(nullptr); }
+};
+
+void testExplicitCTAD() {
+  Range<Range<int>> r;
+  std::ranges::join_view v = r; // expected-error {{no viable constructor or deduction guide for deduction of template arguments of 'join_view'}}
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/ctor.base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/ctor.base.pass.cpp
new file mode 100644
index 0000000000000..2cdbe3b0268c4
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/ctor.base.pass.cpp
@@ -0,0 +1,49 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr explicit join_view(V base);
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "types.h"
+
+constexpr bool test() {
+  int buffer[4][4] = {{1111, 2222, 3333, 4444}, {555, 666, 777, 888}, {99, 1010, 1111, 1212}, {13, 14, 15, 16}};
+
+  {
+    ChildView children[4] = {ChildView(buffer[0]), ChildView(buffer[1]), ChildView(buffer[2]), ChildView(buffer[3])};
+    auto jv = std::ranges::join_view(ParentView{children});
+    assert(std::move(jv).base().ptr_ == children);
+  }
+
+  {
+    std::ranges::join_view jv(buffer);
+    assert(jv.base().base() == buffer + 0);
+  }
+
+  {
+    // Test explicitness.
+    static_assert( std::is_constructible_v<std::ranges::join_view<ParentView<ChildView>>, ParentView<ChildView>>);
+    static_assert(!std::is_convertible_v<std::ranges::join_view<ParentView<ChildView>>, ParentView<ChildView>>);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

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
new file mode 100644
index 0000000000000..ff93d8aa6fdf8
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/ctor.default.pass.cpp
@@ -0,0 +1,37 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// join_view() requires default_initializable<V> = default;
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "types.h"
+
+
+constexpr bool test() {
+  std::ranges::join_view<ParentView<ChildView>> jv;
+  assert(std::move(jv).base().ptr_ == globalChildren);
+
+  static_assert( std::default_initializable<std::ranges::join_view<ParentView<ChildView>>>);
+  static_assert(!std::default_initializable<std::ranges::join_view<CopyableParent>>);
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

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
new file mode 100644
index 0000000000000..33ef7a7374d3e
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/end.pass.cpp
@@ -0,0 +1,120 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr auto end();
+// constexpr auto end() const;
+
+#include <cassert>
+#include <ranges>
+#include <type_traits>
+
+#include "test_macros.h"
+#include "types.h"
+
+
+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);
+    assert(jv.end() == std::ranges::next(jv.begin(), 16));
+
+    static_assert(std::same_as<decltype(jv.end()), decltype(jv.begin())>);
+  }
+
+  // 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));
+    assert(jv.end() == std::ranges::next(jv.begin(), 16));
+
+    static_assert(!std::same_as<decltype(jv.end()), decltype(jv.begin())>);
+  }
+
+  // Const common, forward range.
+  {
+    const std::ranges::join_view jv(buffer);
+    assert(jv.end() == std::ranges::next(jv.begin(), 16));
+
+    static_assert(std::same_as<decltype(jv.end()), decltype(jv.begin())>);
+  }
+
+  // Const not common, input range.
+  {
+    static_assert(std::is_reference_v<std::ranges::range_reference_t<const CopyableParent>>);
+
+    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));
+
+    static_assert(!std::same_as<decltype(jv.end()), decltype(jv.begin())>);
+  }
+
+  // Has some empty children.
+  {
+    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.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)};
+    auto jv = std::ranges::join_view(ParentView(children));
+    assert(jv.end() == std::ranges::next(jv.begin(), 12));
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 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
new file mode 100644
index 0000000000000..e0fb8a8c6ddc1
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/general.pass.cpp
@@ -0,0 +1,51 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// General tests for join_view. This file does not test anything specifically.
+
+#include <algorithm>
+#include <cassert>
+#include <ranges>
+#include <string>
+#include <vector>
+
+#include "test_macros.h"
+#include "types.h"
+
+
+template<class R, class I>
+bool isEqual(R &r, I i) {
+  for (auto e : r)
+    if (e != *i++)
+      return false;
+  return true;
+}
+
+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);
+
+    ChildView children[4] = {ChildView(buffer[0]), ChildView(buffer[1]), ChildView(buffer[2]), ChildView(buffer[3])};
+    auto jv = std::ranges::join_view(ParentView(children));
+    assert(isEqual(jv, flattened));
+  }
+
+  {
+    std::vector<std::string> vec = {"Hello", ",", " ", "World", "!"};
+    std::string check = "Hello, World!";
+    std::ranges::join_view jv(vec);
+    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
new file mode 100644
index 0000000000000..1579f56151645
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/arrow.pass.cpp
@@ -0,0 +1,50 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr InnerIter operator->() const
+//   requires has-arrow<InnerIter> && copyable<InnerIter>;
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "../types.h"
+
+constexpr bool test() {
+  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])};
+    std::ranges::join_view jv(ValueView<ValueView<Box>>{children});
+    assert(jv.begin()->x == 1111);
+  }
+
+  {
+    std::ranges::join_view jv(buffer);
+    assert(jv.begin()->x == 1111);
+  }
+
+  {
+    const std::ranges::join_view jv(buffer);
+    assert(jv.begin()->x == 1111);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.default.pass.cpp
new file mode 100644
index 0000000000000..52bae5bb752fb
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.default.pass.cpp
@@ -0,0 +1,56 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// iterator() requires default_initializable<OuterIter> = default;
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "../types.h"
+
+template<class T>
+struct DefaultCtorParent : std::ranges::view_base {
+  T *ptr_;
+  constexpr DefaultCtorParent(T *ptr) : ptr_(ptr) {}
+
+  constexpr cpp17_input_iterator<T *> begin() { return cpp17_input_iterator<T *>(ptr_); }
+  constexpr cpp17_input_iterator<const T *> begin() const { return cpp17_input_iterator<const T *>(ptr_); }
+  constexpr T *end() { return ptr_ + 4; }
+  constexpr const T *end() const { return ptr_ + 4; }
+};
+
+template<class T>
+constexpr bool operator==(const cpp17_input_iterator<T*> &lhs, const T *rhs) { return lhs.base() == rhs; }
+template<class T>
+constexpr bool operator==(const T *lhs, const cpp17_input_iterator<T*> &rhs) { return rhs.base() == lhs; }
+
+constexpr bool test() {
+  using Base = DefaultCtorParent<ChildView>;
+  // Note, only the outer iterator is default_initializable:
+  static_assert( std::default_initializable<std::ranges::iterator_t<Base>>);
+  static_assert(!std::default_initializable<std::ranges::iterator_t<std::ranges::range_reference_t<Base>>>);
+
+  std::ranges::iterator_t<std::ranges::join_view<Base>> iter1;
+  (void) iter1;
+
+  static_assert(!std::default_initializable<std::ranges::iterator_t<std::ranges::join_view<ParentView<ChildView>>>>);
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

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
new file mode 100644
index 0000000000000..87290c4baec86
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.other.pass.cpp
@@ -0,0 +1,41 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr iterator(iterator<!Const> i);
+
+#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}};
+
+  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);
+
+  // We cannot create a non-const iterator from a const iterator.
+  static_assert(!std::constructible_from<decltype(iter1), decltype(iter2)>);
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

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
new file mode 100644
index 0000000000000..ae6ca21e72a94
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.parent.outer.pass.cpp
@@ -0,0 +1,38 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr iterator(Parent& parent, OuterIter outer);
+
+#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}};
+
+  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);
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

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
new file mode 100644
index 0000000000000..66b3749365461
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/decrement.pass.cpp
@@ -0,0 +1,74 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr iterator& operator--();
+// constexpr iterator operator--(int);
+
+#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}};
+
+  {
+    // outer == ranges::end
+    std::ranges::join_view jv(buffer);
+    auto iter = std::next(jv.begin(), 16);
+    for (int i = 16; i != 0; --i) {
+      assert(*--iter == i);
+    }
+  }
+  {
+    // outer == ranges::end
+    std::ranges::join_view jv(buffer);
+    auto iter = std::next(jv.begin(), 13);
+    for (int i = 13; i != 0; --i) {
+      assert(*--iter == i);
+    }
+  }
+  {
+    // outer != ranges::end
+    std::ranges::join_view jv(buffer);
+    auto iter = std::next(jv.begin(), 12);
+    for (int i = 12; i != 0; --i) {
+      assert(*--iter == i);
+    }
+  }
+  {
+    // outer != ranges::end
+    std::ranges::join_view jv(buffer);
+    auto iter = std::next(jv.begin());
+    for (int i = 1; i != 0; --i) {
+      assert(*--iter == i);
+    }
+  }
+  {
+    int small[2][1] = {{1}, {2}};
+    std::ranges::join_view jv(small);
+    auto iter = std::next(jv.begin(), 2);
+    for (int i = 2; i != 0; --i) {
+      assert(*--iter == i);
+    }
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

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
new file mode 100644
index 0000000000000..b76f72a453cc5
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/eq.pass.cpp
@@ -0,0 +1,41 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// friend constexpr bool operator==(const iterator& x, const iterator& y);
+
+#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);
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

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
new file mode 100644
index 0000000000000..853ed1a27f577
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/increment.pass.cpp
@@ -0,0 +1,160 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr iterator& operator++();
+// constexpr void operator++(int);
+// constexpr iterator operator++(int);
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "../types.h"
+
+constexpr bool test() {
+  // This way if we read past end we'll catch the error.
+  int buffer1[2][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}};
+  int dummy = 42;
+  (void) dummy;
+  int buffer2[2][4] = {{9, 10, 11, 12}, {13, 14, 15, 16}};
+
+  // operator++(int);
+  {
+    std::ranges::join_view jv(buffer1);
+    auto iter = jv.begin();
+    for (int i = 1; i < 9; ++i) {
+      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});
+    auto iter = jv.begin();
+    for (int i = 1; i < 17; ++i) {
+      assert(*iter == i);
+      iter++;
+    }
+
+    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);
+    auto iter = jv.begin();
+    for (int i = 1; i < 3; ++i) {
+      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)};
+    auto jv = std::ranges::join_view(ParentView(children));
+    auto iter = jv.begin();
+    assert(*iter == 1); iter++;
+    assert(*iter == 2); iter++;
+    assert(*iter == 3); iter++;
+    assert(*iter == 4); iter++;
+    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])};
+    std::ranges::join_view jv(ParentView(children, 1));
+    auto iter = jv.begin();
+    assert(*iter == 1); iter++;
+    assert(*iter == 2); iter++;
+    assert(*iter == 3); iter++;
+    assert(*iter == 4); iter++;
+    assert(iter == jv.end());
+  }
+  // Parent and child size is one.
+  {
+    CopyableChild children[1] = {CopyableChild(buffer1[0], 1)};
+    std::ranges::join_view jv(ParentView(children, 1));
+    auto iter = jv.begin();
+    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)};
+    auto jv = std::ranges::join_view(ParentView(children));
+    auto iter = jv.begin();
+    assert(*iter == 1); iter++;
+    assert(*iter == 2); iter++;
+    assert(*iter == 3); iter++;
+    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)};
+    auto jv = std::ranges::join_view(ParentView(children));
+    auto iter = jv.begin();
+    for (int i = 1; i < 13; ++i) {
+      assert(*iter == i);
+      iter++;
+    }
+  }
+  // operator++();
+  {
+    std::ranges::join_view jv(buffer1);
+    auto iter = jv.begin();
+    for (int i = 2; i < 9; ++i) {
+      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});
+    auto iter = jv.begin();
+    for (int i = 2; i < 17; ++i) {
+      assert(*++iter == i);
+    }
+
+    ASSERT_SAME_TYPE(decltype(++iter), decltype(iter)&);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

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
new file mode 100644
index 0000000000000..b3e3fd2dade71
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/iter.move.pass.cpp
@@ -0,0 +1,38 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// friend constexpr decltype(auto) iter_move(const iterator& i);
+
+#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())>())));
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

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
new file mode 100644
index 0000000000000..30d61f516df89
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/iter.swap.pass.cpp
@@ -0,0 +1,43 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// friend constexpr void iter_swap(const iterator& x, const iterator& y);
+
+#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 = std::next(jv.begin());
+  assert(*iter1 == 1);
+  assert(*iter2 == 2);
+  std::ranges::swap(iter1, iter2);
+  assert(*iter1 == 2);
+  assert(*iter2 == 1);
+
+  static_assert(noexcept(std::ranges::iter_swap(iter1, iter2)));
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

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
new file mode 100644
index 0000000000000..acf7ca17cd69b
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/member_types.compile.pass.cpp
@@ -0,0 +1,67 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// Iterator traits and member typedefs in join_view::<iterator>.
+
+#include <ranges>
+
+#include "test_iterators.h"
+#include "test_macros.h"
+#include "../types.h"
+
+template<class T>
+struct ForwardView : std::ranges::view_base {
+  friend forward_iterator<T*> begin(ForwardView&) { return forward_iterator<T*>(nullptr); }
+  friend forward_iterator<T*> begin(ForwardView const&) { return forward_iterator<T*>(nullptr); }
+  friend forward_iterator<T*> end(ForwardView&) { return forward_iterator<T*>(nullptr); }
+  friend forward_iterator<T*> end(ForwardView const&) { return forward_iterator<T*>(nullptr); }
+};
+
+template<class T>
+struct InputView : std::ranges::view_base {
+  friend cpp17_input_iterator<T*> begin(InputView&) { return cpp17_input_iterator<T*>(nullptr); }
+  friend cpp17_input_iterator<T*> begin(InputView const&) { return cpp17_input_iterator<T*>(nullptr); }
+  friend cpp17_input_iterator<T*> end(InputView&) { return cpp17_input_iterator<T*>(nullptr); }
+  friend cpp17_input_iterator<T*> end(InputView const&) { return cpp17_input_iterator<T*>(nullptr); }
+};
+
+template<class T>
+concept HasIterCategory = requires { typename T::iterator_category; };
+
+void test() {
+  {
+    int buffer[4][4];
+    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);
+  }
+  {
+    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);
+  }
+  {
+    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(!HasIterCategory<Iter>);
+    ASSERT_SAME_TYPE(Iter::
diff erence_type, std::ptr
diff _t);
+    ASSERT_SAME_TYPE(Iter::value_type, int);
+  }
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/star.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/star.pass.cpp
new file mode 100644
index 0000000000000..542c3309d59b6
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/star.pass.cpp
@@ -0,0 +1,55 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr decltype(auto) operator*() const;
+
+#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 iter = jv.begin();
+    for (int i = 1; i < 17; ++i) {
+      assert(*iter++ == i);
+    }
+  }
+  {
+    std::ranges::join_view jv(buffer);
+    auto iter = std::next(jv.begin(), 15);
+    assert(*iter++ == 16);
+    assert(iter == jv.end());
+  }
+  {
+    ChildView children[4] = {ChildView(buffer[0]), ChildView(buffer[1]), ChildView(buffer[2]), ChildView(buffer[3])};
+    auto jv = std::ranges::join_view(ParentView(children));
+    auto iter = jv.begin();
+    for (int i = 1; i < 17; ++i) {
+      assert(*iter == i);
+      ++iter;
+    }
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.default.pass.cpp
new file mode 100644
index 0000000000000..74ab5c9af5a59
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.default.pass.cpp
@@ -0,0 +1,33 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// sentinel() = default;
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "../types.h"
+
+constexpr bool test() {
+  std::ranges::sentinel_t<std::ranges::join_view<CopyableParent>> sent;
+  (void) sent;
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

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
new file mode 100644
index 0000000000000..fae2edd53fb87
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.other.pass.cpp
@@ -0,0 +1,41 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr sentinel(sentinel<!Const> s);
+
+#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}};
+
+  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)>);
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.parent.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.parent.pass.cpp
new file mode 100644
index 0000000000000..fc813dbe563de
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.parent.pass.cpp
@@ -0,0 +1,45 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr explicit sentinel(Parent& parent);
+
+#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}};
+
+  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::sentinel_t<decltype(jv)> sent(jv);
+  assert(sent == std::ranges::next(jv.begin(), 16));
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  {
+    // Test explicitness.
+    using Parent = std::ranges::join_view<ParentView<ChildView>>;
+    static_assert( std::is_constructible_v<std::ranges::sentinel_t<Parent>, Parent&>);
+    static_assert(!std::is_convertible_v<std::ranges::sentinel_t<Parent>, Parent&>);
+  }
+
+  return 0;
+}

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
new file mode 100644
index 0000000000000..b33d13ff2df1e
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/eq.pass.cpp
@@ -0,0 +1,52 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// template<bool OtherConst>
+//   requires sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>>
+// friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y);
+
+#include <cassert>
+#include <ranges>
+
+#include "test_macros.h"
+#include "../types.h"
+
+constexpr bool test() {
+  int buffer[4][4] = {{1111, 2222, 3333, 4444}, {555, 666, 777, 888}, {99, 1010, 1111, 1212}, {13, 14, 15, 16}};
+
+  {
+    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));
+  }
+  {
+    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));
+  }
+  {
+    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));
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

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
new file mode 100644
index 0000000000000..57f79a57485b3
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.view/types.h
@@ -0,0 +1,141 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_JOIN_TYPES_H
+#define TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_JOIN_TYPES_H
+
+#include <concepts>
+
+#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}};
+
+struct ChildView : std::ranges::view_base {
+  int *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 bool operator==(const cpp20_input_iterator<int*> &lhs, int* rhs) { return lhs.base() == rhs; }
+constexpr bool operator==(int* lhs, const cpp20_input_iterator<int*> &rhs) { return rhs.base() == lhs; }
+
+ChildView globalChildren[4] = {ChildView(globalBuffer[0]), ChildView(globalBuffer[1]), ChildView(globalBuffer[2]), ChildView(globalBuffer[3])};
+
+template<class T>
+struct ParentView : std::ranges::view_base {
+  T *ptr_;
+  unsigned size_;
+
+  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) {}
+  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_; }
+};
+
+template<class T>
+constexpr bool operator==(const cpp20_input_iterator<T*> &lhs, T *rhs) { return lhs.base() == rhs; }
+template<class T>
+constexpr bool operator==(T *lhs, const cpp20_input_iterator<T*> &rhs) { return rhs.base() == lhs; }
+
+struct CopyableChild : std::ranges::view_base {
+  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_; }
+};
+
+constexpr bool operator==(const cpp17_input_iterator<const int*> &lhs, const int* rhs) { return lhs.base() == rhs; }
+constexpr bool operator==(const int* lhs, const cpp17_input_iterator<const int*> &rhs) { return rhs.base() == lhs; }
+
+struct CopyableParent : std::ranges::view_base {
+  CopyableChild *ptr_;
+  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 bool operator==(const cpp17_input_iterator<const CopyableChild*> &lhs, const CopyableChild *rhs) { return lhs.base() == rhs; }
+constexpr bool operator==(const CopyableChild *lhs, const cpp17_input_iterator<const CopyableChild*> &rhs) { return rhs.base() == lhs; }
+
+struct Box { int x; };
+
+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) {}
+
+  constexpr T operator*() const { return std::move(*ptr_); }
+  constexpr void operator++(int) { ++ptr_; }
+  constexpr InputValueIter& operator++() { ++ptr_; return *this; }
+
+  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; }
+
+template<class T>
+struct ValueView : std::ranges::view_base {
+  InputValueIter<T> ptr_;
+
+  constexpr ValueView(T *ptr) : ptr_(ptr) {}
+
+  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;
+  }
+
+  ValueView(const ValueView&) = delete;
+  ValueView& operator=(const ValueView&) = delete;
+
+  constexpr InputValueIter<T> begin() { return ptr_; }
+  constexpr const InputValueIter<T> begin() const { return ptr_; }
+  constexpr T *end() { return ptr_.ptr_ + 4; }
+  constexpr const T *end() const { return ptr_.ptr_ + 4; }
+};
+
+#endif // TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_JOIN_TYPES_H

diff  --git a/libcxx/test/support/test_iterators.h b/libcxx/test/support/test_iterators.h
index 12cdd36632c12..2381fb8607b40 100644
--- a/libcxx/test/support/test_iterators.h
+++ b/libcxx/test/support/test_iterators.h
@@ -914,6 +914,13 @@ class stride_counting_iterator {
   
diff erence_type stride_displacement_ = 0;
 };
 
+template<class T, class U>
+concept sentinel_for_base = requires(U const& u) {
+  u.base();
+  requires std::input_or_output_iterator<std::remove_cvref_t<decltype(u.base())>>;
+  requires std::equality_comparable_with<T, decltype(u.base())>;
+};
+
 template <std::input_or_output_iterator I>
 class sentinel_wrapper {
 public:
@@ -927,6 +934,12 @@ class sentinel_wrapper {
   constexpr const I& base() const& { return base_; }
   constexpr I base() && { return std::move(base_); }
 
+  template<std::input_or_output_iterator I2>
+  requires sentinel_for_base<I, I2>
+  constexpr bool operator==(I2 const& other) const {
+    return base_ == other.base();
+  }
+
 private:
   I base_ = I();
 };

diff  --git a/libcxx/test/support/test_range.h b/libcxx/test/support/test_range.h
index c99e3f72e8bf9..6b279e21ce38e 100644
--- a/libcxx/test/support/test_range.h
+++ b/libcxx/test/support/test_range.h
@@ -62,4 +62,10 @@ struct test_view : std::ranges::view_base {
   sentinel end() const;
 };
 
+template<template<class...> class I, class R>
+constexpr auto make_archetype_range(R&& r) {
+  return std::ranges::subrange(I(std::ranges::begin(r)), sentinel_wrapper(std::ranges::end(r)));
+}
+
+
 #endif // LIBCXX_TEST_SUPPORT_TEST_RANGE_H


        


More information about the libcxx-commits mailing list