[libcxx-commits] [libcxx] [libc++] P2770R0: "Stashing stashing iterators for proper flattening" (PR #66033)

Jakub Mazurkiewicz via libcxx-commits libcxx-commits at lists.llvm.org
Tue Oct 24 12:14:30 PDT 2023


https://github.com/JMazurkiewicz updated https://github.com/llvm/llvm-project/pull/66033

>From e9e3f3afa1687407b5cae30a1a30cabe7d15bf58 Mon Sep 17 00:00:00 2001
From: Jakub Mazurkiewicz <mazkuba3 at gmail.com>
Date: Fri, 8 Sep 2023 18:20:59 +0200
Subject: [PATCH] [libc++] P2770R0: "Stashing stashing iterators for proper
 flattening"

* Parially implements P2770R0: "Stashing stashing iterators for proper flattening"
* `join_with_view` hasn't been done yet since this type isn't implemented yet
* Rename `test/libcxx/ranges/range.adaptors/range.adaptor.tuple` directory to `test/libcxx/ranges/range.adaptors/range.adaptor.helpers` to match the standard: http://eel.is/c++draft/range.adaptor.helpers (this change happened in P2770R0, see point 3 of wording).
* Rename `libcxx\test\std\ranges\range.adaptors\range.join.view` to `libcxx\test\std\ranges\range.adaptors\range.join` to match the standard
---
 libcxx/docs/Status/Cxx23.rst                  |   1 +
 libcxx/docs/Status/Cxx23Papers.csv            |   2 +-
 libcxx/docs/UsingLibcxx.rst                   |   1 -
 libcxx/include/CMakeLists.txt                 |   1 +
 libcxx/include/__ranges/join_view.h           | 131 +++++++++++-------
 libcxx/include/__utility/as_lvalue.h          |  39 ++++++
 libcxx/include/module.modulemap.in            |   1 +
 libcxx/include/regex                          |   8 ++
 libcxx/include/utility                        |   1 +
 libcxx/modules/std/ranges.inc                 |   2 -
 .../as-lvalue.verify.cpp                      |  48 +++++++
 .../tuple-for-each.pass.cpp                   |   0
 .../segmented_iterator.compile.pass.cpp       |   1 -
 .../alg.copy/ranges.copy.segmented.pass.cpp   |   2 -
 .../alg.copy/ranges.copy_backward.pass.cpp    |   2 -
 .../iterator/ctor.parent.outer.pass.cpp       |  57 --------
 .../adaptor.pass.cpp                          |   1 -
 .../base.pass.cpp                             |   1 -
 .../begin.pass.cpp                            |  31 ++++-
 .../ctad.compile.pass.cpp                     |   1 -
 .../ctad.verify.cpp                           |   1 -
 .../ctor.default.pass.cpp                     |   1 -
 .../ctor.view.pass.cpp                        |   1 -
 .../end.pass.cpp                              |  22 +--
 .../general.pass.cpp                          |   1 -
 .../iterator/arrow.pass.cpp                   |   1 -
 .../iterator/ctor.default.pass.cpp            |  16 +--
 .../iterator/ctor.other.pass.cpp              |   3 +-
 .../iterator/decrement.pass.cpp               |  21 ++-
 .../iterator/eq.pass.cpp                      |   7 +-
 .../iterator/increment.pass.cpp               |  18 ++-
 .../iterator/iter.move.pass.cpp               |   1 -
 .../iterator/iter.swap.pass.cpp               |   1 -
 .../iterator/member_types.compile.pass.cpp    |   1 -
 .../iterator/star.pass.cpp                    |   1 -
 .../sentinel/ctor.default.pass.cpp            |   1 -
 .../sentinel/ctor.other.pass.cpp              |   1 -
 .../sentinel/ctor.parent.pass.cpp             |   1 -
 .../sentinel/eq.pass.cpp                      |  19 ++-
 .../{range.join.view => range.join}/types.h   |  66 ++++++++-
 ...rator_concept_conformance.compile.pass.cpp |   4 +-
 .../std/re/re.iter/re.regiter/types.pass.cpp  |   4 +
 ...rator_concept_conformance.compile.pass.cpp |   4 +-
 .../std/re/re.iter/re.tokiter/types.pass.cpp  |   4 +
 libcxx/utils/data/ignore_format.txt           |  46 +++---
 45 files changed, 372 insertions(+), 205 deletions(-)
 create mode 100644 libcxx/include/__utility/as_lvalue.h
 create mode 100644 libcxx/test/libcxx/ranges/range.adaptors/range.adaptor.helpers/as-lvalue.verify.cpp
 rename libcxx/test/libcxx/ranges/range.adaptors/{range.adaptor.tuple => range.adaptor.helpers}/tuple-for-each.pass.cpp (100%)
 delete mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.parent.outer.pass.cpp
 rename libcxx/test/std/ranges/range.adaptors/{range.join.view => range.join}/adaptor.pass.cpp (99%)
 rename libcxx/test/std/ranges/range.adaptors/{range.join.view => range.join}/base.pass.cpp (98%)
 rename libcxx/test/std/ranges/range.adaptors/{range.join.view => range.join}/begin.pass.cpp (83%)
 rename libcxx/test/std/ranges/range.adaptors/{range.join.view => range.join}/ctad.compile.pass.cpp (98%)
 rename libcxx/test/std/ranges/range.adaptors/{range.join.view => range.join}/ctad.verify.cpp (97%)
 rename libcxx/test/std/ranges/range.adaptors/{range.join.view => range.join}/ctor.default.pass.cpp (97%)
 rename libcxx/test/std/ranges/range.adaptors/{range.join.view => range.join}/ctor.view.pass.cpp (97%)
 rename libcxx/test/std/ranges/range.adaptors/{range.join.view => range.join}/end.pass.cpp (95%)
 rename libcxx/test/std/ranges/range.adaptors/{range.join.view => range.join}/general.pass.cpp (98%)
 rename libcxx/test/std/ranges/range.adaptors/{range.join.view => range.join}/iterator/arrow.pass.cpp (99%)
 rename libcxx/test/std/ranges/range.adaptors/{range.join.view => range.join}/iterator/ctor.default.pass.cpp (70%)
 rename libcxx/test/std/ranges/range.adaptors/{range.join.view => range.join}/iterator/ctor.other.pass.cpp (97%)
 rename libcxx/test/std/ranges/range.adaptors/{range.join.view => range.join}/iterator/decrement.pass.cpp (85%)
 rename libcxx/test/std/ranges/range.adaptors/{range.join.view => range.join}/iterator/eq.pass.cpp (89%)
 rename libcxx/test/std/ranges/range.adaptors/{range.join.view => range.join}/iterator/increment.pass.cpp (94%)
 rename libcxx/test/std/ranges/range.adaptors/{range.join.view => range.join}/iterator/iter.move.pass.cpp (98%)
 rename libcxx/test/std/ranges/range.adaptors/{range.join.view => range.join}/iterator/iter.swap.pass.cpp (98%)
 rename libcxx/test/std/ranges/range.adaptors/{range.join.view => range.join}/iterator/member_types.compile.pass.cpp (99%)
 rename libcxx/test/std/ranges/range.adaptors/{range.join.view => range.join}/iterator/star.pass.cpp (97%)
 rename libcxx/test/std/ranges/range.adaptors/{range.join.view => range.join}/sentinel/ctor.default.pass.cpp (95%)
 rename libcxx/test/std/ranges/range.adaptors/{range.join.view => range.join}/sentinel/ctor.other.pass.cpp (99%)
 rename libcxx/test/std/ranges/range.adaptors/{range.join.view => range.join}/sentinel/ctor.parent.pass.cpp (97%)
 rename libcxx/test/std/ranges/range.adaptors/{range.join.view => range.join}/sentinel/eq.pass.cpp (81%)
 rename libcxx/test/std/ranges/range.adaptors/{range.join.view => range.join}/types.h (88%)

diff --git a/libcxx/docs/Status/Cxx23.rst b/libcxx/docs/Status/Cxx23.rst
index 839640a7c7e881b..3e6e33f08c7ccfa 100644
--- a/libcxx/docs/Status/Cxx23.rst
+++ b/libcxx/docs/Status/Cxx23.rst
@@ -45,6 +45,7 @@ Paper Status
       clang doesn't issue a diagnostic for deprecated using template declarations.
    .. [#note-P2520R0] P2520R0: Libc++ implemented this paper as a DR in C++20 as well.
    .. [#note-P2711R1] P2711R1: ``join_with_view`` hasn't been done yet since this type isn't implemented yet.
+   .. [#note-P2770R0] P2770R0: ``join_with_view`` hasn't been done yet since this type isn't implemented yet.
    .. [#note-P2693R1] P2693R1: The formatter for ``std::thread::id`` is implemented.
       The formatter for ``stacktrace`` is not implemented, since ``stacktrace`` is
       not implemented yet.
diff --git a/libcxx/docs/Status/Cxx23Papers.csv b/libcxx/docs/Status/Cxx23Papers.csv
index 19b1dd8eb5a446f..38b77d42ec5b977 100644
--- a/libcxx/docs/Status/Cxx23Papers.csv
+++ b/libcxx/docs/Status/Cxx23Papers.csv
@@ -104,7 +104,7 @@
 "`P2708R1 <https://wg21.link/P2708R1>`__","LWG", "No Further Fundamentals TSes", "November 2022","|Nothing to do|","",""
 "","","","","","",""
 "`P0290R4 <https://wg21.link/P0290R4>`__","LWG", "``apply()`` for ``synchronized_value<T>``","February 2023","","","|concurrency TS|"
-"`P2770R0 <https://wg21.link/P2770R0>`__","LWG", "Stashing stashing ``iterators`` for proper flattening","February 2023","|In Progress|","","|ranges|"
+"`P2770R0 <https://wg21.link/P2770R0>`__","LWG", "Stashing stashing ``iterators`` for proper flattening","February 2023","|Partial| [#note-P2770R0]_","","|ranges|"
 "`P2164R9 <https://wg21.link/P2164R9>`__","LWG", "``views::enumerate``","February 2023","","","|ranges|"
 "`P2711R1 <https://wg21.link/P2711R1>`__","LWG", "Making multi-param constructors of ``views`` ``explicit``","February 2023","|In Progress| [#note-P2711R1]_","","|ranges|"
 "`P2609R3 <https://wg21.link/P2609R3>`__","LWG", "Relaxing Ranges Just A Smidge","February 2023","","","|ranges|"
diff --git a/libcxx/docs/UsingLibcxx.rst b/libcxx/docs/UsingLibcxx.rst
index 52c76f3b10548fb..2509f36db67aea9 100644
--- a/libcxx/docs/UsingLibcxx.rst
+++ b/libcxx/docs/UsingLibcxx.rst
@@ -50,7 +50,6 @@ when ``-fexperimental-library`` is passed:
 * ``std::stop_token``, ``std::stop_source`` and ``std::stop_callback``
 * ``std::jthread``
 * ``std::chrono::tzdb`` and related time zone functionality
-* ``std::ranges::join_view``
 
 .. warning::
   Experimental libraries are experimental.
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 1f36ff6b3896e46..b27816d535cf586 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -837,6 +837,7 @@ set(files
   __type_traits/void_t.h
   __undef_macros
   __utility/as_const.h
+  __utility/as_lvalue.h
   __utility/auto_cast.h
   __utility/cmp.h
   __utility/convert_to_integral.h
diff --git a/libcxx/include/__ranges/join_view.h b/libcxx/include/__ranges/join_view.h
index e6240dfd2580dc7..dd8a8dc627eaed6 100644
--- a/libcxx/include/__ranges/join_view.h
+++ b/libcxx/include/__ranges/join_view.h
@@ -32,6 +32,7 @@
 #include <__ranges/view_interface.h>
 #include <__type_traits/common_type.h>
 #include <__type_traits/maybe_const.h>
+#include <__utility/as_lvalue.h>
 #include <__utility/forward.h>
 #include <optional>
 
@@ -41,10 +42,7 @@
 
 _LIBCPP_BEGIN_NAMESPACE_STD
 
-// Note: `join_view` is still marked experimental because there is an ABI-breaking change that affects `join_view` in
-// the pipeline (https://isocpp.org/files/papers/D2770R0.html).
-// TODO: make `join_view` non-experimental once D2770 is implemented.
-#if _LIBCPP_STD_VER >= 20 && defined(_LIBCPP_ENABLE_EXPERIMENTAL)
+#if _LIBCPP_STD_VER >= 20
 
 namespace ranges {
   template<class>
@@ -84,11 +82,16 @@ namespace ranges {
     template <class>
     friend struct std::__segmented_iterator_traits;
 
-    static constexpr bool _UseCache = !is_reference_v<_InnerRange>;
-    using _Cache = _If<_UseCache, __non_propagating_cache<remove_cvref_t<_InnerRange>>, __empty_cache>;
-    _LIBCPP_NO_UNIQUE_ADDRESS _Cache __cache_;
     _LIBCPP_NO_UNIQUE_ADDRESS _View __base_ = _View();
 
+    static constexpr bool _UseOuterCache = !forward_range<_View>;
+    using _OuterCache = _If<_UseOuterCache, __non_propagating_cache<iterator_t<_View>>, __empty_cache>;
+    _LIBCPP_NO_UNIQUE_ADDRESS _OuterCache __outer_;
+
+    static constexpr bool _UseInnerCache = !is_reference_v<_InnerRange>;
+    using _InnerCache = _If<_UseInnerCache, __non_propagating_cache<remove_cvref_t<_InnerRange>>, __empty_cache>;
+    _LIBCPP_NO_UNIQUE_ADDRESS _InnerCache __inner_;
+
   public:
     _LIBCPP_HIDE_FROM_ABI
     join_view() requires default_initializable<_View> = default;
@@ -105,16 +108,22 @@ namespace ranges {
 
     _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_)};
+      if constexpr (forward_range<_View>) {
+        constexpr bool __use_const = __simple_view<_View> &&
+                                     is_reference_v<range_reference_t<_View>>;
+        return __iterator<__use_const>{*this, ranges::begin(__base_)};
+      } else {
+        __outer_.__emplace(ranges::begin(__base_));
+        return __iterator<false>{*this};
+      }
     }
 
     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>>
+      requires forward_range<const _V2> &&
+               is_reference_v<range_reference_t<const _V2>> &&
+               input_range<range_reference_t<const _V2>>
     {
       return __iterator<true>{*this, ranges::begin(__base_)};
     }
@@ -134,13 +143,12 @@ namespace ranges {
     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>>
+      requires forward_range<const _V2> &&
+               is_reference_v<range_reference_t<const _V2>> &&
+               input_range<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> &&
+      if constexpr (forward_range<_ConstInnerRange> &&
                     common_range<const _View> &&
                     common_range<_ConstInnerRange>) {
         return __iterator<true>{*this, ranges::end(__base_)};
@@ -154,11 +162,10 @@ namespace ranges {
     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<_View>>;
+    friend join_view;
+
+    using _Parent = __maybe_const<_Const, join_view>;
     using _Base = __maybe_const<_Const, _View>;
     sentinel_t<_Base> __end_ = sentinel_t<_Base>();
 
@@ -179,7 +186,7 @@ namespace ranges {
       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_;
+      return __x.__get_outer() == __y.__end_;
     }
   };
 
@@ -191,9 +198,7 @@ namespace ranges {
   template<bool _Const>
   struct join_view<_View>::__iterator final
     : public __join_view_iterator_category<__maybe_const<_Const, _View>> {
-
-    template<bool>
-    friend struct __iterator;
+    friend join_view;
 
     template <class>
     friend struct std::__segmented_iterator_traits;
@@ -209,8 +214,11 @@ namespace ranges {
 
     static constexpr bool __ref_is_glvalue = is_reference_v<range_reference_t<_Base>>;
 
+    static constexpr bool _OuterPresent = forward_range<_Base>;
+    using _OuterType                    = _If<_OuterPresent, _Outer, __empty_cache>; // TODO Use `__empty` from D154238
+
   public:
-    _Outer __outer_ = _Outer();
+    _LIBCPP_NO_UNIQUE_ADDRESS _OuterType __outer_ = _OuterType();
 
   private:
     optional<_Inner> __inner_;
@@ -218,12 +226,12 @@ namespace ranges {
 
     _LIBCPP_HIDE_FROM_ABI
     constexpr void __satisfy() {
-      for (; __outer_ != ranges::end(__parent_->__base_); ++__outer_) {
-        auto&& __inner = [&]() -> auto&& {
+      for (; __get_outer() != ranges::end(__parent_->__base_); ++__get_outer()) {
+        auto&& __inner = [this]() -> auto&& {
           if constexpr (__ref_is_glvalue)
-            return *__outer_;
+            return *__get_outer();
           else
-            return __parent_->__cache_.__emplace_from([&]() -> decltype(auto) { return *__outer_; });
+            return __parent_->__inner_.__emplace_from([&]() -> decltype(auto) { return *__get_outer(); });
         }();
         __inner_ = ranges::begin(__inner);
         if (*__inner_ != ranges::end(__inner))
@@ -234,8 +242,37 @@ namespace ranges {
         __inner_.reset();
     }
 
+    _LIBCPP_HIDE_FROM_ABI constexpr _Outer& __get_outer() {
+      if constexpr (forward_range<_Base>) {
+        return __outer_;
+      } else {
+        return *__parent_->__outer_;
+      }
+    }
+
+    _LIBCPP_HIDE_FROM_ABI constexpr const _Outer& __get_outer() const {
+      if constexpr (forward_range<_Base>) {
+        return __outer_;
+      } else {
+        return *__parent_->__outer_;
+      }
+    }
+
+    _LIBCPP_HIDE_FROM_ABI constexpr __iterator(_Parent& __parent, _Outer __outer)
+      requires forward_range<_Base>
+        : __outer_(std::move(__outer)), __parent_(std::addressof(__parent)) {
+      __satisfy();
+    }
+
+    _LIBCPP_HIDE_FROM_ABI constexpr __iterator(_Parent& __parent)
+      requires(!forward_range<_Base>)
+        : __parent_(std::addressof(__parent)) {
+      __satisfy();
+    }
+
     _LIBCPP_HIDE_FROM_ABI constexpr __iterator(_Parent* __parent, _Outer __outer, _Inner __inner)
-      : __outer_(std::move(__outer)), __inner_(std::move(__inner)), __parent_(__parent) {}
+      requires forward_range<_Base>
+        : __outer_(std::move(__outer)), __inner_(std::move(__inner)), __parent_(__parent) {}
 
   public:
     using iterator_concept = _If<
@@ -254,15 +291,7 @@ namespace ranges {
     using difference_type = common_type_t<
       range_difference_t<_Base>, range_difference_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_(std::move(__outer))
-      , __parent_(std::addressof(__parent)) {
-      __satisfy();
-    }
+    _LIBCPP_HIDE_FROM_ABI __iterator() = default;
 
     _LIBCPP_HIDE_FROM_ABI
     constexpr __iterator(__iterator<!_Const> __i)
@@ -287,14 +316,14 @@ namespace ranges {
 
     _LIBCPP_HIDE_FROM_ABI
     constexpr __iterator& operator++() {
-      auto&& __inner = [&]() -> auto&& {
+      auto __get_inner_range = [&]() -> decltype(auto) {
         if constexpr (__ref_is_glvalue)
-          return *__outer_;
+          return *__get_outer();
         else
-          return *__parent_->__cache_;
-      }();
-      if (++*__inner_ == ranges::end(__inner)) {
-        ++__outer_;
+          return *__parent_->__inner_;
+      };
+      if (++*__inner_ == ranges::end(ranges::__as_lvalue(__get_inner_range()))) {
+        ++__get_outer();
         __satisfy();
       }
       return *this;
@@ -324,11 +353,11 @@ namespace ranges {
                common_range<range_reference_t<_Base>>
     {
       if (__outer_ == ranges::end(__parent_->__base_))
-        __inner_ = ranges::end(*--__outer_);
+        __inner_ = ranges::end(ranges::__as_lvalue(*--__outer_));
 
       // Skip empty inner ranges when going backwards.
-      while (*__inner_ == ranges::begin(*__outer_)) {
-        __inner_ = ranges::end(*--__outer_);
+      while (*__inner_ == ranges::begin(ranges::__as_lvalue(*__outer_))) {
+        __inner_ = ranges::end(ranges::__as_lvalue(*--__outer_));
       }
 
       --*__inner_;
@@ -350,7 +379,7 @@ namespace ranges {
     _LIBCPP_HIDE_FROM_ABI
     friend constexpr bool operator==(const __iterator& __x, const __iterator& __y)
       requires __ref_is_glvalue &&
-               equality_comparable<iterator_t<_Base>> &&
+               forward_range<_Base> &&
                equality_comparable<iterator_t<range_reference_t<_Base>>>
     {
       return __x.__outer_ == __y.__outer_ && __x.__inner_ == __y.__inner_;
@@ -436,7 +465,7 @@ struct __segmented_iterator_traits<_JoinViewIterator> {
   }
 };
 
-#endif // #if _LIBCPP_STD_VER >= 20 && defined(_LIBCPP_ENABLE_EXPERIMENTAL)
+#endif // #if _LIBCPP_STD_VER >= 20
 
 _LIBCPP_END_NAMESPACE_STD
 
diff --git a/libcxx/include/__utility/as_lvalue.h b/libcxx/include/__utility/as_lvalue.h
new file mode 100644
index 000000000000000..b4df650edbf65ef
--- /dev/null
+++ b/libcxx/include/__utility/as_lvalue.h
@@ -0,0 +1,39 @@
+// -*- 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___UTILITY_AS_LVALUE_H
+#define _LIBCPP___UTILITY_AS_LVALUE_H
+
+#include <__config>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER >= 20
+
+namespace ranges {
+template <class _Tp>
+_LIBCPP_HIDE_FROM_ABI constexpr _Tp& __as_lvalue(_LIBCPP_LIFETIMEBOUND _Tp&& __t) {
+  return static_cast<_Tp&>(__t);
+}
+} // namespace ranges
+
+#endif // _LIBCPP_STD_VER >= 20
+
+_LIBCPP_END_NAMESPACE_STD
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___UTILITY_AS_LVALUE_H
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 41158212c9a86cf..efbca92e3b272d7 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -2037,6 +2037,7 @@ module std_private_type_traits_unwrap_ref                                [system
 module std_private_type_traits_void_t                                    [system] { header "__type_traits/void_t.h" }
 
 module std_private_utility_as_const               [system] { header "__utility/as_const.h" }
+module std_private_utility_as_lvalue              [system] { header "__utility/as_lvalue.h" }
 module std_private_utility_auto_cast              [system] {
   header "__utility/auto_cast.h"
   export std_private_type_traits_decay
diff --git a/libcxx/include/regex b/libcxx/include/regex
index e8865ac1089d6f4..59d3af2a4bcb32a 100644
--- a/libcxx/include/regex
+++ b/libcxx/include/regex
@@ -697,6 +697,7 @@ public:
     typedef const value_type*                    pointer;
     typedef const value_type&                    reference;
     typedef forward_iterator_tag                 iterator_category;
+    typedef input_iterator_tag                   iterator_concept; // since C++20
 
     regex_iterator();
     regex_iterator(BidirectionalIterator a, BidirectionalIterator b,
@@ -737,6 +738,7 @@ public:
     typedef const value_type*                pointer;
     typedef const value_type&                reference;
     typedef forward_iterator_tag             iterator_category;
+    typedef input_iterator_tag               iterator_concept; // since C++20
 
     regex_token_iterator();
     regex_token_iterator(BidirectionalIterator a, BidirectionalIterator b,
@@ -6407,6 +6409,9 @@ public:
     typedef const value_type*                     pointer;
     typedef const value_type&                     reference;
     typedef forward_iterator_tag                  iterator_category;
+#if _LIBCPP_STD_VER >= 20
+    typedef input_iterator_tag                    iterator_concept;
+#endif
 
 private:
     _BidirectionalIterator           __begin_;
@@ -6542,6 +6547,9 @@ public:
     typedef const value_type*                 pointer;
     typedef const value_type&                 reference;
     typedef forward_iterator_tag              iterator_category;
+#if _LIBCPP_STD_VER >= 20
+    typedef input_iterator_tag                iterator_concept;
+#endif
 
 private:
     typedef regex_iterator<_BidirectionalIterator, _CharT, _Traits> _Position;
diff --git a/libcxx/include/utility b/libcxx/include/utility
index c5581d55e79bbb3..1deef3db204107f 100644
--- a/libcxx/include/utility
+++ b/libcxx/include/utility
@@ -249,6 +249,7 @@ template <class T>
 #include <__assert> // all public C++ headers provide the assertion handler
 #include <__config>
 #include <__utility/as_const.h>
+#include <__utility/as_lvalue.h>
 #include <__utility/auto_cast.h>
 #include <__utility/cmp.h>
 #include <__utility/declval.h>
diff --git a/libcxx/modules/std/ranges.inc b/libcxx/modules/std/ranges.inc
index a883103d812588b..82c7d99f8979a82 100644
--- a/libcxx/modules/std/ranges.inc
+++ b/libcxx/modules/std/ranges.inc
@@ -204,13 +204,11 @@ export namespace std {
       using std::ranges::views::drop_while;
     } // namespace views
 
-#ifdef _LIBCPP_ENABLE_EXPERIMENTAL
     using std::ranges::join_view;
 
     namespace views {
       using std::ranges::views::join;
     } // namespace views
-#endif // _LIBCPP_ENABLE_EXPERIMENTAL
 #if 0
     using std::ranges::join_with_view;
 
diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.adaptor.helpers/as-lvalue.verify.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.adaptor.helpers/as-lvalue.verify.cpp
new file mode 100644
index 000000000000000..878ef91b13839d2
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.adaptor.helpers/as-lvalue.verify.cpp
@@ -0,0 +1,48 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// <ranges>
+
+// template<class T>
+// constexpr T& as-lvalue(T&& t) { // exposition only
+
+#include <ranges>
+
+#include <concepts>
+#include <utility>
+
+constexpr bool test() {
+  // Check glvalue
+  {
+    int lvalue{};
+    [[maybe_unused]] std::same_as<int&> decltype(auto) check = std::ranges::__as_lvalue(lvalue);
+  }
+
+  // Check prvalue
+  {
+    [[maybe_unused]] std::same_as<int&> decltype(auto) check = std::ranges::__as_lvalue(
+        0); // expected-warning {{temporary bound to local reference 'check' will be destroyed at the end of the full-expression}}
+  }
+
+  // Check xvalue
+  {
+    int xvalue{};
+    [[maybe_unused]] std::same_as<int&> decltype(auto) check = std::ranges::__as_lvalue(std::move(xvalue));
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.adaptor.tuple/tuple-for-each.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.adaptor.helpers/tuple-for-each.pass.cpp
similarity index 100%
rename from libcxx/test/libcxx/ranges/range.adaptors/range.adaptor.tuple/tuple-for-each.pass.cpp
rename to libcxx/test/libcxx/ranges/range.adaptors/range.adaptor.helpers/tuple-for-each.pass.cpp
diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.join/segmented_iterator.compile.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.join/segmented_iterator.compile.pass.cpp
index 82e8cab503a2750..6cd17c2b3f3533b 100644
--- a/libcxx/test/libcxx/ranges/range.adaptors/range.join/segmented_iterator.compile.pass.cpp
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.join/segmented_iterator.compile.pass.cpp
@@ -7,7 +7,6 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: !c++experimental
 
 #include <ranges>
 #include <utility>
diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy.segmented.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy.segmented.pass.cpp
index abb9157df9abbbb..0a7d572412431a1 100644
--- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy.segmented.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy.segmented.pass.cpp
@@ -7,8 +7,6 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// TODO: make `join_view` non-experimental once D2770 is implemented.
-// UNSUPPORTED: !c++experimental
 
 #include <algorithm>
 #include <array>
diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_backward.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_backward.pass.cpp
index 184008c3e2fd0ff..301fbcbc533c260 100644
--- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_backward.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_backward.pass.cpp
@@ -9,8 +9,6 @@
 // <algorithm>
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// TODO: make `join_view` non-experimental once D2770 is implemented.
-// UNSUPPORTED: !c++experimental
 // UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME
 
 // template<bidirectional_iterator I1, sentinel_for<I1> S1, bidirectional_iterator I2>
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
deleted file mode 100644
index 215318f15cad0db..000000000000000
--- a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.parent.outer.pass.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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: !c++experimental
-
-// constexpr iterator(Parent& parent, OuterIter outer);
-
-#include <cassert>
-#include <ranges>
-
-#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);
-  }
-
-  {
-    // 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;
-}
-
-int main(int, char**) {
-  test();
-  static_assert(test());
-
-  return 0;
-}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/adaptor.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join/adaptor.pass.cpp
similarity index 99%
rename from libcxx/test/std/ranges/range.adaptors/range.join.view/adaptor.pass.cpp
rename to libcxx/test/std/ranges/range.adaptors/range.join/adaptor.pass.cpp
index afaf32272109963..9beb3d282a27cc9 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.join.view/adaptor.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.join/adaptor.pass.cpp
@@ -7,7 +7,6 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: !c++experimental
 
 // std::views::join
 
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join/base.pass.cpp
similarity index 98%
rename from libcxx/test/std/ranges/range.adaptors/range.join.view/base.pass.cpp
rename to libcxx/test/std/ranges/range.adaptors/range.join/base.pass.cpp
index 13883e894ac7bf9..caf018b582263a5 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.join.view/base.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.join/base.pass.cpp
@@ -7,7 +7,6 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: !c++experimental
 
 // constexpr V base() const& requires copy_constructible<V>;
 // constexpr V base() &&;
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/begin.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join/begin.pass.cpp
similarity index 83%
rename from libcxx/test/std/ranges/range.adaptors/range.join.view/begin.pass.cpp
rename to libcxx/test/std/ranges/range.adaptors/range.join/begin.pass.cpp
index 9e4fa5f8c59a44a..005d0d1d2d5cb75 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.join.view/begin.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.join/begin.pass.cpp
@@ -7,15 +7,17 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: !c++experimental
 
 // constexpr auto begin();
 // constexpr auto begin() const
-//    requires input_range<const V> &&
-//             is_reference_v<range_reference_t<const V>>;
+//    requires forward_range<const V> &&
+//             is_reference_v<range_reference_t<const V>> &&
+//             input_range<range_reference_t<const V>>;
 
+#include <algorithm>
 #include <cassert>
 #include <ranges>
+#include <string_view>
 
 #include "types.h"
 
@@ -120,7 +122,7 @@ constexpr bool test() {
     static_assert(HasConstBegin<decltype(jv)>);
   }
 
-  // !input_range<const V>
+  // !forward_range<const V>
   {
     std::ranges::join_view jv{ConstNotRange{}};
     static_assert(!HasConstBegin<decltype(jv)>);
@@ -146,6 +148,27 @@ constexpr bool test() {
     static_assert(std::same_as<decltype(jv.begin()), decltype(std::as_const(jv).begin())>);
   }
 
+  // Check stashing iterators (LWG3698: regex_iterator and join_view don't work together very well)
+  {
+    std::ranges::join_view<StashingRange> jv;
+    assert(std::ranges::equal(std::views::counted(jv.begin(), 10), std::string_view{"aababcabcd"}));
+  }
+
+  // LWG3700: The `const begin` of the `join_view` family does not require `InnerRng` to be a range
+  {
+    std::ranges::join_view<ConstNonJoinableRange> jv;
+    static_assert(!HasConstBegin<decltype(jv)>);
+  }
+
+  // Check example from LWG3700
+  {
+    auto r = std::views::iota(0, 5) | std::views::split(1);
+    auto s = std::views::single(r);
+    auto j = s | std::views::join;
+    auto f = j.front();
+    assert(std::ranges::equal(f, std::views::single(0)));
+  }
+
   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/ctad.compile.pass.cpp
similarity index 98%
rename from libcxx/test/std/ranges/range.adaptors/range.join.view/ctad.compile.pass.cpp
rename to libcxx/test/std/ranges/range.adaptors/range.join/ctad.compile.pass.cpp
index 2c470991be0b6bc..a8eafc5a9c02111 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.join.view/ctad.compile.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.join/ctad.compile.pass.cpp
@@ -7,7 +7,6 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: !c++experimental
 
 // template<class R>
 //   explicit join_view(R&&) -> join_view<views::all_t<R>>;
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/ctad.verify.cpp b/libcxx/test/std/ranges/range.adaptors/range.join/ctad.verify.cpp
similarity index 97%
rename from libcxx/test/std/ranges/range.adaptors/range.join.view/ctad.verify.cpp
rename to libcxx/test/std/ranges/range.adaptors/range.join/ctad.verify.cpp
index eddc950747ba768..2c6eea500580d6b 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.join.view/ctad.verify.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.join/ctad.verify.cpp
@@ -7,7 +7,6 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: !c++experimental
 
 // template<class R>
 //   explicit join_view(R&&) -> join_view<views::all_t<R>>;
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/ctor.default.pass.cpp
similarity index 97%
rename from libcxx/test/std/ranges/range.adaptors/range.join.view/ctor.default.pass.cpp
rename to libcxx/test/std/ranges/range.adaptors/range.join/ctor.default.pass.cpp
index 26206e32c358cec..0daff7d3b3c98ab 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.join.view/ctor.default.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.join/ctor.default.pass.cpp
@@ -7,7 +7,6 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: !c++experimental
 
 // join_view() requires default_initializable<V> = default;
 
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/ctor.view.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join/ctor.view.pass.cpp
similarity index 97%
rename from libcxx/test/std/ranges/range.adaptors/range.join.view/ctor.view.pass.cpp
rename to libcxx/test/std/ranges/range.adaptors/range.join/ctor.view.pass.cpp
index ce5393062d77814..75d4c7e5916b0a5 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.join.view/ctor.view.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.join/ctor.view.pass.cpp
@@ -7,7 +7,6 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: !c++experimental
 
 // constexpr explicit join_view(V base);
 
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/end.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join/end.pass.cpp
similarity index 95%
rename from libcxx/test/std/ranges/range.adaptors/range.join.view/end.pass.cpp
rename to libcxx/test/std/ranges/range.adaptors/range.join/end.pass.cpp
index 7e225202cc23127..516ba25a0e8596d 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.join.view/end.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.join/end.pass.cpp
@@ -7,10 +7,12 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: !c++experimental
 
 // constexpr auto end();
 // constexpr auto end() const;
+//   requires forward_range<const V> &&
+//            is_reference_v<range_reference_t<const V>> &&
+//            input_range<range_reference_t<const V>>
 
 #include <cassert>
 #include <ranges>
@@ -33,13 +35,13 @@ concept HasConstEnd = requires (const T& t){
 // | 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>|
+// | 6  |   Y    |   N     |   Y    |   Y   |    Y    |   Y    |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>|
+// | 12 |   N    |   N     |   Y    |   Y   |    Y    |   Y    |sentinel<false>|      -       |
 //
 //
 
@@ -131,10 +133,8 @@ constexpr bool test() {
 
     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(!HasConstEnd<decltype(jv)>);
     static_assert(!std::ranges::common_range<decltype(jv)>);
     static_assert(!std::ranges::common_range<const decltype(jv)>);
   }
@@ -219,10 +219,8 @@ constexpr bool test() {
 
     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(!HasConstEnd<decltype(jv)>);
     static_assert(!std::ranges::common_range<decltype(jv)>);
     static_assert(!std::ranges::common_range<const decltype(jv)>);
   }
@@ -288,6 +286,12 @@ constexpr bool test() {
     assert(jv.end() == std::ranges::next(jv.begin(), 12));
   }
 
+  // LWG3700: The `const begin` of the `join_view` family does not require `InnerRng` to be a range
+  {
+    std::ranges::join_view<ConstNonJoinableRange> jv;
+    static_assert(!HasConstEnd<decltype(jv)>);
+  }
+
   return true;
 }
 
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/general.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join/general.pass.cpp
similarity index 98%
rename from libcxx/test/std/ranges/range.adaptors/range.join.view/general.pass.cpp
rename to libcxx/test/std/ranges/range.adaptors/range.join/general.pass.cpp
index e9eab585260cdc9..f92eb418fac77c8 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.join.view/general.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.join/general.pass.cpp
@@ -7,7 +7,6 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: !c++experimental
 
 // General tests for join_view. This file does not test anything specifically.
 
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/iterator/arrow.pass.cpp
similarity index 99%
rename from libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/arrow.pass.cpp
rename to libcxx/test/std/ranges/range.adaptors/range.join/iterator/arrow.pass.cpp
index e610cde2c3b5bec..ddcf66bfe775e7d 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/arrow.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.join/iterator/arrow.pass.cpp
@@ -7,7 +7,6 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: !c++experimental
 
 // constexpr InnerIter operator->() const
 //   requires has-arrow<InnerIter> && copyable<InnerIter>;
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/iterator/ctor.default.pass.cpp
similarity index 70%
rename from libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.default.pass.cpp
rename to libcxx/test/std/ranges/range.adaptors/range.join/iterator/ctor.default.pass.cpp
index e4f193e4e606421..82fe824fad1b2a4 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.default.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.join/iterator/ctor.default.pass.cpp
@@ -7,9 +7,8 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: !c++experimental
 
-// iterator() requires default_initializable<OuterIter> = default;
+// iterator() = default;
 
 #include <ranges>
 
@@ -29,19 +28,12 @@ constexpr void test_default_constructible() {
   using JoinView = std::ranges::join_view<view<It>>;
   using JoinIterator = std::ranges::iterator_t<JoinView>;
   static_assert(std::is_default_constructible_v<JoinIterator>);
-  JoinIterator it; (void)it;
-}
-
-template <class It>
-constexpr void test_non_default_constructible() {
-  using JoinView = std::ranges::join_view<view<It>>;
-  using JoinIterator = std::ranges::iterator_t<JoinView>;
-  static_assert(!std::is_default_constructible_v<JoinIterator>);
+  [[maybe_unused]] JoinIterator it;
 }
 
 constexpr bool test() {
-  test_non_default_constructible<cpp17_input_iterator<ChildView*>>();
-  // NOTE: cpp20_input_iterator can't be used with join_view because it is not copyable.
+  test_default_constructible<cpp17_input_iterator<ChildView*>>();
+  test_default_constructible<cpp20_input_iterator<ChildView*>>();
   test_default_constructible<forward_iterator<ChildView*>>();
   test_default_constructible<bidirectional_iterator<ChildView*>>();
   test_default_constructible<random_access_iterator<ChildView*>>();
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/iterator/ctor.other.pass.cpp
similarity index 97%
rename from libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/ctor.other.pass.cpp
rename to libcxx/test/std/ranges/range.adaptors/range.join/iterator/ctor.other.pass.cpp
index a0406f90c88c639..e220b2cfeac84aa 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/iterator/ctor.other.pass.cpp
@@ -7,7 +7,6 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: !c++experimental
 
 // constexpr iterator(iterator<!Const> i)
 //             requires Const &&
@@ -37,7 +36,7 @@ constexpr bool test() {
   {
     CopyableChild children[4] = {CopyableChild(buffer[0]), CopyableChild(buffer[1]), CopyableChild(buffer[2]),
                                  CopyableChild(buffer[3])};
-    std::ranges::join_view jv(CopyableParent{children});
+    std::ranges::join_view jv(ForwardCopyableParent{children});
     auto iter1 = jv.begin();
     using iterator = decltype(iter1);
     using const_iterator = decltype(std::as_const(jv).begin());
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/iterator/decrement.pass.cpp
similarity index 85%
rename from libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/decrement.pass.cpp
rename to libcxx/test/std/ranges/range.adaptors/range.join/iterator/decrement.pass.cpp
index 4363fb0e330c3b9..c07bf4cc2adfe1c 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/decrement.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.join/iterator/decrement.pass.cpp
@@ -7,7 +7,14 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: !c++experimental
+
+// This causes ICE on powerpc(64)-ibm-aix:
+//   Assertion failed: Offset >= Size,
+//   file clang/lib/CodeGen/CGRecordLayoutBuilder.cpp,
+//   line 802,
+//   void (anonymous namespace)::CGRecordLowering::insertPadding()()
+// TODO: investigation needed
+// XFAIL: target=powerpc{{.*}}-ibm-aix
 
 // constexpr iterator& operator--();
 //              requires ref-is-glvalue && bidirectional_range<Base> &&
@@ -18,9 +25,12 @@
 //                       bidirectional_range<range_reference_t<Base>> &&
 //                       common_range<range_reference_t<Base>>;
 
+#include <algorithm>
+#include <array>
 #include <cassert>
 #include <ranges>
 #include <type_traits>
+#include <vector>
 
 #include "../types.h"
 
@@ -150,6 +160,15 @@ constexpr bool test() {
     static_assert(!CanPostDecrement<decltype(iter)>);
   }
 
+  {
+    // LWG3791: `join_view::iterator::operator--` may be ill-formed
+    std::vector<std::vector<int>> vec = {{1, 2}, {3, 4}, {5, 6}};
+    auto r = vec | std::views::transform([](auto& x) -> auto&& { return std::move(x); }) | std::views::join;
+    auto e = --r.end();
+    assert(*e == 6);
+    assert(std::ranges::equal(std::views::reverse(r), std::array{6, 5, 4, 3, 2, 1}));
+  }
+
   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/iterator/eq.pass.cpp
similarity index 89%
rename from libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/eq.pass.cpp
rename to libcxx/test/std/ranges/range.adaptors/range.join/iterator/eq.pass.cpp
index 327cc82b06b085b..8b7e694b080f47f 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/eq.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.join/iterator/eq.pass.cpp
@@ -7,10 +7,9 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: !c++experimental
 
 // friend constexpr bool operator==(const iterator& x, const iterator& y);
-//          requires ref-is-glvalue && equality_comparable<iterator_t<Base>> &&
+//          requires ref-is-glvalue && forward_range<Base> &&
 //                   equality_comparable<iterator_t<range_reference_t<Base>>>;
 
 #include <cassert>
@@ -43,7 +42,7 @@ constexpr bool test() {
   }
 
   {
-    // !equality_comparable<iterator_t<Base>>
+    // !forward_range<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>>);
@@ -51,8 +50,6 @@ constexpr bool test() {
     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)>);
   }
 
   {
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/iterator/increment.pass.cpp
similarity index 94%
rename from libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/increment.pass.cpp
rename to libcxx/test/std/ranges/range.adaptors/range.join/iterator/increment.pass.cpp
index 4bcb4de7e9c8868..dada91462a73fff 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/increment.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.join/iterator/increment.pass.cpp
@@ -7,7 +7,6 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: !c++experimental
 
 // constexpr iterator& operator++();
 // constexpr void operator++(int);
@@ -205,6 +204,23 @@ constexpr bool test() {
     static_assert(std::is_void_v<decltype(iter++)>);
   }
 
+  {
+    // Check stashing iterators (LWG3698: regex_iterator and join_view don't work together very well)
+    std::ranges::join_view<StashingRange> jv;
+    auto it = jv.begin();
+    assert(*it == 'a');
+    ++it;
+    assert(*it == 'a');
+    ++it;
+    assert(*it == 'b');
+    it++;
+    assert(*it == 'a');
+    it++;
+    assert(*it == 'b');
+    ++it;
+    assert(*it == 'c');
+  }
+
   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/iterator/iter.move.pass.cpp
similarity index 98%
rename from libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/iter.move.pass.cpp
rename to libcxx/test/std/ranges/range.adaptors/range.join/iterator/iter.move.pass.cpp
index 0bf6aa3d926146e..917e72dc858545c 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/iterator/iter.move.pass.cpp
@@ -7,7 +7,6 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: !c++experimental
 
 // friend constexpr decltype(auto) iter_move(const iterator& i);
 
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/iterator/iter.swap.pass.cpp
similarity index 98%
rename from libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/iter.swap.pass.cpp
rename to libcxx/test/std/ranges/range.adaptors/range.join/iterator/iter.swap.pass.cpp
index e9b73f1a415966a..28e1bf75726f639 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/iterator/iter.swap.pass.cpp
@@ -7,7 +7,6 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: !c++experimental
 
 // friend constexpr void iter_swap(const iterator& x, const iterator& y);
 
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/iterator/member_types.compile.pass.cpp
similarity index 99%
rename from libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/member_types.compile.pass.cpp
rename to libcxx/test/std/ranges/range.adaptors/range.join/iterator/member_types.compile.pass.cpp
index 17b98facd65081d..b9b9d73d77e2655 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/iterator/member_types.compile.pass.cpp
@@ -7,7 +7,6 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: !c++experimental
 
 // Iterator traits and member typedefs in join_view::<iterator>.
 
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/iterator/star.pass.cpp
similarity index 97%
rename from libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/star.pass.cpp
rename to libcxx/test/std/ranges/range.adaptors/range.join/iterator/star.pass.cpp
index fa6f7bb031207a3..73457b826df0b04 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/star.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.join/iterator/star.pass.cpp
@@ -7,7 +7,6 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: !c++experimental
 
 // constexpr decltype(auto) operator*() const;
 
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/sentinel/ctor.default.pass.cpp
similarity index 95%
rename from libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.default.pass.cpp
rename to libcxx/test/std/ranges/range.adaptors/range.join/sentinel/ctor.default.pass.cpp
index 0eebe14af3fcba3..42fcc733e181f4f 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.default.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.join/sentinel/ctor.default.pass.cpp
@@ -7,7 +7,6 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: !c++experimental
 
 // sentinel() = default;
 
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/sentinel/ctor.other.pass.cpp
similarity index 99%
rename from libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.other.pass.cpp
rename to libcxx/test/std/ranges/range.adaptors/range.join/sentinel/ctor.other.pass.cpp
index c2d7058746d758b..4bd8025efb5c1b3 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/sentinel/ctor.other.pass.cpp
@@ -7,7 +7,6 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: !c++experimental
 
 // constexpr sentinel(sentinel<!Const> s);
 //             requires Const && convertible_to<sentinel_t<V>, sentinel_t<Base>>;
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/sentinel/ctor.parent.pass.cpp
similarity index 97%
rename from libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.parent.pass.cpp
rename to libcxx/test/std/ranges/range.adaptors/range.join/sentinel/ctor.parent.pass.cpp
index a9df7c3881ba8f9..1ac68277338fee2 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/ctor.parent.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.join/sentinel/ctor.parent.pass.cpp
@@ -7,7 +7,6 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: !c++experimental
 
 // constexpr explicit sentinel(Parent& parent);
 
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/sentinel/eq.pass.cpp
similarity index 81%
rename from libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/eq.pass.cpp
rename to libcxx/test/std/ranges/range.adaptors/range.join/sentinel/eq.pass.cpp
index cbd03b84f208b9f..bc7d4bec94d3e63 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/eq.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.join/sentinel/eq.pass.cpp
@@ -7,7 +7,6 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: !c++experimental
 
 // template<bool OtherConst>
 //   requires sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>>
@@ -17,6 +16,7 @@
 #include <concepts>
 #include <functional>
 #include <ranges>
+#include <type_traits>
 
 #include "../types.h"
 
@@ -61,18 +61,27 @@ static_assert(EqualityComparable<std::ranges::iterator_t<const ConstComparableVi
 constexpr bool test() {
   int buffer[4][4] = {{1111, 2222, 3333, 4444}, {555, 666, 777, 888}, {99, 1010, 1111, 1212}, {13, 14, 15, 16}};
 
+  // test iterator<false> == sentinel<false>
   {
     ChildView children[4] = {ChildView(buffer[0]), ChildView(buffer[1]), ChildView(buffer[2]), ChildView(buffer[3])};
-    auto jv = std::ranges::join_view(ParentView(children));
+    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())>);
   }
 
+  // test iterator<false> == sentinel<true>
+  {
+    ChildView children[4] = {ChildView(buffer[0]), ChildView(buffer[1]), ChildView(buffer[2]), ChildView(buffer[3])};
+    using ParentT         = std::remove_all_extents_t<decltype(children)>;
+    auto jv               = std::ranges::join_view(ForwardParentView<ParentT>(children));
+    assert(std::as_const(jv).end() == std::ranges::next(jv.begin(), 16));
+  }
+
+  // test iterator<true> == sentinel<true>
   {
     CopyableChild children[4] = {CopyableChild(buffer[0]), CopyableChild(buffer[1]), CopyableChild(buffer[2]),
                                  CopyableChild(buffer[3])};
-    const auto jv = std::ranges::join_view(ParentView(children));
+    using ParentT             = std::remove_all_extents_t<decltype(children)>;
+    const auto jv             = std::ranges::join_view(ForwardParentView<ParentT>(children));
     assert(jv.end() == std::ranges::next(jv.begin(), 16));
   }
 
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.view/types.h b/libcxx/test/std/ranges/range.adaptors/range.join/types.h
similarity index 88%
rename from libcxx/test/std/ranges/range.adaptors/range.join.view/types.h
rename to libcxx/test/std/ranges/range.adaptors/range.join/types.h
index b2ef5f090b5731d..c1378dc1144b403 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.join.view/types.h
+++ b/libcxx/test/std/ranges/range.adaptors/range.join/types.h
@@ -11,6 +11,7 @@
 
 #include <concepts>
 #include <cstdint>
+#include <string>
 #include <tuple>
 
 #include "test_macros.h"
@@ -52,13 +53,13 @@ inline ChildView globalChildren[4] = {
     ChildView(globalBuffer[3]),
 };
 
-template <class T>
+template <class T, template<class...> class Iter = cpp17_input_iterator>
 struct ParentView : std::ranges::view_base {
   T* ptr_;
   unsigned size_;
 
-  using iterator = cpp20_input_iterator<T*>;
-  using const_iterator = cpp20_input_iterator<const T*>;
+  using iterator = Iter<T*>;
+  using const_iterator = Iter<const T*>;
   using sentinel = sentinel_wrapper<iterator>;
   using const_sentinel = sentinel_wrapper<const_iterator>;
 
@@ -80,6 +81,9 @@ struct ParentView : std::ranges::view_base {
 template <class T>
 ParentView(T*) -> ParentView<T>;
 
+template<class T>
+using ForwardParentView = ParentView<T, forward_iterator>;
+
 struct CopyableChild : std::ranges::view_base {
   int* ptr_;
   unsigned size_;
@@ -97,15 +101,16 @@ struct CopyableChild : std::ranges::view_base {
   constexpr const_sentinel end() const { return const_sentinel(const_iterator(ptr_ + size_)); }
 };
 
-struct CopyableParent : std::ranges::view_base {
+template<template<class...> class Iter>
+struct CopyableParentTemplate : std::ranges::view_base {
   CopyableChild* ptr_;
 
-  using iterator = cpp17_input_iterator<CopyableChild*>;
-  using const_iterator = cpp17_input_iterator<const CopyableChild*>;
+  using iterator = Iter<CopyableChild*>;
+  using const_iterator = Iter<const CopyableChild*>;
   using sentinel = sentinel_wrapper<iterator>;
   using const_sentinel = sentinel_wrapper<const_iterator>;
 
-  constexpr CopyableParent(CopyableChild* ptr) : ptr_(ptr) {}
+  constexpr CopyableParentTemplate(CopyableChild* ptr) : ptr_(ptr) {}
 
   constexpr iterator begin() { return iterator(ptr_); }
   constexpr const_iterator begin() const { return const_iterator(ptr_); }
@@ -113,6 +118,9 @@ struct CopyableParent : std::ranges::view_base {
   constexpr const_sentinel end() const { return const_sentinel(const_iterator(ptr_ + 4)); }
 };
 
+using CopyableParent = CopyableParentTemplate<cpp17_input_iterator>;
+using ForwardCopyableParent = CopyableParentTemplate<forward_iterator>;
+
 struct Box {
   int x;
 };
@@ -392,4 +400,48 @@ struct IterMoveSwapAwareView : BufferView<int*> {
 };
 static_assert(std::ranges::input_range<IterMoveSwapAwareView>);
 
+class StashingIterator {
+public:
+  using difference_type = std::ptrdiff_t;
+  using value_type      = std::string;
+
+  constexpr StashingIterator() : letter_('a') {}
+
+  constexpr StashingIterator& operator++() {
+    str_ += letter_;
+    ++letter_;
+    return *this;
+  }
+
+  constexpr void operator++(int) { ++*this; }
+
+  constexpr value_type operator*() const { return str_; }
+
+  constexpr bool operator==(std::default_sentinel_t) const { return letter_ > 'z'; }
+
+private:
+  char letter_;
+  value_type str_;
+};
+
+using StashingRange = std::ranges::subrange<StashingIterator, std::default_sentinel_t>;
+static_assert(std::ranges::input_range<StashingRange>);
+static_assert(!std::ranges::forward_range<StashingRange>);
+
+class ConstNonJoinableRange : public std::ranges::view_base {
+public:
+  constexpr StashingIterator begin() { return {}; }
+  constexpr std::default_sentinel_t end() { return {}; }
+
+  constexpr const int* begin() const { return &val_; }
+  constexpr const int* end() const { return &val_ + 1; }
+
+private:
+  int val_ = 1;
+};
+static_assert(std::ranges::input_range<ConstNonJoinableRange>);
+static_assert(std::ranges::input_range<const ConstNonJoinableRange>);
+static_assert(std::ranges::input_range<std::ranges::range_reference_t<ConstNonJoinableRange>>);
+static_assert(!std::ranges::input_range<std::ranges::range_reference_t<const ConstNonJoinableRange>>);
+
 #endif // TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_JOIN_TYPES_H
diff --git a/libcxx/test/std/re/re.iter/re.regiter/iterator_concept_conformance.compile.pass.cpp b/libcxx/test/std/re/re.iter/re.regiter/iterator_concept_conformance.compile.pass.cpp
index 6f2da091c3709b3..ad61baa76018da8 100644
--- a/libcxx/test/std/re/re.iter/re.regiter/iterator_concept_conformance.compile.pass.cpp
+++ b/libcxx/test/std/re/re.iter/re.regiter/iterator_concept_conformance.compile.pass.cpp
@@ -14,8 +14,8 @@
 
 #include <iterator>
 
-static_assert(std::forward_iterator<std::cregex_iterator>);
-static_assert(!std::bidirectional_iterator<std::cregex_iterator>);
+static_assert(std::input_iterator<std::cregex_iterator>);
+static_assert(!std::forward_iterator<std::cregex_iterator>);
 static_assert(!std::indirectly_writable<std::cregex_iterator, char>);
 static_assert(std::sentinel_for<std::cregex_iterator, std::cregex_iterator>);
 static_assert(!std::sized_sentinel_for<std::cregex_iterator, std::cregex_iterator>);
diff --git a/libcxx/test/std/re/re.iter/re.regiter/types.pass.cpp b/libcxx/test/std/re/re.iter/re.regiter/types.pass.cpp
index 7d30b0adcc234c3..8ee2c5006d31c4a 100644
--- a/libcxx/test/std/re/re.iter/re.regiter/types.pass.cpp
+++ b/libcxx/test/std/re/re.iter/re.regiter/types.pass.cpp
@@ -20,6 +20,7 @@
 //     typedef const value_type*                    pointer;
 //     typedef const value_type&                    reference;
 //     typedef forward_iterator_tag                 iterator_category;
+//     typedef input_iterator_tag                   iterator_concept; // since C++20
 
 #include <regex>
 #include <type_traits>
@@ -36,6 +37,9 @@ test()
     static_assert((std::is_same<typename I::pointer, const std::match_results<const CharT*>*>::value), "");
     static_assert((std::is_same<typename I::reference, const std::match_results<const CharT*>&>::value), "");
     static_assert((std::is_same<typename I::iterator_category, std::forward_iterator_tag>::value), "");
+#if TEST_STD_VER >= 20
+    static_assert(std::is_same_v<typename I::iterator_concept, std::input_iterator_tag>);
+#endif
 }
 
 int main(int, char**)
diff --git a/libcxx/test/std/re/re.iter/re.tokiter/iterator_concept_conformance.compile.pass.cpp b/libcxx/test/std/re/re.iter/re.tokiter/iterator_concept_conformance.compile.pass.cpp
index 397226552edee07..23eea7f369c170d 100644
--- a/libcxx/test/std/re/re.iter/re.tokiter/iterator_concept_conformance.compile.pass.cpp
+++ b/libcxx/test/std/re/re.iter/re.tokiter/iterator_concept_conformance.compile.pass.cpp
@@ -14,8 +14,8 @@
 
 #include <iterator>
 
-static_assert(std::forward_iterator<std::cregex_token_iterator>);
-static_assert(!std::bidirectional_iterator<std::cregex_token_iterator>);
+static_assert(std::input_iterator<std::cregex_token_iterator>);
+static_assert(!std::forward_iterator<std::cregex_token_iterator>);
 static_assert(!std::indirectly_writable<std::cregex_token_iterator, char>);
 static_assert(std::sentinel_for<std::cregex_token_iterator, std::cregex_token_iterator>);
 static_assert(!std::sized_sentinel_for<std::cregex_token_iterator, std::cregex_token_iterator>);
diff --git a/libcxx/test/std/re/re.iter/re.tokiter/types.pass.cpp b/libcxx/test/std/re/re.iter/re.tokiter/types.pass.cpp
index 73ad58f4eecfb6a..a9c18e8a1b77a16 100644
--- a/libcxx/test/std/re/re.iter/re.tokiter/types.pass.cpp
+++ b/libcxx/test/std/re/re.iter/re.tokiter/types.pass.cpp
@@ -20,6 +20,7 @@
 //     typedef const value_type*                pointer;
 //     typedef const value_type&                reference;
 //     typedef forward_iterator_tag             iterator_category;
+//     typedef input_iterator_tag               iterator_concept; // since C++20
 
 #include <regex>
 #include <type_traits>
@@ -36,6 +37,9 @@ test()
     static_assert((std::is_same<typename I::pointer, const std::sub_match<const CharT*>*>::value), "");
     static_assert((std::is_same<typename I::reference, const std::sub_match<const CharT*>&>::value), "");
     static_assert((std::is_same<typename I::iterator_category, std::forward_iterator_tag>::value), "");
+#if TEST_STD_VER >= 20
+    static_assert(std::is_same_v<typename I::iterator_concept, std::input_iterator_tag>);
+#endif
 }
 
 int main(int, char**)
diff --git a/libcxx/utils/data/ignore_format.txt b/libcxx/utils/data/ignore_format.txt
index d4afd8c87350fd1..ecd1d1414a8ad21 100644
--- a/libcxx/utils/data/ignore_format.txt
+++ b/libcxx/utils/data/ignore_format.txt
@@ -5220,29 +5220,29 @@ libcxx/test/std/ranges/range.adaptors/range.filter/sentinel/compare.pass.cpp
 libcxx/test/std/ranges/range.adaptors/range.filter/sentinel/ctor.default.pass.cpp
 libcxx/test/std/ranges/range.adaptors/range.filter/sentinel/ctor.parent.pass.cpp
 libcxx/test/std/ranges/range.adaptors/range.filter/types.h
-libcxx/test/std/ranges/range.adaptors/range.join.view/adaptor.pass.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.default.pass.cpp
-libcxx/test/std/ranges/range.adaptors/range.join.view/ctor.view.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/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
+libcxx/test/std/ranges/range.adaptors/range.join/adaptor.pass.cpp
+libcxx/test/std/ranges/range.adaptors/range.join/base.pass.cpp
+libcxx/test/std/ranges/range.adaptors/range.join/begin.pass.cpp
+libcxx/test/std/ranges/range.adaptors/range.join/ctad.compile.pass.cpp
+libcxx/test/std/ranges/range.adaptors/range.join/ctad.verify.cpp
+libcxx/test/std/ranges/range.adaptors/range.join/ctor.default.pass.cpp
+libcxx/test/std/ranges/range.adaptors/range.join/ctor.view.pass.cpp
+libcxx/test/std/ranges/range.adaptors/range.join/end.pass.cpp
+libcxx/test/std/ranges/range.adaptors/range.join/general.pass.cpp
+libcxx/test/std/ranges/range.adaptors/range.join/iterator/arrow.pass.cpp
+libcxx/test/std/ranges/range.adaptors/range.join/iterator/ctor.default.pass.cpp
+libcxx/test/std/ranges/range.adaptors/range.join/iterator/ctor.other.pass.cpp
+libcxx/test/std/ranges/range.adaptors/range.join/iterator/ctor.parent.outer.pass.cpp
+libcxx/test/std/ranges/range.adaptors/range.join/iterator/decrement.pass.cpp
+libcxx/test/std/ranges/range.adaptors/range.join/iterator/eq.pass.cpp
+libcxx/test/std/ranges/range.adaptors/range.join/iterator/increment.pass.cpp
+libcxx/test/std/ranges/range.adaptors/range.join/iterator/member_types.compile.pass.cpp
+libcxx/test/std/ranges/range.adaptors/range.join/iterator/star.pass.cpp
+libcxx/test/std/ranges/range.adaptors/range.join/sentinel/ctor.default.pass.cpp
+libcxx/test/std/ranges/range.adaptors/range.join/sentinel/ctor.other.pass.cpp
+libcxx/test/std/ranges/range.adaptors/range.join/sentinel/ctor.parent.pass.cpp
+libcxx/test/std/ranges/range.adaptors/range.join/sentinel/eq.pass.cpp
+libcxx/test/std/ranges/range.adaptors/range.join/types.h
 libcxx/test/std/ranges/range.adaptors/range.lazy.split/adaptor.pass.cpp
 libcxx/test/std/ranges/range.adaptors/range.lazy.split/base.pass.cpp
 libcxx/test/std/ranges/range.adaptors/range.lazy.split/begin.pass.cpp



More information about the libcxx-commits mailing list