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

via libcxx-commits libcxx-commits at lists.llvm.org
Tue Dec 12 06:45:18 PST 2023


Author: Jakub Mazurkiewicz
Date: 2023-12-12T09:45:14-05:00
New Revision: 6a664674990094c1b5d2e717256f08cb04485899

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

LOG: [libc++] P2770R0: Stashing stashing iterators for proper flattening (#66033)

- Partially implements P2770R0 (http://wg21.link/p2770)
- Fixes https://wg21.link/LWG3698, https://wg21.link/LWG3700, and https://wg21.link/LWG3791
- join_with_view hasn't been done yet since this type isn't implemented yet
- Rename tuple test directory to match the standard (which changed in P2770R0)
- Rename join_view test directory to match the standard

Added: 
    libcxx/include/__utility/as_lvalue.h
    libcxx/test/libcxx/ranges/range.adaptors/range.adaptor.helpers/as-lvalue.lifetimebound.verify.cpp
    libcxx/test/libcxx/ranges/range.adaptors/range.adaptor.helpers/as-lvalue.pass.cpp
    libcxx/test/libcxx/ranges/range.adaptors/range.adaptor.helpers/tuple-for-each.pass.cpp
    libcxx/test/libcxx/ranges/range.adaptors/range.join/range.join.iterator/ctor.parent.outer.pass.cpp
    libcxx/test/libcxx/ranges/range.adaptors/range.join/range.join.iterator/ctor.parent.pass.cpp
    libcxx/test/libcxx/ranges/range.adaptors/range.join/range.join.iterator/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/lwg3698.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join/range.join.iterator/arrow.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join/range.join.iterator/ctor.default.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join/range.join.iterator/ctor.other.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join/range.join.iterator/decrement.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join/range.join.iterator/eq.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join/range.join.iterator/increment.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join/range.join.iterator/iter.move.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join/range.join.iterator/iter.swap.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join/range.join.iterator/member_types.compile.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join/range.join.iterator/star.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join/range.join.sentinel/ctor.default.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join/range.join.sentinel/ctor.other.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join/range.join.sentinel/ctor.parent.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join/range.join.sentinel/eq.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.join/types.h

Modified: 
    libcxx/docs/Status/Cxx23.rst
    libcxx/docs/Status/Cxx23Papers.csv
    libcxx/docs/UsingLibcxx.rst
    libcxx/include/CMakeLists.txt
    libcxx/include/__ranges/join_view.h
    libcxx/include/module.modulemap.in
    libcxx/include/regex
    libcxx/include/utility
    libcxx/modules/std/ranges.inc
    libcxx/test/libcxx/ranges/range.adaptors/range.join/segmented_iterator.compile.pass.cpp
    libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy.segmented.pass.cpp
    libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_backward.pass.cpp
    libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_backward.segmented.pass.cpp
    libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_n.segmented.pass.cpp
    libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move.segmented.pass.cpp
    libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move_backward.segmented.pass.cpp
    libcxx/test/std/library/description/conventions/customization.point.object/cpo.compile.pass.cpp
    libcxx/test/std/ranges/iterator_robust_against_adl.compile.pass.cpp
    libcxx/test/std/re/re.iter/re.regiter/iterator_concept_conformance.compile.pass.cpp
    libcxx/test/std/re/re.iter/re.regiter/types.pass.cpp
    libcxx/test/std/re/re.iter/re.tokiter/iterator_concept_conformance.compile.pass.cpp
    libcxx/test/std/re/re.iter/re.tokiter/types.pass.cpp

Removed: 
    libcxx/test/libcxx/ranges/range.adaptors/range.adaptor.tuple/tuple-for-each.pass.cpp
    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/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


################################################################################
diff  --git a/libcxx/docs/Status/Cxx23.rst b/libcxx/docs/Status/Cxx23.rst
index 839640a7c7e881..3e6e33f08c7ccf 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 e03cbff2a08bbf..b4f915d37d0021 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 cdab2c36eab745..8d9f795da977e3 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 d8faf6467b79ae..533b7ac9103ab0 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 e6240dfd2580dc..f80beda33b11ed 100644
--- a/libcxx/include/__ranges/join_view.h
+++ b/libcxx/include/__ranges/join_view.h
@@ -32,6 +32,8 @@
 #include <__ranges/view_interface.h>
 #include <__type_traits/common_type.h>
 #include <__type_traits/maybe_const.h>
+#include <__utility/as_lvalue.h>
+#include <__utility/empty.h>
 #include <__utility/forward.h>
 #include <optional>
 
@@ -41,10 +43,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 +83,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 +109,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 +144,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,12 +163,12 @@ namespace ranges {
     requires view<_View> && input_range<range_reference_t<_View>>
   template<bool _Const>
   struct join_view<_View>::__sentinel {
-  template<bool>
+  private:
+    template <bool>
     friend struct __sentinel;
 
-  private:
-    using _Parent = __maybe_const<_Const, join_view<_View>>;
-    using _Base = __maybe_const<_Const, _View>;
+    using _Parent            = __maybe_const<_Const, join_view>;
+    using _Base              = __maybe_const<_Const, _View>;
     sentinel_t<_Base> __end_ = sentinel_t<_Base>();
 
   public:
@@ -179,7 +188,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 +200,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;
@@ -207,23 +214,25 @@ namespace ranges {
     using _Inner = iterator_t<range_reference_t<_Base>>;
     using _InnerRange = range_reference_t<_View>;
 
+    static_assert(!_Const || forward_range<_Base>, "Const can only be true when Base models forward_range.");
+
     static constexpr bool __ref_is_glvalue = is_reference_v<range_reference_t<_Base>>;
 
-  public:
-    _Outer __outer_ = _Outer();
+    static constexpr bool _OuterPresent = forward_range<_Base>;
+    using _OuterType                    = _If<_OuterPresent, _Outer, std::__empty>;
+    _LIBCPP_NO_UNIQUE_ADDRESS _OuterType __outer_ = _OuterType();
 
-  private:
     optional<_Inner> __inner_;
-    _Parent *__parent_ = nullptr;
+    _Parent* __parent_ = nullptr;
 
     _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 +243,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 explicit __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 +292,7 @@ namespace ranges {
     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_(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 +317,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(std::__as_lvalue(__get_inner_range()))) {
+        ++__get_outer();
         __satisfy();
       }
       return *this;
@@ -324,11 +354,11 @@ namespace ranges {
                common_range<range_reference_t<_Base>>
     {
       if (__outer_ == ranges::end(__parent_->__base_))
-        __inner_ = ranges::end(*--__outer_);
+        __inner_ = ranges::end(std::__as_lvalue(*--__outer_));
 
       // Skip empty inner ranges when going backwards.
-      while (*__inner_ == ranges::begin(*__outer_)) {
-        __inner_ = ranges::end(*--__outer_);
+      while (*__inner_ == ranges::begin(std::__as_lvalue(*__outer_))) {
+        __inner_ = ranges::end(std::__as_lvalue(*--__outer_));
       }
 
       --*__inner_;
@@ -350,7 +380,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 +466,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 00000000000000..159f45dad4d41c
--- /dev/null
+++ b/libcxx/include/__utility/as_lvalue.h
@@ -0,0 +1,37 @@
+// -*- 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
+
+#ifndef _LIBCPP_CXX03_LANG
+
+template <class _Tp>
+_LIBCPP_HIDE_FROM_ABI constexpr _Tp& __as_lvalue(_LIBCPP_LIFETIMEBOUND _Tp&& __t) {
+  return static_cast<_Tp&>(__t);
+}
+
+#endif // !_LIBCPP_CXX03_LANG
+
+_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 90ee7fbb2157c2..7e93e0a155033d 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -2019,6 +2019,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 fcdd85f8c4997e..008fe70a0ca6e7 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 c5581d55e79bbb..1deef3db204107 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 a883103d812588..82c7d99f8979a8 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.lifetimebound.verify.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.adaptor.helpers/as-lvalue.lifetimebound.verify.cpp
new file mode 100644
index 00000000000000..7046936b1b7a7f
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.adaptor.helpers/as-lvalue.lifetimebound.verify.cpp
@@ -0,0 +1,22 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// template<class T>
+// constexpr T& as-lvalue(T&& t) { // exposition only
+
+#include <utility>
+
+void test() {
+  // Check prvalue
+  {
+    [[maybe_unused]] auto& check = std::__as_lvalue(
+        0); // expected-warning {{temporary bound to local reference 'check' will be destroyed at the end of the full-expression}}
+  }
+}

diff  --git a/libcxx/test/libcxx/ranges/range.adaptors/range.adaptor.helpers/as-lvalue.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.adaptor.helpers/as-lvalue.pass.cpp
new file mode 100644
index 00000000000000..721279fcd586b0
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.adaptor.helpers/as-lvalue.pass.cpp
@@ -0,0 +1,40 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// template<class T>
+// constexpr T& as-lvalue(T&& t) { // exposition only
+
+#include <type_traits>
+#include <utility>
+
+constexpr bool test() {
+  // Check glvalue
+  {
+    int lvalue{};
+    [[maybe_unused]] decltype(auto) check = std::__as_lvalue(lvalue);
+    static_assert(std::is_same<decltype(check), int&>::value, "");
+  }
+
+  // Check xvalue
+  {
+    int xvalue{};
+    [[maybe_unused]] decltype(auto) check = std::__as_lvalue(std::move(xvalue));
+    static_assert(std::is_same<decltype(check), int&>::value, "");
+  }
+
+  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/range.join.iterator/ctor.parent.outer.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.join/range.join.iterator/ctor.parent.outer.pass.cpp
new file mode 100644
index 00000000000000..a54980bec287fc
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.join/range.join.iterator/ctor.parent.outer.pass.cpp
@@ -0,0 +1,65 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// constexpr iterator(Parent& parent, OuterIter outer)
+//   requires forward_range<Base>; // exposition only
+
+#include <cassert>
+#include <ranges>
+#include <string>
+#include <utility>
+
+#include "types.h"
+
+constexpr bool test() {
+  std::string strings[4] = {"aaaa", "bbbb", "cccc", "dddd"};
+
+  { // Check if `outer_` is initialized with `std::move(outer)` for `iterator<false>`
+    MoveOnAccessSubrange r{DieOnCopyIterator(strings), sentinel_wrapper(strings + 4)};
+    std::ranges::join_view jv(std::move(r));
+    auto iter = jv.begin(); // Calls `iterator(Parent& parent, OuterIter outer)`
+    assert(*iter == 'a');
+  }
+
+  { // Check if `outer_` is initialized with `std::move(outer)` for `iterator<true>`
+    MoveOnAccessSubrange r{DieOnCopyIterator(strings), sentinel_wrapper(strings + 4)};
+    std::ranges::join_view jv(std::ranges::ref_view{r});
+    auto iter = std::as_const(jv).begin(); // Calls `iterator(Parent& parent, OuterIter outer)`
+    assert(*iter == 'a');
+  }
+
+  {
+    // 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.
+    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>);
+
+    int buffer[2][2]               = {{1, 2}, {3, 4}};
+    NonDefaultCtrIterView inners[] = {buffer[0], buffer[1]};
+    auto outer                     = std::views::all(inners);
+    std::ranges::join_view jv(outer);
+    auto iter = jv.begin(); // Calls `iterator(Parent& parent, OuterIter outer)`
+    assert(*iter == 1);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/libcxx/ranges/range.adaptors/range.join/range.join.iterator/ctor.parent.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.join/range.join.iterator/ctor.parent.pass.cpp
new file mode 100644
index 00000000000000..3026a02abf00f3
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.join/range.join.iterator/ctor.parent.pass.cpp
@@ -0,0 +1,36 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// constexpr explicit iterator(Parent& parent)
+//   requires (!forward_range<Base>); // exposition only
+
+#include <string>
+#include <ranges>
+
+#include "types.h"
+
+constexpr bool test() {
+  std::string strings[4] = {"eeee", "ffff", "gggg", "hhhh"};
+
+  MoveOnAccessSubrange r{
+      DieOnCopyIterator(cpp20_input_iterator(strings)), sentinel_wrapper(cpp20_input_iterator(strings + 4))};
+  std::ranges::join_view jv(std::move(r));
+  auto iter = jv.begin(); // Calls `iterator(Parent& parent)`
+  assert(*iter == 'e');
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/libcxx/ranges/range.adaptors/range.join/range.join.iterator/types.h b/libcxx/test/libcxx/ranges/range.adaptors/range.join/range.join.iterator/types.h
new file mode 100644
index 00000000000000..0652d4bdb4717c
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.join/range.join.iterator/types.h
@@ -0,0 +1,114 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_LIBCXX_RANGES_RANGE_ADAPTORS_RANGE_JOIN_RANGE_JOIN_ITERATOR_TYPES_H
+#define TEST_LIBCXX_RANGES_RANGE_ADAPTORS_RANGE_JOIN_RANGE_JOIN_ITERATOR_TYPES_H
+
+#include <cassert>
+#include <cstddef>
+#include <ranges>
+
+#include "test_iterators.h"
+
+template <std::input_iterator Iter>
+struct DieOnCopyIterator {
+  using value_type      = std::iter_value_t<Iter>;
+  using 
diff erence_type = std::iter_
diff erence_t<Iter>;
+
+  DieOnCopyIterator()
+    requires std::default_initializable<Iter>
+  = default;
+
+  constexpr explicit DieOnCopyIterator(Iter iter) : iter_(std::move(iter)) {}
+  constexpr DieOnCopyIterator(DieOnCopyIterator&& other) = default;
+  DieOnCopyIterator& operator=(DieOnCopyIterator&&)      = default;
+
+  constexpr DieOnCopyIterator(const DieOnCopyIterator&) { assert(false); }
+  constexpr DieOnCopyIterator& operator=(const DieOnCopyIterator&) { assert(false); }
+
+  constexpr DieOnCopyIterator& operator++() {
+    ++iter_;
+    return *this;
+  }
+
+  constexpr void operator++(int) { iter_++; }
+
+  constexpr DieOnCopyIterator operator++(int)
+    requires std::forward_iterator<Iter>
+  {
+    auto tmp = *this;
+    ++tmp;
+    return tmp;
+  }
+
+  constexpr decltype(auto) operator*() const { return *iter_; }
+
+  friend constexpr bool operator==(const DieOnCopyIterator& left, const DieOnCopyIterator& right)
+    requires std::equality_comparable<Iter>
+  {
+    return left.iter_ == right.iter_;
+  }
+
+  friend constexpr bool operator==(const DieOnCopyIterator& it, const sentinel_wrapper<Iter>& se) {
+    return it.iter_ == se;
+  }
+
+private:
+  Iter iter_ = Iter();
+};
+
+template <class Iter>
+explicit DieOnCopyIterator(Iter) -> DieOnCopyIterator<Iter>;
+
+static_assert(std::input_iterator<DieOnCopyIterator<cpp20_input_iterator<int*>>>);
+static_assert(!std::forward_iterator<DieOnCopyIterator<cpp20_input_iterator<int*>>>);
+static_assert(std::forward_iterator<DieOnCopyIterator<int*>>);
+static_assert(!std::bidirectional_iterator<DieOnCopyIterator<int*>>);
+static_assert(std::sentinel_for<sentinel_wrapper<int*>, DieOnCopyIterator<int*>>);
+
+template <std::input_iterator Iter, std::sentinel_for<Iter> Sent = Iter>
+struct MoveOnAccessSubrange : std::ranges::view_base {
+  constexpr explicit MoveOnAccessSubrange(Iter iter, Sent sent) : iter_(std::move(iter)), sent_(std::move(sent)) {}
+
+  MoveOnAccessSubrange(MoveOnAccessSubrange&&)            = default;
+  MoveOnAccessSubrange& operator=(MoveOnAccessSubrange&&) = default;
+
+  MoveOnAccessSubrange(const MoveOnAccessSubrange&)            = delete;
+  MoveOnAccessSubrange& operator=(const MoveOnAccessSubrange&) = delete;
+
+  constexpr Iter begin() { return std::move(iter_); }
+  constexpr Sent end() { return std::move(sent_); }
+
+private:
+  Iter iter_;
+  Sent sent_;
+};
+
+template <class Iter, class Sent>
+MoveOnAccessSubrange(Iter, Sent) -> MoveOnAccessSubrange<Iter, Sent>;
+
+static_assert(std::ranges::input_range<MoveOnAccessSubrange<int*, sentinel_wrapper<int*>>>);
+static_assert(std::ranges::forward_range<MoveOnAccessSubrange<DieOnCopyIterator<int*>>>);
+
+template <class Iter, class Sent>
+  requires(!std::same_as<Iter, Sent>)
+struct BufferView : std::ranges::view_base {
+  using T = std::iter_value_t<Iter>;
+  T* data_;
+  std::size_t size_;
+
+  template <std::size_t N>
+  constexpr BufferView(T (&b)[N]) : data_(b), size_(N) {}
+
+  constexpr Iter begin() const { return Iter(data_); }
+  constexpr Sent end() const { return Sent(Iter(data_ + size_)); }
+};
+
+static_assert(std::ranges::input_range<BufferView<int*, sentinel_wrapper<int*>>>);
+
+#endif // TEST_LIBCXX_RANGES_RANGE_ADAPTORS_RANGE_JOIN_RANGE_JOIN_ITERATOR_TYPES_H

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 82e8cab503a275..6cd17c2b3f3533 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 50fb479afcd064..ec60ab8db1609b 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 feca8fd3be858b..343447446ab2d7 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/algorithms/alg.modifying.operations/alg.copy/ranges.copy_backward.segmented.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_backward.segmented.pass.cpp
index c434cea1208cfe..efeada57625581 100644
--- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_backward.segmented.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_backward.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_n.segmented.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_n.segmented.pass.cpp
index eae40cefa663f8..7da0f30775905f 100644
--- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_n.segmented.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_n.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.move/ranges.move.segmented.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move.segmented.pass.cpp
index 2df6a10b18504c..e29ba8af07d6f8 100644
--- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move.segmented.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move.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.move/ranges.move_backward.segmented.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move_backward.segmented.pass.cpp
index 0f0a71439a10dd..50f371a6d64d3a 100644
--- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move_backward.segmented.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move_backward.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/library/description/conventions/customization.point.object/cpo.compile.pass.cpp b/libcxx/test/std/library/description/conventions/customization.point.object/cpo.compile.pass.cpp
index e6c0e09dfff5f8..060f179fe16832 100644
--- a/libcxx/test/std/library/description/conventions/customization.point.object/cpo.compile.pass.cpp
+++ b/libcxx/test/std/library/description/conventions/customization.point.object/cpo.compile.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
 
 // [customization.point.object]
 // [range.adaptor.object] "A range adaptor object is a customization point object..."

diff  --git a/libcxx/test/std/ranges/iterator_robust_against_adl.compile.pass.cpp b/libcxx/test/std/ranges/iterator_robust_against_adl.compile.pass.cpp
index 09b77c0901a229..5efd6c72a13dbf 100644
--- a/libcxx/test/std/ranges/iterator_robust_against_adl.compile.pass.cpp
+++ b/libcxx/test/std/ranges/iterator_robust_against_adl.compile.pass.cpp
@@ -7,7 +7,6 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: !c++experimental
 
 // ADL call with nested iterators of views should not look up base's view's
 // namespace

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 215318f15cad0d..00000000000000
--- 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 afaf3227210996..9beb3d282a27cc 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 13883e894ac7bf..caf018b582263a 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 9e4fa5f8c59a44..005d0d1d2d5cb7 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 2c470991be0b6b..a8eafc5a9c0211 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 eddc950747ba76..2c6eea500580d6 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 26206e32c358ce..0daff7d3b3c98a 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 ce5393062d7781..75d4c7e5916b0a 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 7e225202cc2312..516ba25a0e8596 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 e9eab585260cdc..f92eb418fac77c 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/lwg3698.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join/lwg3698.pass.cpp
new file mode 100644
index 00000000000000..0abe37bf17f7e8
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join/lwg3698.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: no-localization
+
+// Check LWG-3698: `regex_iterator` and `join_view` don't work together very well
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <ranges>
+#include <regex>
+#include <string_view>
+
+int main(int, char**) {
+  char const text[] = "Hello";
+  std::regex regex{"[a-z]"};
+
+  auto lower =
+      std::ranges::subrange(
+          std::cregex_iterator(std::ranges::begin(text), std::ranges::end(text), regex), std::cregex_iterator{}) |
+      std::views::join | std::views::transform([](auto const& sm) { return std::string_view(sm.first, sm.second); });
+
+  assert(std::ranges::equal(lower, std::to_array<std::string_view>({"e", "l", "l", "o"})));
+
+  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/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/range.join.iterator/arrow.pass.cpp
index e610cde2c3b5be..ddcf66bfe775e7 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/arrow.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.join/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/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/range.join.iterator/ctor.default.pass.cpp
index e4f193e4e60642..82fe824fad1b2a 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/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/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/range.join.iterator/ctor.other.pass.cpp
index a0406f90c88c63..e220b2cfeac84a 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/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/range.join.iterator/decrement.pass.cpp
similarity index 90%
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/range.join.iterator/decrement.pass.cpp
index 4363fb0e330c3b..29720d93bab66f 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/decrement.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.join/range.join.iterator/decrement.pass.cpp
@@ -7,7 +7,6 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: !c++experimental
 
 // constexpr iterator& operator--();
 //              requires ref-is-glvalue && bidirectional_range<Base> &&
@@ -18,9 +17,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 +152,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/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/range.join.iterator/eq.pass.cpp
index 327cc82b06b085..5c831f33e67c70 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/eq.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.join/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<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/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/range.join.iterator/increment.pass.cpp
index 4bcb4de7e9c886..dada91462a73ff 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/increment.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.join/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/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/range.join.iterator/iter.move.pass.cpp
index 0bf6aa3d926146..917e72dc858545 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/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/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/range.join.iterator/iter.swap.pass.cpp
index e9b73f1a415966..28e1bf75726f63 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/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/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/range.join.iterator/member_types.compile.pass.cpp
index 17b98facd65081..b9b9d73d77e265 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/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/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/range.join.iterator/star.pass.cpp
index fa6f7bb031207a..73457b826df0b0 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.join.view/iterator/star.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.join/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/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/range.join.sentinel/ctor.default.pass.cpp
index 0eebe14af3fcba..42fcc733e181f4 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/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/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/range.join.sentinel/ctor.other.pass.cpp
index 96196dcbaa5b31..5ef3e7416ef10b 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/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/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/range.join.sentinel/ctor.parent.pass.cpp
index a9df7c3881ba8f..1ac68277338fee 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/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/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/range.join.sentinel/eq.pass.cpp
index cbd03b84f208b9..bc7d4bec94d3e6 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.join.view/sentinel/eq.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.join/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 b2ef5f090b5731..c1378dc1144b40 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 
diff erence_type = std::ptr
diff _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 6f2da091c3709b..ad61baa76018da 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 7d30b0adcc234c..8ee2c5006d31c4 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 397226552edee0..23eea7f369c170 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 73ad58f4eecfb6..a9c18e8a1b77a1 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**)


        


More information about the libcxx-commits mailing list