[libcxx-commits] [libcxx] [libc++] Implement `views::join_with` (PR #65536)

Jakub Mazurkiewicz via libcxx-commits libcxx-commits at lists.llvm.org
Thu Sep 7 17:07:53 PDT 2023


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

>From 0481670fb6749cc61b72a92ab80899139bfcb576 Mon Sep 17 00:00:00 2001
From: Jakub Mazurkiewicz <mazkuba3 at gmail.com>
Date: Wed, 6 Sep 2023 02:00:32 +0200
Subject: [PATCH] [libc++] Implement `views::join_with`

* Partially implements P2441R2: `views::join_with` (https://wg21.link/P2441R2):
  * `views::join_with` cannot be used in constant expression because P2231R1 hasn't been implemented,
  * Feature test macro `__cpp_lib_ranges_join_with` is not included,
* Completes implementation of P2711R1: "Making multi-param constructors of views explicit" (https://wg21.link/P2711R1),
* Implements `[range.join.with.*]` parts of P2770R0: "Stashing stashing iterators for proper flattening" (https://wg21.link/P2770R0),
* Renames `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).
* Adds `__ranges/helpers.h` file for helper functions.
---
 libcxx/docs/ReleaseNotes/18.rst               |   1 +
 libcxx/docs/Status/Cxx23.rst                  |   5 +-
 libcxx/docs/Status/Cxx23Papers.csv            |   6 +-
 libcxx/docs/Status/RangesMajorFeatures.csv    |   1 +
 libcxx/docs/Status/RangesViews.csv            |   4 +-
 libcxx/include/CMakeLists.txt                 |   2 +
 libcxx/include/__ranges/helpers.h             |  39 ++
 libcxx/include/__ranges/join_with_view.h      | 463 ++++++++++++++++++
 libcxx/include/module.modulemap.in            |   2 +
 libcxx/include/ranges                         |  14 +
 libcxx/modules/std/ranges.inc                 |   6 +-
 .../range.adaptor.helpers/as-lvalue.cpp       |  47 ++
 .../tuple-for-each.pass.cpp                   |   0
 .../adaptor.nodiscard.verify.cpp              |  31 ++
 .../base.nodiscard.verify.cpp                 |  28 ++
 .../test/libcxx/transitive_includes/cxx03.csv |   1 +
 .../test/libcxx/transitive_includes/cxx11.csv |   1 +
 .../test/libcxx/transitive_includes/cxx14.csv |   1 +
 .../test/libcxx/transitive_includes/cxx17.csv |   1 +
 .../test/libcxx/transitive_includes/cxx20.csv |   1 +
 .../range.join.with.overview/adaptor.pass.cpp | 361 ++++++++++++++
 .../range.join.with.overview/example.pass.cpp |  46 ++
 .../range.join.with.view/base.pass.cpp        | 149 ++++++
 .../constraints.compile.pass.cpp              | 269 ++++++++++
 .../range.join.with.view/ctad.pass.cpp        | 142 ++++++
 .../ctor.default.pass.cpp                     |  92 ++++
 .../ctor.view_pattern.pass.cpp                | 128 +++++
 .../range.join.with.view/helpers.h            |  22 +
 .../inheritance.compile.pass.cpp              |  38 ++
 29 files changed, 1892 insertions(+), 9 deletions(-)
 create mode 100644 libcxx/include/__ranges/helpers.h
 create mode 100644 libcxx/include/__ranges/join_with_view.h
 create mode 100644 libcxx/test/libcxx/ranges/range.adaptors/range.adaptor.helpers/as-lvalue.cpp
 rename libcxx/test/libcxx/ranges/range.adaptors/{range.adaptor.tuple => range.adaptor.helpers}/tuple-for-each.pass.cpp (100%)
 create mode 100644 libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.overview/adaptor.nodiscard.verify.cpp
 create mode 100644 libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.view/base.nodiscard.verify.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.overview/adaptor.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.overview/example.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/base.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/constraints.compile.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctad.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctor.default.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctor.view_pattern.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/helpers.h
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/inheritance.compile.pass.cpp

diff --git a/libcxx/docs/ReleaseNotes/18.rst b/libcxx/docs/ReleaseNotes/18.rst
index b10c8fa78c2270d..c090167c5e6e7e0 100644
--- a/libcxx/docs/ReleaseNotes/18.rst
+++ b/libcxx/docs/ReleaseNotes/18.rst
@@ -42,6 +42,7 @@ Implemented Papers
 - P2497R0 - Testing for success or failure of ``<charconv>`` functions
 - P2697R1 - Interfacing ``bitset`` with ``string_view``
 - P2443R1 - ``views::chunk_by``
+- P2711R1 - Making multi-param constructors of ``views`` ``explicit``
 
 Improvements and New Features
 -----------------------------
diff --git a/libcxx/docs/Status/Cxx23.rst b/libcxx/docs/Status/Cxx23.rst
index 5b4d9a6fe943d45..e8637f65ad5d2a7 100644
--- a/libcxx/docs/Status/Cxx23.rst
+++ b/libcxx/docs/Status/Cxx23.rst
@@ -45,7 +45,8 @@ Paper Status
    .. [#note-P1413R3] P1413R3: ``std::aligned_storage_t`` and ``std::aligned_union_t`` are marked deprecated, but
       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-P2441R2] P2441R2: ``views::join_with`` cannot be used in constant expressions (depends on P2231R1). Feature test macro is not defined.
+   .. [#note-P2770R0] P2770R0: ``views::join_with`` part only.
    .. [#note-P2693R1] P2693R1: The formatter for ``std::thread::id`` is implemented.
       The formatter for ``stacktrace`` is not implemented, since ``stacktrace`` is
       not implemented yet.
@@ -63,4 +64,4 @@ Library Working Group Issues Status
 .. note::
 
    .. [#note-LWG3750] LWG3750 Only ``__cpp_lib_format_ranges`` is fully implemented.
-   .. [#note-LWG3798] LWG3798: ``join_with_view``, ``zip_transform_view``, and ``adjacent_transform_view`` haven't been done yet since these types aren't implemented yet.
+   .. [#note-LWG3798] LWG3798: ``zip_transform_view`` and ``adjacent_transform_view`` haven't been done yet since these types aren't implemented yet.
diff --git a/libcxx/docs/Status/Cxx23Papers.csv b/libcxx/docs/Status/Cxx23Papers.csv
index ba26cebbc1e8a67..a8c8120cdfaec66 100644
--- a/libcxx/docs/Status/Cxx23Papers.csv
+++ b/libcxx/docs/Status/Cxx23Papers.csv
@@ -47,7 +47,7 @@
 "`P2273R3 <https://wg21.link/P2273R3>`__","LWG","Making ``std::unique_ptr`` constexpr","February 2022","|Complete|","16.0"
 "`P2387R3 <https://wg21.link/P2387R3>`__","LWG","Pipe support for user-defined range adaptors","February 2022","","","|ranges|"
 "`P2440R1 <https://wg21.link/P2440R1>`__","LWG","``ranges::iota``, ``ranges::shift_left`` and ``ranges::shift_right``","February 2022","","","|ranges|"
-"`P2441R2 <https://wg21.link/P2441R2>`__","LWG","``views::join_with``","February 2022","","","|ranges|"
+"`P2441R2 <https://wg21.link/P2441R2>`__","LWG","``views::join_with``","February 2022","|Partial| [#note-P2441R2]_","","|ranges|"
 "`P2442R1 <https://wg21.link/P2442R1>`__","LWG","Windowing range adaptors: ``views::chunk`` and ``views::slide``","February 2022","","","|ranges|"
 "`P2443R1 <https://wg21.link/P2443R1>`__","LWG","``views::chunk_by``","February 2022","|Complete|","18.0","|ranges|"
 "","","","","","",""
@@ -104,9 +104,9 @@
 "`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","","","|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","|Partial| [#note-P2711R1]_","","|ranges|"
+"`P2711R1 <https://wg21.link/P2711R1>`__","LWG", "Making multi-param constructors of ``views`` ``explicit``","February 2023","|Complete|","18.0","|ranges|"
 "`P2609R3 <https://wg21.link/P2609R3>`__","LWG", "Relaxing Ranges Just A Smidge","February 2023","","","|ranges|"
 "`P2713R1 <https://wg21.link/P2713R1>`__","LWG", "Escaping improvements in ``std::format``","February 2023","","","|format|"
 "`P2675R1 <https://wg21.link/P2675R1>`__","LWG", "``format``'s width estimation is too approximate and not forward compatible","February 2023","|Complete|","17.0","|format|"
diff --git a/libcxx/docs/Status/RangesMajorFeatures.csv b/libcxx/docs/Status/RangesMajorFeatures.csv
index 259a0218ce15e32..64a8a9ed27e1dcc 100644
--- a/libcxx/docs/Status/RangesMajorFeatures.csv
+++ b/libcxx/docs/Status/RangesMajorFeatures.csv
@@ -2,3 +2,4 @@ Standard,Name,Assignee,CL,Status
 C++23,`ranges::to <https://wg21.link/P1206R7>`_,Konstantin Varlamov,`D142335 <https://reviews.llvm.org/D142335>`_,Complete
 C++23,`Pipe support for user-defined range adaptors <https://wg21.link/P2387R3>`_,Unassigned,No patch yet,Not started
 C++23,`Formatting Ranges <https://wg21.link/P2286R8>`_,Mark de Wever,Various,Complete
+C++23,`Stashing stashing iterators for proper flattening <https://wg21.link/P2770R0>`_,Jakub Mazurkiewicz,`GitHub #65536 <https://github.com/llvm/llvm-project/pull/65536>`_,In progress
diff --git a/libcxx/docs/Status/RangesViews.csv b/libcxx/docs/Status/RangesViews.csv
index d7ae06932fb0c55..a0f2ca7be3195c3 100644
--- a/libcxx/docs/Status/RangesViews.csv
+++ b/libcxx/docs/Status/RangesViews.csv
@@ -28,10 +28,10 @@ C++23,`zip <https://wg21.link/P2321R2>`_,Hui Xie,`D122806 <https://llvm.org/D122
 C++23,`zip_transform <https://wg21.link/P2321R2>`_,Hui Xie,No patch yet,Not started
 C++23,`adjacent <https://wg21.link/P2321R2>`_,Hui Xie,No patch yet,Not started
 C++23,`adjacent_transform <https://wg21.link/P2321R2>`_,Hui Xie,No patch yet,Not started
-C++23,`join_with <https://wg21.link/P2441R2>`_,Unassigned,No patch yet,Not started
+C++23,`join_with <https://wg21.link/P2441R2>`_,Jakub Mazurkiewicz,`GitHub #65536 <https://github.com/llvm/llvm-project/pull/65536>`_,✅
 C++23,`slide <https://wg21.link/P2442R1>`_,Unassigned,No patch yet,Not started
 C++23,`chunk <https://wg21.link/P2442R1>`_,Unassigned,No patch yet,Not started
-C++23,`chunk_by <https://wg21.link/P2443R1>`_,Jakub Mazurkiewicz,`D144767 <https://llvm.org/D144767>`,✅
+C++23,`chunk_by <https://wg21.link/P2443R1>`_,Jakub Mazurkiewicz,`D144767 <https://llvm.org/D144767>`_,✅
 C++23,`as_const <https://wg21.link/P2278R4>`_,Unassigned,No patch yet,Not started
 C++23,`as_rvalue <https://wg21.link/P2446R2>`_,Nikolas Klauser,`D137637 <https://llvm.org/D137637>`_,✅
 C++23,`stride <https://wg21.link/P1899R3>`_,Hristo Hristov and Will Hawkins,`D156924 <https://llvm.org/D156924>`_,In Progress
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index d9f6311d9b48eb6..f65d574c6e250f4 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -620,9 +620,11 @@ set(files
   __ranges/enable_view.h
   __ranges/filter_view.h
   __ranges/from_range.h
+  __ranges/helpers.h
   __ranges/iota_view.h
   __ranges/istream_view.h
   __ranges/join_view.h
+  __ranges/join_with_view.h
   __ranges/lazy_split_view.h
   __ranges/movable_box.h
   __ranges/non_propagating_cache.h
diff --git a/libcxx/include/__ranges/helpers.h b/libcxx/include/__ranges/helpers.h
new file mode 100644
index 000000000000000..269f5447bd4aa67
--- /dev/null
+++ b/libcxx/include/__ranges/helpers.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___RANGES_HELPERS_H
+#define _LIBCPP___RANGES_HELPERS_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 >= 23
+
+namespace ranges {
+template <class _Tp>
+_LIBCPP_HIDE_FROM_ABI constexpr _Tp& __as_lvalue(_Tp&& __t) {
+  return static_cast<_Tp&>(__t);
+}
+} // namespace ranges
+
+#endif // _LIBCPP_STD_VER >= 23
+
+_LIBCPP_END_NAMESPACE_STD
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___RANGES_HELPERS_H
diff --git a/libcxx/include/__ranges/join_with_view.h b/libcxx/include/__ranges/join_with_view.h
new file mode 100644
index 000000000000000..7471aa457bbe904
--- /dev/null
+++ b/libcxx/include/__ranges/join_with_view.h
@@ -0,0 +1,463 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___RANGES_JOIN_WITH_VIEW_H
+#define _LIBCPP___RANGES_JOIN_WITH_VIEW_H
+
+#include <__concepts/common_reference_with.h>
+#include <__concepts/common_with.h>
+#include <__concepts/constructible.h>
+#include <__concepts/convertible_to.h>
+#include <__concepts/derived_from.h>
+#include <__config>
+#include <__functional/bind_back.h>
+#include <__iterator/concepts.h>
+#include <__iterator/incrementable_traits.h>
+#include <__iterator/iter_move.h>
+#include <__iterator/iter_swap.h>
+#include <__iterator/iterator_traits.h>
+#include <__memory/addressof.h>
+#include <__ranges/access.h>
+#include <__ranges/all.h>
+#include <__ranges/concepts.h>
+#include <__ranges/helpers.h>
+#include <__ranges/non_propagating_cache.h>
+#include <__ranges/single_view.h>
+#include <__ranges/view_interface.h>
+#include <__type_traits/conditional.h>
+#include <__type_traits/decay.h>
+#include <__type_traits/is_reference.h>
+#include <__type_traits/maybe_const.h>
+#include <__utility/forward.h>
+#include <__utility/move.h>
+#include <variant>
+
+#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 >= 23
+
+namespace ranges {
+template <class _Range, class _Pattern>
+concept __compatible_joinable_ranges =
+    common_with<range_value_t<_Range>, range_value_t<_Pattern>> &&
+    common_reference_with<range_reference_t<_Range>, range_reference_t<_Pattern>> &&
+    common_reference_with<range_rvalue_reference_t<_Range>, range_rvalue_reference_t<_Pattern>>;
+
+template <class _Range>
+concept __bidirectional_common = bidirectional_range<_Range> && common_range<_Range>;
+
+template <input_range _View, forward_range _Pattern>
+  requires view<_View> && input_range<range_reference_t<_View>> && view<_Pattern> &&
+           __compatible_joinable_ranges<range_reference_t<_View>, _Pattern>
+class join_with_view : public view_interface<join_with_view<_View, _Pattern>> {
+  using _InnerRng = range_reference_t<_View>;
+
+  _LIBCPP_NO_UNIQUE_ADDRESS _View __base_ = _View();
+
+  static constexpr bool _UseOuterItCache = !forward_range<_View>;
+  using _OuterItCache = _If<_UseOuterItCache, __non_propagating_cache<iterator_t<_View>>, __empty_cache>;
+  _LIBCPP_NO_UNIQUE_ADDRESS _OuterItCache __outer_it_;
+
+  static constexpr bool _UseInnerCache = !is_reference_v<_InnerRng>;
+  using _InnerCache = _If<_UseInnerCache, __non_propagating_cache<remove_cvref_t<_InnerRng>>, __empty_cache>;
+  _LIBCPP_NO_UNIQUE_ADDRESS _InnerCache __inner_;
+
+  _LIBCPP_NO_UNIQUE_ADDRESS _Pattern __pattern_ = _Pattern();
+
+  template <bool _Const>
+  struct __iterator;
+
+  template <bool _Const>
+  struct __sentinel;
+
+public:
+  _LIBCPP_HIDE_FROM_ABI join_with_view()
+    requires default_initializable<_View> && default_initializable<_Pattern>
+  = default;
+
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit join_with_view(_View __base, _Pattern __pattern)
+      : __base_(std::move(__base)), __pattern_(std::move(__pattern)) {}
+
+  template <input_range _Range>
+    requires constructible_from<_View, views::all_t<_Range>> &&
+                 constructible_from<_Pattern, single_view<range_value_t<_InnerRng>>>
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit join_with_view(_Range&& __r, range_value_t<_InnerRng> __e)
+      : __base_(views::all(std::forward<_Range>(__r))), __pattern_(views::single(std::move(__e))) {}
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr _View base() const&
+    requires copy_constructible<_View>
+  {
+    return __base_;
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr _View base() && { return std::move(__base_); }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto begin() {
+    if constexpr (forward_range<_View>) {
+      constexpr bool __use_const = __simple_view<_View> && is_reference_v<_InnerRng> && __simple_view<_Pattern>;
+      return __iterator<__use_const>{*this, ranges::begin(__base_)};
+    } else {
+      __outer_it_ = ranges::begin(__base_);
+      return __iterator<false>{*this};
+    }
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto begin() const
+    requires forward_range<const _View> && forward_range<const _Pattern> &&
+             is_reference_v<range_reference_t<const _View>> && input_range<range_reference_t<const _View>>
+  {
+    return __iterator<true>{*this, ranges::begin(__base_)};
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto end() {
+    constexpr bool __use_const = __simple_view<_View> && __simple_view<_Pattern>;
+    if constexpr (forward_range<_View> && is_reference_v<_InnerRng> && forward_range<_InnerRng> &&
+                  common_range<_View> && common_range<_InnerRng>) {
+      return __iterator<__use_const>{*this, ranges::end(__base_)};
+    } else {
+      return __sentinel<__use_const>{*this};
+    }
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto end() const
+    requires forward_range<const _View> && forward_range<const _Pattern> &&
+             is_reference_v<range_reference_t<const _View>> && input_range<range_reference_t<const _View>>
+  {
+    using _InnerConstRng = range_reference_t<const _View>;
+    if constexpr (forward_range<_InnerConstRng> && common_range<const _View> && common_range<_InnerConstRng>) {
+      return __iterator<true>{*this, ranges::end(__base_)};
+    } else {
+      return __sentinel<true>{*this};
+    }
+  }
+};
+
+template <class _Range, class _Pattern>
+join_with_view(_Range&&, _Pattern&&) -> join_with_view<views::all_t<_Range>, views::all_t<_Pattern>>;
+
+template <input_range _Range>
+join_with_view(_Range&&, range_value_t<range_reference_t<_Range>>)
+    -> join_with_view<views::all_t<_Range>, single_view<range_value_t<range_reference_t<_Range>>>>;
+
+template <class _Base, class _PatternBase>
+struct __join_with_view_iterator_category {};
+
+template <class _Base, class _PatternBase>
+  requires is_reference_v<range_reference_t<_Base>> && forward_range<_Base> && forward_range<range_reference_t<_Base>>
+struct __join_with_view_iterator_category<_Base, _PatternBase> {
+private:
+  using _OuterC   = iterator_traits<iterator_t<_Base>>::iterator_category;
+  using _InnerC   = iterator_traits<iterator_t<range_reference_t<_Base>>>::iterator_category;
+  using _PatternC = iterator_traits<iterator_t<_PatternBase>>::iterator_category;
+
+  static consteval auto __get_iterator_category() noexcept {
+    if constexpr (!is_reference_v<common_reference_t<iter_reference_t<iterator_t<range_reference_t<_Base>>>,
+                                                     iter_reference_t<iterator_t<_PatternBase>>>>) {
+      return input_iterator_tag{};
+    } else if constexpr (derived_from<_OuterC, bidirectional_iterator_tag> &&
+                         derived_from<_InnerC, bidirectional_iterator_tag> &&
+                         derived_from<_PatternC, bidirectional_iterator_tag> &&
+                         common_range<range_reference_t<_Base>> && common_range<_PatternC>) {
+      return bidirectional_iterator_tag{};
+    } else if constexpr (derived_from<_OuterC, forward_iterator_tag> && derived_from<_InnerC, forward_iterator_tag> &&
+                         derived_from<_PatternC, forward_iterator_tag>) {
+      return forward_iterator_tag{};
+    } else {
+      return input_iterator_tag{};
+    }
+  }
+
+public:
+  using iterator_category = decltype(__get_iterator_category());
+};
+
+template <input_range _View, forward_range _Pattern>
+  requires view<_View> && input_range<range_reference_t<_View>> && view<_Pattern> &&
+           __compatible_joinable_ranges<range_reference_t<_View>, _Pattern>
+template <bool _Const>
+struct join_with_view<_View, _Pattern>::__iterator : public __join_with_view_iterator_category<_View, _Pattern> {
+private:
+  friend join_with_view;
+
+  using _Parent      = __maybe_const<_Const, join_with_view>;
+  using _Base        = __maybe_const<_Const, _View>;
+  using _InnerBase   = range_reference_t<_Base>;
+  using _PatternBase = __maybe_const<_Const, _Pattern>;
+
+  using _OuterIter   = iterator_t<_Base>;
+  using _InnerIter   = iterator_t<_InnerBase>;
+  using _PatternIter = iterator_t<_PatternBase>;
+
+  static constexpr bool __ref_is_glvalue = is_reference_v<_InnerBase>;
+
+  _Parent* __parent_ = nullptr;
+
+  struct __no_outer_iter {}; // TODO use 'std::__empty' from D154238
+
+  static constexpr bool _OuterIterPresent              = forward_range<_Base>;
+  using _OuterIterType                                 = _If<_OuterIterPresent, _OuterIter, __no_outer_iter>;
+  _LIBCPP_NO_UNIQUE_ADDRESS _OuterIterType __outer_it_ = _OuterIterType();
+
+  variant<_PatternIter, _InnerIter> __inner_it_;
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator(_Parent& __parent, _OuterIter __outer)
+    requires forward_range<_Base>
+      : __parent_(std::addressof(__parent)), __outer_it_(std::move(__outer)) {
+    if (__get_outer() != ranges::end(__parent_->__base_)) {
+      __inner_it_.template emplace<1>(ranges::begin(__update_inner()));
+      __satisfy();
+    }
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit __iterator(_Parent& __parent)
+    requires(!forward_range<_Base>)
+      : __parent_(std::addressof(__parent)) {
+    if (__get_outer() != ranges::end(__parent_->__base_)) {
+      __inner_it_.template emplace<1>(ranges::begin(__update_inner()));
+      __satisfy();
+    }
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr _OuterIter& __get_outer() {
+    if constexpr (forward_range<_Base>) {
+      return __outer_it_;
+    } else {
+      return *__parent_->__outer_it_;
+    }
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr const _OuterIter& __get_outer() const {
+    if constexpr (forward_range<_Base>) {
+      return __outer_it_;
+    } else {
+      return *__parent_->__outer_it_;
+    }
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr auto& __update_inner() {
+    if constexpr (__ref_is_glvalue) {
+      return __as_lvalue(*__get_outer());
+    } else {
+      return __parent_->__inner_.__emplace_from([&]() -> decltype(auto) { return *__get_outer(); });
+    }
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr auto& __get_inner() {
+    if constexpr (__ref_is_glvalue) {
+      return __as_lvalue(*__get_outer());
+    } else {
+      return *__parent_->__inner_;
+    }
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr void __satisfy() {
+    while (true) {
+      if (__inner_it_.index() == 0) {
+        if (std::get<0>(__inner_it_) != ranges::end(__parent_->__pattern_))
+          break;
+
+        __inner_it_.template emplace<1>(ranges::begin(__update_inner()));
+      } else {
+        if (std::get<1>(__inner_it_) != ranges::end(__get_inner()))
+          break;
+
+        if (++__get_outer() == ranges::end(__parent_->__base_)) {
+          if constexpr (__ref_is_glvalue)
+            __inner_it_.template emplace<0>();
+
+          break;
+        }
+
+        __inner_it_.template emplace<0>(ranges::begin(__parent_->__pattern_));
+      }
+    }
+  }
+
+  static consteval auto __get_iterator_concept() noexcept {
+    if constexpr (__ref_is_glvalue && bidirectional_range<_Base> && __bidirectional_common<_InnerBase> &&
+                  __bidirectional_common<_PatternBase>) {
+      return bidirectional_iterator_tag{};
+    } else if constexpr (__ref_is_glvalue && forward_range<_Base> && forward_range<_InnerBase>) {
+      return forward_iterator_tag{};
+    } else {
+      return input_iterator_tag{};
+    }
+  }
+
+public:
+  using iterator_concept = decltype(__get_iterator_concept());
+  using value_type       = common_type_t<iter_value_t<_InnerIter>, iter_value_t<_PatternIter>>;
+  using difference_type =
+      common_type_t<iter_difference_t<_OuterIter>, iter_difference_t<_InnerIter>, iter_difference_t<_PatternIter>>;
+
+  _LIBCPP_HIDE_FROM_ABI __iterator() = default;
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator(__iterator<!_Const> __i)
+    requires _Const && convertible_to<iterator_t<_View>, _OuterIter> &&
+                 convertible_to<iterator_t<_InnerRng>, _InnerIter> && convertible_to<iterator_t<_Pattern>, _PatternIter>
+      : __outer_it_(std::move(__i.__outer_it_)), __parent_(__i.__parent_) {
+    if (__i.__inner_it_.index() == 0) {
+      __inner_it_.template emplace<0>(std::get<0>(std::move(__i.__inner_it_)));
+    } else {
+      __inner_it_.template emplace<1>(std::get<1>(std::move(__i.__inner_it_)));
+    }
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr decltype(auto) operator*() const {
+    using __reference = common_reference_t<iter_reference_t<_InnerIter>, iter_reference_t<_PatternIter>>;
+    return std::visit([](auto& __it) -> __reference { return *__it; }, __inner_it_);
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator++() {
+    std::visit([](auto& __it) { ++__it; }, __inner_it_);
+    __satisfy();
+    return *this;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr void operator++(int) { ++*this; }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator operator++(int)
+    requires __ref_is_glvalue && forward_iterator<_OuterIter> && forward_iterator<_InnerIter>
+  {
+    __iterator __tmp = *this;
+    ++*this;
+    return __tmp;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator--()
+    requires __ref_is_glvalue
+          && bidirectional_range<_Base> && __bidirectional_common<_InnerBase> && __bidirectional_common<_PatternBase>
+  {
+    if (__outer_it_ == ranges::end(__parent_->__base_)) {
+      auto&& __inner = *--__outer_it_;
+      __inner_it_.template emplace<1>(ranges::end(__inner));
+    }
+
+    while (true) {
+      if (__inner_it_.index() == 0) {
+        auto& __it = std::get<0>(__inner_it_);
+        if (__it == ranges::begin(__parent_->__pattern_)) {
+          auto&& __inner = *--__outer_it_;
+          __inner_it_.template emplace<1>(ranges::end(__inner));
+        } else {
+          break;
+        }
+      } else {
+        auto& __it     = std::get<1>(__inner_it_);
+        auto&& __inner = *__outer_it_;
+        if (__it == ranges::begin(__inner)) {
+          __inner_it_.template emplace<0>(ranges::end(__parent_->__pattern_));
+        } else {
+          break;
+        }
+      }
+    }
+
+    std::visit([](auto& __it) { --__it; }, __inner_it_);
+    return *this;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator operator--(int)
+    requires __ref_is_glvalue
+          && bidirectional_range<_Base> && __bidirectional_common<_InnerBase> && __bidirectional_common<_PatternBase>
+  {
+    __iterator __tmp = *this;
+    --*this;
+    return __tmp;
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI friend constexpr bool
+  operator==(const __iterator& __x, const __iterator& __y)
+    requires __ref_is_glvalue && forward_range<_Base> && equality_comparable<_InnerIter>
+  {
+    return __x.__outer_it_ == __y.__outer_it_ && __x.__inner_it_ == __y.__inner_it_;
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI friend constexpr decltype(auto) iter_move(const __iterator& __x) {
+    using __rvalue_reference =
+        common_reference_t<iter_rvalue_reference_t<_InnerIter>, iter_rvalue_reference_t<_PatternIter>>;
+    return std::visit<__rvalue_reference>(ranges::iter_move, __x.__inner_it_);
+  }
+
+  _LIBCPP_HIDE_FROM_ABI friend constexpr void iter_swap(const __iterator& __x, const __iterator& __y)
+    requires indirectly_swappable<_InnerIter, _PatternIter>
+  {
+    std::visit(ranges::iter_swap, __x.__inner_it_, __y.__inner_it_);
+  }
+};
+
+template <input_range _View, forward_range _Pattern>
+  requires view<_View> && input_range<range_reference_t<_View>> && view<_Pattern> &&
+           __compatible_joinable_ranges<range_reference_t<_View>, _Pattern>
+template <bool _Const>
+struct join_with_view<_View, _Pattern>::__sentinel {
+private:
+  friend join_with_view;
+
+  using _Parent = __maybe_const<_Const, join_with_view>;
+  using _Base   = __maybe_const<_Const, _View>;
+
+  sentinel_t<_Base> __end_ = sentinel_t<_Base>();
+
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit __sentinel(_Parent& __parent) : __end_(ranges::end(__parent.__base_)) {}
+
+public:
+  _LIBCPP_HIDE_FROM_ABI __sentinel() = default;
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __sentinel(__sentinel<!_Const> __s)
+    requires _Const && convertible_to<sentinel_t<_View>, sentinel_t<_Base>>
+      : __end_(std::move(__s.__end_)) {}
+
+  template <bool _OtherConst>
+    requires sentinel_for<sentinel_t<_Base>, iterator_t<__maybe_const<_OtherConst, _View>>>
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI friend constexpr bool
+  operator==(const __iterator<_OtherConst>& __x, const __sentinel& __y) {
+    return __x.__get_outer() == __y.__end_;
+  }
+};
+
+namespace views {
+namespace __join_with_view {
+struct __fn {
+  template <class _Range, class _Pattern>
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Range&& __range, _Pattern&& __pattern) const
+      noexcept(noexcept(/**/ join_with_view(std::forward<_Range>(__range), std::forward<_Pattern>(__pattern))))
+          -> decltype(/*--*/ join_with_view(std::forward<_Range>(__range), std::forward<_Pattern>(__pattern))) {
+    return /*-------------*/ join_with_view(std::forward<_Range>(__range), std::forward<_Pattern>(__pattern));
+  }
+
+  template <class _Pattern>
+    requires constructible_from<decay_t<_Pattern>, _Pattern>
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Pattern&& __pattern) const
+      noexcept(is_nothrow_constructible_v<decay_t<_Pattern>, _Pattern>) {
+    return __range_adaptor_closure_t(std::__bind_back(*this, std::forward<_Pattern>(__pattern)));
+  }
+};
+} // namespace __join_with_view
+
+inline namespace __cpo {
+inline constexpr auto join_with = __join_with_view::__fn{};
+} // namespace __cpo
+} // namespace views
+} // namespace ranges
+
+#endif // _LIBCPP_STD_VER >= 23
+
+_LIBCPP_END_NAMESPACE_STD
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___RANGES_JOIN_WITH_VIEW_H
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index e349386e0fba133..aa5fdeebc17e523 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -1692,6 +1692,7 @@ module std_private_ranges_filter_view                [system] {
   export std_private_ranges_range_adaptor
 }
 module std_private_ranges_from_range                 [system] { header "__ranges/from_range.h" }
+module std_private_ranges_helpers                    [system] { header "__ranges/helpers.h" }
 module std_private_ranges_iota_view                  [system] { header "__ranges/iota_view.h" }
 module std_private_ranges_istream_view               [system] {
   header "__ranges/istream_view.h"
@@ -1701,6 +1702,7 @@ module std_private_ranges_join_view                  [system] {
   export std_private_iterator_iterator_with_data
   export std_private_iterator_segmented_iterator
 }
+module std_private_ranges_join_with_view             [system] { header "__ranges/join_with_view.h" }
 module std_private_ranges_lazy_split_view            [system] {
   header "__ranges/lazy_split_view.h"
   export std_private_ranges_non_propagating_cache
diff --git a/libcxx/include/ranges b/libcxx/include/ranges
index db592fd5cb360c4..38563f0275b38ec 100644
--- a/libcxx/include/ranges
+++ b/libcxx/include/ranges
@@ -280,6 +280,18 @@ namespace std::ranges {
     requires view<V> && input_range<range_reference_t<V>>
   class join_view;
 
+  // [range.join.with], join with view
+  template<class R, class P>
+    concept compatible-joinable-ranges = see below; // exposition only
+
+  template<input_range V, forward_range Pattern>
+    requires view<V> && input_range<range_reference_t<V>>
+          && view<Pattern>
+          && compatible-joinable-ranges<range_reference_t<V>, Pattern>
+  class join_with_view;                                                     // since C++23
+
+  namespace views { inline constexpr unspecified join_with = unspecified; } // since C++23
+
   // [range.lazy.split], lazy split view
   template<class R>
     concept tiny-range = see below;   // exposition only
@@ -395,8 +407,10 @@ namespace std {
 #include <__ranges/enable_view.h>
 #include <__ranges/filter_view.h>
 #include <__ranges/from_range.h>
+#include <__ranges/helpers.h>
 #include <__ranges/iota_view.h>
 #include <__ranges/join_view.h>
+#include <__ranges/join_with_view.h>
 #include <__ranges/lazy_split_view.h>
 #include <__ranges/rbegin.h>
 #include <__ranges/ref_view.h>
diff --git a/libcxx/modules/std/ranges.inc b/libcxx/modules/std/ranges.inc
index a883103d812588b..7f4f5e2818bec44 100644
--- a/libcxx/modules/std/ranges.inc
+++ b/libcxx/modules/std/ranges.inc
@@ -211,13 +211,15 @@ export namespace std {
       using std::ranges::views::join;
     } // namespace views
 #endif // _LIBCPP_ENABLE_EXPERIMENTAL
-#if 0
+
+#if _LIBCPP_STD_VER >= 23
     using std::ranges::join_with_view;
 
     namespace views {
       using std::ranges::views::join_with;
     } // namespace views
-#endif
+#endif // _LIBCPP_STD_VER >= 23
+
     using std::ranges::lazy_split_view;
 
     // [range.split], split view
diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.adaptor.helpers/as-lvalue.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.adaptor.helpers/as-lvalue.cpp
new file mode 100644
index 000000000000000..dca54db1a62b6ae
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.adaptor.helpers/as-lvalue.cpp
@@ -0,0 +1,47 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <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 (reference is dangling)
+  {
+    [[maybe_unused]] std::same_as<int&> decltype(auto) check = std::ranges::__as_lvalue(0);
+  }
+
+  // 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.with/range.join.with.overview/adaptor.nodiscard.verify.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.overview/adaptor.nodiscard.verify.cpp
new file mode 100644
index 000000000000000..bd27e6f27ec6681
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.overview/adaptor.nodiscard.verify.cpp
@@ -0,0 +1,31 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// Test the libc++ extension that std::views::join_with is marked as [[nodiscard]].
+
+#include <ranges>
+
+void test() {
+  int range[][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
+  int pattern_base[] = {-1, -1};
+  auto pattern = std::views::all(pattern_base);
+
+  std::views::join_with(pattern); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::views::join_with(range, pattern); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  range | std::views::join_with(pattern); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::views::all | std::views::join_with(pattern); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+
+  std::views::join_with(0); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::views::join_with(range, 0); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  range | std::views::join_with(0); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::views::all | std::views::join_with(0); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+}
diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.view/base.nodiscard.verify.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.view/base.nodiscard.verify.cpp
new file mode 100644
index 000000000000000..c75ecdce7cfc216
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.view/base.nodiscard.verify.cpp
@@ -0,0 +1,28 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// Test the libc++ extension that std::ranges::join_with_view::base is marked as [[nodiscard]].
+
+#include <ranges>
+#include <utility>
+
+void test() {
+  int range[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
+  int pattern[2]  = {-1, -1};
+
+  std::ranges::join_with_view view(range, pattern);
+
+  view.base(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::as_const(view).base(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::move(std::as_const(view)).base(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::move(view).base(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+}
diff --git a/libcxx/test/libcxx/transitive_includes/cxx03.csv b/libcxx/test/libcxx/transitive_includes/cxx03.csv
index ea1b2662c1cd581..2c81adfe7681c34 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx03.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx03.csv
@@ -694,6 +694,7 @@ ranges optional
 ranges span
 ranges tuple
 ranges type_traits
+ranges variant
 ranges version
 ratio climits
 ratio cstdint
diff --git a/libcxx/test/libcxx/transitive_includes/cxx11.csv b/libcxx/test/libcxx/transitive_includes/cxx11.csv
index 5a6ed875aec21c0..e93b84892529f37 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx11.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx11.csv
@@ -699,6 +699,7 @@ ranges optional
 ranges span
 ranges tuple
 ranges type_traits
+ranges variant
 ranges version
 ratio climits
 ratio cstdint
diff --git a/libcxx/test/libcxx/transitive_includes/cxx14.csv b/libcxx/test/libcxx/transitive_includes/cxx14.csv
index e10d9978c8d8643..7af651851547131 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx14.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx14.csv
@@ -701,6 +701,7 @@ ranges optional
 ranges span
 ranges tuple
 ranges type_traits
+ranges variant
 ranges version
 ratio climits
 ratio cstdint
diff --git a/libcxx/test/libcxx/transitive_includes/cxx17.csv b/libcxx/test/libcxx/transitive_includes/cxx17.csv
index e10d9978c8d8643..7af651851547131 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx17.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx17.csv
@@ -701,6 +701,7 @@ ranges optional
 ranges span
 ranges tuple
 ranges type_traits
+ranges variant
 ranges version
 ratio climits
 ratio cstdint
diff --git a/libcxx/test/libcxx/transitive_includes/cxx20.csv b/libcxx/test/libcxx/transitive_includes/cxx20.csv
index fe0f35888d917a8..fbbc39f3b1fdd62 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx20.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx20.csv
@@ -706,6 +706,7 @@ ranges optional
 ranges span
 ranges tuple
 ranges type_traits
+ranges variant
 ranges version
 ratio climits
 ratio cstdint
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.overview/adaptor.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.overview/adaptor.pass.cpp
new file mode 100644
index 000000000000000..483d86d917bedd0
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.overview/adaptor.pass.cpp
@@ -0,0 +1,361 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// std::views::join_with_view
+
+#include <ranges>
+
+#include <string_view>
+#include <span>
+#include <utility>
+
+#include "test_iterators.h"
+
+template <class View, class T>
+concept CanBePiped = requires(View&& view, T&& t) {
+  { std::forward<View>(view) | std::forward<T>(t) };
+};
+
+struct Range : std::ranges::view_base {
+  using Iterator = forward_iterator<std::string_view*>;
+  using Sentinel = sentinel_wrapper<Iterator>;
+  constexpr explicit Range(std::string_view* b, std::string_view* e) : begin_(b), end_(e) {}
+  constexpr Iterator begin() const { return Iterator(begin_); }
+  constexpr Sentinel end() const { return Sentinel(Iterator(end_)); }
+
+private:
+  std::string_view* begin_;
+  std::string_view* end_;
+};
+
+struct Pattern : std::ranges::view_base {
+  using Iterator = forward_iterator<const char*>;
+  using Sentinel = sentinel_wrapper<Iterator>;
+  static constexpr std::string_view pat{", "};
+
+  constexpr Pattern() = default;
+  constexpr Iterator begin() const { return Iterator(pat.data()); }
+  constexpr Sentinel end() const { return Sentinel(Iterator(pat.data() + pat.size())); }
+};
+
+struct NonCopyablePattern : Pattern {
+  NonCopyablePattern(NonCopyablePattern const&) = delete;
+};
+
+template <typename View>
+constexpr void compareViews(View v, std::string_view list) {
+  auto b1 = v.begin();
+  auto e1 = v.end();
+  auto b2 = list.begin();
+  auto e2 = list.end();
+  for (; b1 != e1 && b2 != e2; ++b1, ++b2) {
+    assert(*b1 == *b2);
+  }
+  assert(b1 == e1);
+  assert(b2 == e2);
+}
+
+constexpr int absoluteValue(int x) { return x < 0 ? -x : x; }
+
+template <class T>
+constexpr const T&& asConstRvalue(T&& t) {
+  return static_cast<T const&&>(t);
+}
+
+constexpr void test_adaptor_with_pattern(std::span<std::string_view> buff) {
+  // Test `views::join_with(pattern)(v)`
+  {
+    using Result = std::ranges::join_with_view<Range, Pattern>;
+    Range const range(buff.data(), buff.data() + buff.size());
+    Pattern pattern;
+
+    {
+      // 'views::join_with(pattern)' - &&
+      std::same_as<Result> decltype(auto) result = std::views::join_with(pattern)(range);
+      compareViews(result, "abcd, ef, ghij, kl");
+    }
+    {
+      // 'views::join_with(pattern)' - const&&
+      std::same_as<Result> decltype(auto) result = asConstRvalue(std::views::join_with(pattern))(range);
+      compareViews(result, "abcd, ef, ghij, kl");
+    }
+    {
+      // 'views::join_with(pattern)' - &
+      auto partial                               = std::views::join_with(pattern);
+      std::same_as<Result> decltype(auto) result = partial(range);
+      compareViews(result, "abcd, ef, ghij, kl");
+    }
+    {
+      // 'views::join_with(pattern)' - const&
+      auto const partial                         = std::views::join_with(pattern);
+      std::same_as<Result> decltype(auto) result = partial(range);
+      compareViews(result, "abcd, ef, ghij, kl");
+    }
+  }
+
+  // Test `v | views::join_with(pattern)`
+  {
+    using Result = std::ranges::join_with_view<Range, Pattern>;
+    Range const range(buff.data(), buff.data() + buff.size());
+    Pattern pattern;
+
+    {
+      // 'views::join_with(pattern)' - &&
+      std::same_as<Result> decltype(auto) result = range | std::views::join_with(pattern);
+      compareViews(result, "abcd, ef, ghij, kl");
+    }
+    {
+      // 'views::join_with(pattern)' - const&&
+      std::same_as<Result> decltype(auto) result = range | asConstRvalue(std::views::join_with(pattern));
+      compareViews(result, "abcd, ef, ghij, kl");
+    }
+    {
+      // 'views::join_with(pattern)' - &
+      auto partial                               = std::views::join_with(pattern);
+      std::same_as<Result> decltype(auto) result = range | partial;
+      compareViews(result, "abcd, ef, ghij, kl");
+    }
+    {
+      // 'views::join_with(pattern)' - const&
+      auto const partial                         = std::views::join_with(pattern);
+      std::same_as<Result> decltype(auto) result = range | partial;
+      compareViews(result, "abcd, ef, ghij, kl");
+    }
+  }
+
+  // Test `views::join_with(v, pattern)` range adaptor object
+  {
+    using Result = std::ranges::join_with_view<Range, Pattern>;
+    Range const range(buff.data(), buff.data() + buff.size());
+    Pattern pattern;
+
+    {
+      // 'views::join_with' - &&
+      auto range_adaptor                         = std::views::join_with;
+      std::same_as<Result> decltype(auto) result = std::move(range_adaptor)(range, pattern);
+      compareViews(result, "abcd, ef, ghij, kl");
+    }
+    {
+      // 'views::join_with' - const&&
+      auto const range_adaptor                   = std::views::join_with;
+      std::same_as<Result> decltype(auto) result = std::move(range_adaptor)(range, pattern);
+      compareViews(result, "abcd, ef, ghij, kl");
+    }
+    {
+      // 'views::join_with' - &
+      auto range_adaptor                         = std::views::join_with;
+      std::same_as<Result> decltype(auto) result = range_adaptor(range, pattern);
+      compareViews(result, "abcd, ef, ghij, kl");
+    }
+    {
+      // 'views::join_with' - const&
+      auto const range_adaptor                   = std::views::join_with;
+      std::same_as<Result> decltype(auto) result = range_adaptor(range, pattern);
+      compareViews(result, "abcd, ef, ghij, kl");
+    }
+  }
+
+  // Test `adaptor | views::join_with(pattern)`
+  {
+    auto pred    = [](std::string_view s) { return s.size() >= 3; };
+    using Result = std::ranges::join_with_view<std::ranges::filter_view<Range, decltype(pred)>, Pattern>;
+    Range const range(buff.data(), buff.data() + buff.size());
+    Pattern pattern;
+
+    {
+      std::same_as<Result> decltype(auto) result = range | std::views::filter(pred) | std::views::join_with(pattern);
+      compareViews(result, "abcd, ghij");
+    }
+    {
+      auto const partial                         = std::views::filter(pred) | std::views::join_with(pattern);
+      std::same_as<Result> decltype(auto) result = range | partial;
+      compareViews(result, "abcd, ghij");
+    }
+  }
+}
+
+constexpr void test_adaptor_with_single_element(std::span<std::string_view> buff) {
+  // Test `views::join_with(element)(v)`
+  {
+    using Result = std::ranges::join_with_view<Range, std::ranges::single_view<char>>;
+    Range const range(buff.data(), buff.data() + buff.size());
+    const char element = '.';
+
+    {
+      // 'views::join_with(element)' - &&
+      std::same_as<Result> decltype(auto) result = std::views::join_with(element)(range);
+      compareViews(result, "abcd.ef.ghij.kl");
+    }
+    {
+      // 'views::join_with(element)' - const&&
+      std::same_as<Result> decltype(auto) result = asConstRvalue(std::views::join_with(element))(range);
+      compareViews(result, "abcd.ef.ghij.kl");
+    }
+    {
+      // 'views::join_with(element)' - &
+      auto partial                               = std::views::join_with(element);
+      std::same_as<Result> decltype(auto) result = partial(range);
+      compareViews(result, "abcd.ef.ghij.kl");
+    }
+    {
+      // 'views::join_with(element)' - const&
+      auto const partial                         = std::views::join_with(element);
+      std::same_as<Result> decltype(auto) result = partial(range);
+      compareViews(result, "abcd.ef.ghij.kl");
+    }
+  }
+
+  // Test `v | views::join_with(element)`
+  {
+    using Result = std::ranges::join_with_view<Range, std::ranges::single_view<char>>;
+    Range const range(buff.data(), buff.data() + buff.size());
+    const char element = '.';
+
+    {
+      // 'views::join_with(element)' - &&
+      std::same_as<Result> decltype(auto) result = range | std::views::join_with(element);
+      compareViews(result, "abcd.ef.ghij.kl");
+    }
+    {
+      // 'views::join_with(element)' - const&&
+      std::same_as<Result> decltype(auto) result = range | asConstRvalue(std::views::join_with(element));
+      compareViews(result, "abcd.ef.ghij.kl");
+    }
+    {
+      // 'views::join_with(element)' - &
+      auto partial                               = std::views::join_with(element);
+      std::same_as<Result> decltype(auto) result = range | partial;
+      compareViews(result, "abcd.ef.ghij.kl");
+    }
+    {
+      // 'views::join_with(element)' - const&
+      auto const partial                         = std::views::join_with(element);
+      std::same_as<Result> decltype(auto) result = range | partial;
+      compareViews(result, "abcd.ef.ghij.kl");
+    }
+  }
+
+  // Test `views::join_with(v, element)` range adaptor object
+  {
+    using Result = std::ranges::join_with_view<Range, std::ranges::single_view<char>>;
+    Range const range(buff.data(), buff.data() + buff.size());
+    const char element = '.';
+
+    {
+      // 'views::join_with' - &&
+      auto range_adaptor                         = std::views::join_with;
+      std::same_as<Result> decltype(auto) result = std::move(range_adaptor)(range, element);
+      compareViews(result, "abcd.ef.ghij.kl");
+    }
+    {
+      // 'views::join_with' - const&&
+      auto const range_adaptor                   = std::views::join_with;
+      std::same_as<Result> decltype(auto) result = std::move(range_adaptor)(range, element);
+      compareViews(result, "abcd.ef.ghij.kl");
+    }
+    {
+      // 'views::join_with' - &
+      auto range_adaptor                         = std::views::join_with;
+      std::same_as<Result> decltype(auto) result = range_adaptor(range, element);
+      compareViews(result, "abcd.ef.ghij.kl");
+    }
+    {
+      // 'views::join_with' - const&
+      auto const range_adaptor                   = std::views::join_with;
+      std::same_as<Result> decltype(auto) result = range_adaptor(range, element);
+      compareViews(result, "abcd.ef.ghij.kl");
+    }
+  }
+
+  // Test `adaptor | views::join_with(element)`
+  {
+    auto pred = [](std::string_view s) { return s.size() >= 3; };
+    using Result =
+        std::ranges::join_with_view<std::ranges::filter_view<Range, decltype(pred)>, std::ranges::single_view<char>>;
+    Range const range(buff.data(), buff.data() + buff.size());
+    const char element = '.';
+
+    {
+      std::same_as<Result> decltype(auto) result = range | std::views::filter(pred) | std::views::join_with(element);
+      compareViews(result, "abcd.ghij");
+    }
+    {
+      auto const partial                         = std::views::filter(pred) | std::views::join_with(element);
+      std::same_as<Result> decltype(auto) result = range | partial;
+      compareViews(result, "abcd.ghij");
+    }
+  }
+}
+
+constexpr bool test() {
+  std::string_view buff[] = {"abcd", "ef", "ghij", "kl"};
+
+  // Test range adaptor object
+  {
+    using RangeAdaptorObject = decltype(std::views::join_with);
+    static_assert(std::is_const_v<RangeAdaptorObject>);
+
+    // The type of a customization point object, ignoring cv-qualifiers, shall model semiregular
+    static_assert(std::semiregular<std::remove_const<RangeAdaptorObject>>);
+  }
+
+  test_adaptor_with_pattern(buff);
+  test_adaptor_with_single_element(buff);
+
+  // Test that one can call std::views::join_with with arbitrary stuff, as long as we
+  // don't try to actually complete the call by passing it a range.
+  //
+  // That makes no sense and we can't do anything with the result, but it's valid.
+  {
+    long array[3]                 = {1, 2, 3};
+    [[maybe_unused]] auto partial = std::views::join_with(std::move(array));
+  }
+
+  // Test SFINAE friendliness
+  {
+    struct NotAView {};
+
+    static_assert(!CanBePiped<Range, decltype(std::views::join_with)>);
+    static_assert(CanBePiped<Range, decltype(std::views::join_with(Pattern{}))>);
+    static_assert(CanBePiped<Range, decltype(std::views::join_with('.'))>);
+    static_assert(!CanBePiped<NotAView, decltype(std::views::join_with(Pattern{}))>);
+    static_assert(!CanBePiped<NotAView, decltype(std::views::join_with('.'))>);
+    static_assert(!CanBePiped<std::initializer_list<char>, decltype(std::views::join_with(Pattern{}))>);
+    static_assert(!CanBePiped<std::initializer_list<char>, decltype(std::views::join_with('.'))>);
+    static_assert(!CanBePiped<Range, decltype(std::views::join_with(NotAView{}))>);
+
+    static_assert(!std::is_invocable_v<decltype(std::views::join_with)>);
+    static_assert(!std::is_invocable_v<decltype(std::views::join_with), Pattern, Range>);
+    static_assert(!std::is_invocable_v<decltype(std::views::join_with), char, Range>);
+    static_assert(std::is_invocable_v<decltype(std::views::join_with), Range, Pattern>);
+    static_assert(std::is_invocable_v<decltype(std::views::join_with), Range, char>);
+    static_assert(!std::is_invocable_v<decltype(std::views::join_with), Range, Pattern, Pattern>);
+    static_assert(!std::is_invocable_v<decltype(std::views::join_with), Range, char, char>);
+    static_assert(!std::is_invocable_v<decltype(std::views::join_with), NonCopyablePattern>);
+  }
+
+  {
+    static_assert(std::is_same_v<decltype(std::ranges::views::join_with), decltype(std::views::join_with)>);
+    assert(std::addressof(std::ranges::views::join_with) == std::addressof(std::views::join_with));
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+#if __cpp_lib_variant >= 202106 // TODO RANGES Remove when P2231R1 is implemented.
+  static_assert(test());
+#endif
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.overview/example.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.overview/example.pass.cpp
new file mode 100644
index 000000000000000..e411676c464d623
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.overview/example.pass.cpp
@@ -0,0 +1,46 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// [Example 1:
+//   vector<string> vs = {"the", "quick", "brown", "fox"};
+//   for (char c : vs | views::join_with('-')) {
+//     cout << c;
+//   }
+//   // The above prints the-quick-brown-fox
+
+#include <ranges>
+
+#include <algorithm>
+#include <cassert>
+#include <string>
+#include <string_view>
+#include <vector>
+
+using namespace std::string_view_literals;
+
+constexpr bool test() {
+  std::array<std::string_view, 4> vs = {"the", "quick", "brown", "fox"};
+  std::string result;
+  for (char c : vs | std::views::join_with('-'))
+    result += c;
+
+  return std::ranges::equal(result, "the-quick-brown-fox"sv);
+}
+
+int main(int, char**) {
+  assert(test());
+#if __cpp_lib_variant >= 202106 // TODO RANGES Remove when P2231R1 is implemented.
+  static_assert(test());
+#endif
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/base.pass.cpp
new file mode 100644
index 000000000000000..784d248061c292c
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/base.pass.cpp
@@ -0,0 +1,149 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// constexpr View base() const& requires copy_constructible<View>;
+// constexpr View base() &&;
+
+#include <ranges>
+
+#include <cassert>
+#include <concepts>
+#include <utility>
+#include <vector>
+
+using InnerRange = std::vector<int>;
+
+struct Range : std::ranges::view_base {
+  constexpr explicit Range(InnerRange* b, InnerRange* e) : begin_(b), end_(e) {}
+  constexpr Range(Range const& other) : begin_(other.begin_), end_(other.end_), wasCopyInitialized(true) {}
+  constexpr Range(Range&& other) : begin_(other.begin_), end_(other.end_), wasMoveInitialized(true) {}
+  Range& operator=(Range const&) = default;
+  Range& operator=(Range&&)      = default;
+  constexpr InnerRange* begin() const { return begin_; }
+  constexpr InnerRange* end() const { return end_; }
+
+  InnerRange* begin_;
+  InnerRange* end_;
+  bool wasCopyInitialized = false;
+  bool wasMoveInitialized = false;
+};
+
+static_assert(std::ranges::view<Range>);
+static_assert(std::ranges::input_range<Range>);
+
+struct Pattern : std::ranges::view_base {
+  static constexpr int pat[2] = {0, 0};
+  constexpr const int* begin() const { return pat; }
+  constexpr const int* end() const { return pat + 2; }
+};
+
+static_assert(std::ranges::view<Pattern>);
+static_assert(std::ranges::forward_range<Pattern>);
+
+template <class Tp>
+struct NonCopyableRange : std::ranges::view_base {
+  NonCopyableRange(NonCopyableRange const&)            = delete;
+  NonCopyableRange(NonCopyableRange&&)                 = default;
+  NonCopyableRange& operator=(NonCopyableRange const&) = default;
+  NonCopyableRange& operator=(NonCopyableRange&&)      = default;
+  Tp* begin() const;
+  Tp* end() const;
+};
+
+static_assert(!std::copy_constructible<NonCopyableRange<InnerRange>>);
+static_assert(!std::copy_constructible<NonCopyableRange<int>>);
+
+template <typename T>
+concept CanCallBaseOn = requires(T&& t) { std::forward<T>(t).base(); };
+
+constexpr bool test() {
+  InnerRange buff[3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
+  Pattern pattern;
+
+  // Check the const& overload
+  {
+    Range range(buff, buff + 3);
+    std::ranges::join_with_view<Range, Pattern> view(range, pattern);
+    std::same_as<Range> decltype(auto) result = view.base();
+    assert(result.wasCopyInitialized);
+    assert(result.begin() == buff);
+    assert(result.end() == buff + 3);
+  }
+
+  // Check the const& overload on const `view`
+  {
+    Range range(buff, buff + 3);
+    std::ranges::join_with_view<Range, Pattern> const view(range, pattern);
+    std::same_as<Range> decltype(auto) result = view.base();
+    assert(result.wasCopyInitialized);
+    assert(result.begin() == buff);
+    assert(result.end() == buff + 3);
+  }
+
+  // Check the && overload
+  {
+    Range range(buff, buff + 3);
+    std::ranges::join_with_view<Range, Pattern> view(range, pattern);
+    std::same_as<Range> decltype(auto) result = std::move(view).base();
+    assert(result.wasMoveInitialized);
+    assert(result.begin() == buff);
+    assert(result.end() == buff + 3);
+  }
+
+  // Check calling `base` function on const&& `view`
+  {
+    Range range(buff, buff + 3);
+    std::ranges::join_with_view<Range, Pattern> const view(range, pattern);
+    std::same_as<Range> decltype(auto) result = std::move(view).base();
+    assert(result.wasCopyInitialized);
+    assert(result.begin() == buff);
+    assert(result.end() == buff + 3);
+  }
+
+  // Ensure the const& overload is not considered when the base is not copy-constructible
+  {
+    static_assert(!CanCallBaseOn<std::ranges::join_with_view<NonCopyableRange<InnerRange>, Pattern> const&>);
+    static_assert(!CanCallBaseOn<std::ranges::join_with_view<NonCopyableRange<InnerRange>, Pattern>&>);
+    static_assert(!CanCallBaseOn<std::ranges::join_with_view<NonCopyableRange<InnerRange>, Pattern> const&&>);
+    static_assert(CanCallBaseOn<std::ranges::join_with_view<NonCopyableRange<InnerRange>, Pattern>&&>);
+    static_assert(CanCallBaseOn<std::ranges::join_with_view<NonCopyableRange<InnerRange>, Pattern>>);
+  }
+
+  // Ensure the const& overload does not depend on Pattern's copy-constructability
+  {
+    static_assert(CanCallBaseOn<std::ranges::join_with_view<Range, NonCopyableRange<int>> const& >);
+    static_assert(CanCallBaseOn<std::ranges::join_with_view<Range, NonCopyableRange<int>>& >);
+    static_assert(CanCallBaseOn<std::ranges::join_with_view<Range, NonCopyableRange<int>> const&& >);
+    static_assert(CanCallBaseOn<std::ranges::join_with_view<Range, NonCopyableRange<int>>&& >);
+    static_assert(CanCallBaseOn<std::ranges::join_with_view<Range, NonCopyableRange<int>>>);
+  }
+
+  // Check above two at the same time
+  {
+    static_assert(
+        !CanCallBaseOn<std::ranges::join_with_view<NonCopyableRange<InnerRange>, NonCopyableRange<int>> const&>);
+    static_assert(!CanCallBaseOn<std::ranges::join_with_view<NonCopyableRange<InnerRange>, NonCopyableRange<int>>&>);
+    static_assert(
+        !CanCallBaseOn<std::ranges::join_with_view<NonCopyableRange<InnerRange>, NonCopyableRange<int>> const&&>);
+    static_assert(CanCallBaseOn<std::ranges::join_with_view<NonCopyableRange<InnerRange>, NonCopyableRange<int>>&&>);
+    static_assert(CanCallBaseOn<std::ranges::join_with_view<NonCopyableRange<InnerRange>, NonCopyableRange<int>>>);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/constraints.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/constraints.compile.pass.cpp
new file mode 100644
index 000000000000000..fa0ca30002bd42e
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/constraints.compile.pass.cpp
@@ -0,0 +1,269 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// template <input_range V, forward_range Pattern>
+//  requires view<V> && input_range<range_reference_t<V>> && view<Pattern> &&
+//           compatible-joinable-ranges<range_reference_t<V>, Pattern>
+// class join_with_view;
+
+#include <ranges>
+
+#include <concepts>
+#include <iterator>
+#include <vector>
+
+#include "test_iterators.h"
+#include "test_range.h"
+
+template <class View, class Pattern>
+concept CanFormJoinWithView = requires { typename std::ranges::join_with_view<View, Pattern>; };
+
+// join_with_view is not valid when the view is not an input_range
+namespace test_when_view_is_not_an_input_range {
+struct View : std::ranges::view_base {
+  cpp20_output_iterator<std::vector<int>*> begin();
+  sentinel_wrapper<cpp20_output_iterator<std::vector<int>*>> end();
+};
+
+struct Pattern : std::ranges::view_base {
+  int* begin();
+  int* end();
+};
+
+static_assert(std::ranges::range<View>);
+static_assert(!std::ranges::input_range<View>);
+static_assert(std::ranges::view<View>);
+static_assert(std::ranges::forward_range<Pattern>);
+static_assert(std::ranges::view<Pattern>);
+static_assert(!CanFormJoinWithView<View, Pattern>);
+} // namespace test_when_view_is_not_an_input_range
+
+// join_with_view is not valid when the pattern is not a forward_range
+namespace test_when_pattern_is_not_a_forward_range {
+struct View : std::ranges::view_base {
+  std::vector<float>* begin();
+  std::vector<float>* end();
+};
+
+struct Pattern : std::ranges::view_base {
+  cpp20_input_iterator<float*> begin();
+  sentinel_wrapper<cpp20_input_iterator<float*>> end();
+};
+
+static_assert(std::ranges::input_range<View>);
+static_assert(std::ranges::view<View>);
+static_assert(!std::ranges::forward_range<Pattern>);
+static_assert(std::ranges::view<Pattern>);
+static_assert(!CanFormJoinWithView<View, Pattern>);
+} // namespace test_when_pattern_is_not_a_forward_range
+
+// join_with_view is not valid when the view does not model std::ranges::view
+namespace test_when_view_does_not_model_view {
+struct View {
+  std::vector<double>* begin();
+  std::vector<double>* end();
+};
+
+struct Pattern : std::ranges::view_base {
+  double* begin();
+  double* end();
+};
+
+static_assert(std::ranges::input_range<View>);
+static_assert(!std::ranges::view<View>);
+static_assert(std::ranges::forward_range<Pattern>);
+static_assert(std::ranges::view<Pattern>);
+static_assert(!CanFormJoinWithView<View, Pattern>);
+} // namespace test_when_view_does_not_model_view
+
+// join_with_view is not valid then range_reference_t of view is not an input_range
+namespace test_when_range_reference_t_of_view_is_not_an_input_range {
+struct InnerRange {
+  cpp20_output_iterator<long*> begin();
+  sentinel_wrapper<cpp20_output_iterator<long*>> end();
+};
+
+struct View : std::ranges::view_base {
+  InnerRange* begin();
+  InnerRange* end();
+};
+
+struct Pattern : std::ranges::view_base {
+  long* begin();
+  long* end();
+};
+
+static_assert(std::ranges::range<InnerRange>);
+static_assert(!std::ranges::input_range<InnerRange>);
+static_assert(std::ranges::input_range<View>);
+static_assert(std::ranges::view<View>);
+static_assert(std::ranges::forward_range<Pattern>);
+static_assert(std::ranges::view<Pattern>);
+static_assert(!CanFormJoinWithView<View, Pattern>);
+} // namespace test_when_range_reference_t_of_view_is_not_an_input_range
+
+// join_with_view is not valid when the pattern does not model std::ranges::view
+namespace test_when_pattern_does_not_model_view {
+struct View : std::ranges::view_base {
+  std::vector<short>* begin();
+  std::vector<short>* end();
+};
+
+struct Pattern {
+  short* begin();
+  short* end();
+};
+
+static_assert(std::ranges::input_range<View>);
+static_assert(std::ranges::view<View>);
+static_assert(std::ranges::forward_range<Pattern>);
+static_assert(!std::ranges::view<Pattern>);
+static_assert(!CanFormJoinWithView<View, Pattern>);
+} // namespace test_when_pattern_does_not_model_view
+
+// join_with_view is not valid when the range_reference_t<V> and pattern
+// does not model together compatible-joinable-ranges
+namespace test_when_used_ranges_are_not_compatible_joinable_ranges {
+using std::ranges::range_reference_t;
+using std::ranges::range_rvalue_reference_t;
+using std::ranges::range_value_t;
+
+template <class InnerRange>
+struct View : std::ranges::view_base {
+  InnerRange* begin();
+  InnerRange* end();
+};
+
+namespace no_common_range_value_type {
+struct InnerRange {
+  struct It {
+    using difference_type = ptrdiff_t;
+    struct value_type {};
+
+    struct reference {
+      operator value_type();
+      operator float();
+    };
+
+    It& operator++();
+    void operator++(int);
+    reference operator*() const;
+  };
+
+  It begin();
+  sentinel_wrapper<It> end();
+};
+
+struct Pattern : std::ranges::view_base {
+  const float* begin();
+  const float* end();
+};
+
+static_assert(std::ranges::input_range<InnerRange>);
+static_assert(std::ranges::forward_range<Pattern>);
+static_assert(std::ranges::view<Pattern>);
+static_assert(!std::common_with<range_value_t<InnerRange>, range_value_t<Pattern>>);
+static_assert(std::common_reference_with<range_reference_t<InnerRange>, range_reference_t<Pattern>>);
+static_assert(std::common_reference_with<range_rvalue_reference_t<InnerRange>, range_rvalue_reference_t<Pattern>>);
+static_assert(!CanFormJoinWithView<View<InnerRange>, Pattern>);
+} // namespace no_common_range_value_type
+
+namespace no_common_range_reference_type {
+struct InnerRange {
+  struct It {
+    using difference_type = ptrdiff_t;
+    struct value_type {};
+
+    struct reference {
+      operator value_type();
+      operator int();
+    };
+
+    It& operator++();
+    void operator++(int);
+    reference operator*() const;
+  };
+
+  It begin();
+  sentinel_wrapper<It> end();
+};
+
+struct Pattern : std::ranges::view_base {
+  struct It {
+    using difference_type = ptrdiff_t;
+
+    struct value_type {
+      operator InnerRange::It::value_type() const;
+    };
+
+    struct reference {
+      operator value_type();
+      operator InnerRange::It::value_type();
+    };
+
+    It& operator++();
+    It operator++(int);
+    reference operator*() const;
+    bool operator==(const It&) const;
+    friend InnerRange::It::value_type&& iter_move(const It&);
+  };
+
+  It begin();
+  It end();
+};
+
+static_assert(std::ranges::input_range<InnerRange>);
+static_assert(std::ranges::forward_range<Pattern>);
+static_assert(std::ranges::view<Pattern>);
+static_assert(std::common_with<range_value_t<InnerRange>, range_value_t<Pattern>>);
+static_assert(!std::common_reference_with<range_reference_t<InnerRange>, range_reference_t<Pattern>>);
+static_assert(std::common_reference_with<range_rvalue_reference_t<InnerRange>, range_rvalue_reference_t<Pattern>>);
+static_assert(!CanFormJoinWithView<View<InnerRange>, Pattern>);
+} // namespace no_common_range_reference_type
+
+namespace no_common_range_rvalue_reference_type {
+struct InnerRange {
+  cpp20_input_iterator<int*> begin();
+  sentinel_wrapper<cpp20_input_iterator<int*>> end();
+};
+
+struct Pattern : std::ranges::view_base {
+  struct It {
+    using difference_type = ptrdiff_t;
+    struct value_type {
+      operator int() const;
+    };
+
+    struct rvalue_reference {
+      operator value_type&();
+    };
+
+    It& operator++();
+    It operator++(int);
+    value_type& operator*() const;
+    bool operator==(const It&) const;
+    friend rvalue_reference iter_move(const It&);
+  };
+
+  It begin();
+  It end();
+};
+
+static_assert(std::ranges::input_range<InnerRange>);
+static_assert(std::ranges::forward_range<Pattern>);
+static_assert(std::ranges::view<Pattern>);
+static_assert(std::common_with<range_value_t<InnerRange>, range_value_t<Pattern>>);
+static_assert(std::common_reference_with<range_reference_t<InnerRange>, range_reference_t<Pattern>>);
+static_assert(!std::common_reference_with<range_rvalue_reference_t<InnerRange>, range_rvalue_reference_t<Pattern>>);
+static_assert(!CanFormJoinWithView<View<InnerRange>, Pattern>);
+} // namespace no_common_range_rvalue_reference_type
+} // namespace test_when_used_ranges_are_not_compatible_joinable_ranges
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctad.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctad.pass.cpp
new file mode 100644
index 000000000000000..46a61dca61e9bdd
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctad.pass.cpp
@@ -0,0 +1,142 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// template<class R, class P>
+//   join_with_view(R&&, P&&) -> join_with_view<views::all_t<R>, views::all_t<P>>;
+//
+// template<input_range R>
+//   join_with_view(R&&, range_value_t<range_reference_t<R>>)
+//     -> join_with_view<views::all_t<R>, single_view<range_value_t<range_reference_t<R>>>>;
+
+#include <ranges>
+
+#include <cassert>
+#include <deque>
+#include <type_traits>
+
+#include "test_iterators.h"
+
+struct View : std::ranges::view_base {
+  View() = default;
+  cpp20_input_iterator<std::deque<int>*> begin() const;
+  sentinel_wrapper<cpp20_input_iterator<std::deque<int>*>> end() const;
+};
+static_assert(std::ranges::view<View>);
+
+struct Pattern : std::ranges::view_base {
+  Pattern() = default;
+  forward_iterator<int*> begin();
+  forward_iterator<int*> end();
+};
+static_assert(std::ranges::view<Pattern>);
+
+// A range that is not a view
+struct Range {
+  Range() = default;
+  cpp20_input_iterator<std::deque<int>*> begin() const;
+  sentinel_wrapper<cpp20_input_iterator<std::deque<int>*>> end() const;
+};
+static_assert(std::ranges::range<Range>);
+static_assert(!std::ranges::view<Range>);
+
+// A pattern that is not a view
+struct RangePattern {
+  RangePattern() = default;
+  forward_iterator<int*> begin();
+  forward_iterator<int*> end();
+};
+static_assert(std::ranges::range<RangePattern>);
+static_assert(!std::ranges::view<RangePattern>);
+
+constexpr void test_range_and_pattern_deduction_guide() {
+  {
+    View v;
+    Pattern pat;
+    std::ranges::join_with_view view(v, pat);
+    static_assert(std::is_same_v<decltype(view), std::ranges::join_with_view<View, Pattern>>);
+  }
+  {
+    Range r;
+    Pattern pat;
+    std::ranges::join_with_view view(r, pat);
+    static_assert(std::is_same_v<decltype(view), std::ranges::join_with_view<std::ranges::ref_view<Range>, Pattern>>);
+  }
+  {
+    View v;
+    RangePattern pat;
+    std::ranges::join_with_view view(v, pat);
+    static_assert(
+        std::is_same_v<decltype(view), std::ranges::join_with_view<View, std::ranges::ref_view<RangePattern>>>);
+  }
+  {
+    Range v;
+    RangePattern pat;
+    std::ranges::join_with_view view(v, pat);
+    static_assert(
+        std::is_same_v<decltype(view),
+                       std::ranges::join_with_view<std::ranges::ref_view<Range>, std::ranges::ref_view<RangePattern>>>);
+  }
+  {
+    Pattern pat;
+    std::ranges::join_with_view view(Range{}, pat);
+    static_assert(
+        std::is_same_v<decltype(view), std::ranges::join_with_view<std::ranges::owning_view<Range>, Pattern>>);
+  }
+  {
+    View v;
+    std::ranges::join_with_view view(v, RangePattern{});
+    static_assert(
+        std::is_same_v<decltype(view), std::ranges::join_with_view<View, std::ranges::owning_view<RangePattern>>>);
+  }
+  {
+    std::ranges::join_with_view view(Range{}, RangePattern{});
+    static_assert(
+        std::is_same_v<
+            decltype(view),
+            std::ranges::join_with_view<std::ranges::owning_view<Range>, std::ranges::owning_view<RangePattern>>>);
+  }
+}
+
+constexpr void test_range_and_element_deduction_guide() {
+  {
+    View v;
+    std::ranges::join_with_view view(v, 0);
+    static_assert(std::is_same_v<decltype(view), std::ranges::join_with_view<View, std::ranges::single_view<int>>>);
+  }
+  {
+    Range r;
+    std::ranges::join_with_view view(r, 1);
+    static_assert(
+        std::is_same_v<decltype(view),
+                       std::ranges::join_with_view<std::ranges::ref_view<Range>, std::ranges::single_view<int>>>);
+  }
+  {
+    std::ranges::join_with_view view(Range{}, 2);
+    static_assert(
+        std::is_same_v<decltype(view),
+                       std::ranges::join_with_view<std::ranges::owning_view<Range>, std::ranges::single_view<int>>>);
+  }
+}
+
+constexpr bool test() {
+  test_range_and_pattern_deduction_guide();
+  test_range_and_element_deduction_guide();
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctor.default.pass.cpp
new file mode 100644
index 000000000000000..3bc75e3baa45ea2
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctor.default.pass.cpp
@@ -0,0 +1,92 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// join_with_view()
+//   requires default_initializable<V> && default_initializable<Pattern> = default;
+
+#include <ranges>
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <type_traits>
+
+using InnerRange = std::array<int, 2>;
+using OuterRange = std::array<InnerRange, 3>;
+
+struct TrivialView : std::ranges::view_base {
+  int val_; // intentionally uninitialised
+
+  TrivialView() = default;
+  const InnerRange* begin();
+  const InnerRange* end();
+};
+
+static_assert(std::is_trivial_v<TrivialView>);
+
+struct DefaultView : std::ranges::view_base {
+  static constexpr OuterRange view = {{{1, 2}, {3, 4}, {5, 6}}};
+  const OuterRange* r_;
+
+  constexpr DefaultView(const OuterRange* r = &view) : r_(r) {}
+
+  constexpr const InnerRange* begin() { return r_->data(); }
+  constexpr const InnerRange* end() { return r_->data() + r_->size(); }
+};
+
+struct NonDefaultConstructibleView : TrivialView {
+  NonDefaultConstructibleView(int);
+};
+
+struct Pattern : std::ranges::view_base {
+  int val; // intentionally uninitialised
+
+  constexpr int* begin() { return &val; }
+  constexpr int* end() { return &val + 1; }
+};
+
+struct NonDefaultConstructiblePattern : Pattern {
+  NonDefaultConstructiblePattern(int);
+};
+
+constexpr bool test() {
+  // Check if `base_` is value initialised
+  {
+    std::ranges::join_with_view<TrivialView, Pattern> v;
+    assert(std::move(v).base().val_ == 0);
+  }
+
+  // Check if `pattern_` is value initialised
+  {
+    std::ranges::join_with_view<DefaultView, Pattern> v;
+    assert(std::ranges::equal(v, std::array{1, 2, 0, 3, 4, 0, 5, 6}));
+    assert(std::move(v).base().r_ == &DefaultView::view);
+  }
+
+  static_assert(std::default_initializable<std::ranges::join_with_view<TrivialView, Pattern>>);
+  static_assert(!std::default_initializable<std::ranges::join_with_view<TrivialView, NonDefaultConstructiblePattern>>);
+  static_assert(!std::default_initializable<std::ranges::join_with_view<NonDefaultConstructibleView, Pattern>>);
+  static_assert(!std::default_initializable<
+                std::ranges::join_with_view<NonDefaultConstructibleView, NonDefaultConstructiblePattern>>);
+
+  return true;
+}
+
+int main() {
+  test();
+#if __cpp_lib_variant >= 202106 // TODO RANGES Remove when P2231R1 is implemented.
+  static_assert(test());
+#endif
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctor.view_pattern.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctor.view_pattern.pass.cpp
new file mode 100644
index 000000000000000..b8181bd7280763b
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctor.view_pattern.pass.cpp
@@ -0,0 +1,128 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// constexpr explicit join_with_view(V base, Pattern pattern);
+
+#include <ranges>
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <type_traits>
+#include <utility>
+
+#include "helpers.h"
+
+struct View : std::ranges::view_base {
+  using InnerRange = std::array<int, 2>;
+  using OuterRange = std::array<InnerRange, 3>;
+
+  static constexpr OuterRange default_range = {{{1, 2}, {3, 4}, {5, 6}}};
+  static constexpr OuterRange range_on_move = {{{6, 5}, {4, 3}, {2, 1}}};
+
+  constexpr View() : r_(&default_range) {}
+  constexpr View(const View&) : r_(&default_range) {}
+  constexpr View(View&&) : r_(&range_on_move) {}
+
+  constexpr View& operator=(const View&) {
+    r_ = &default_range;
+    return *this;
+  }
+
+  constexpr View& operator=(View&&) {
+    r_ = &default_range;
+    return *this;
+  }
+
+  const InnerRange* begin() { return r_->data(); }
+  const InnerRange* end() { return r_->data() + r_->size(); }
+
+private:
+  const OuterRange* r_;
+};
+
+struct Pattern : std::ranges::view_base {
+  using PatternRange = std::array<int, 2>;
+
+  static constexpr PatternRange default_range = {0, 0};
+  static constexpr PatternRange range_on_move = {7, 7};
+
+  constexpr Pattern() : r_(&default_range) {}
+  constexpr Pattern(const Pattern&) : r_(&default_range) {}
+  constexpr Pattern(Pattern&&) : r_(&range_on_move) {}
+
+  constexpr Pattern& operator=(const Pattern&) {
+    r_ = &default_range;
+    return *this;
+  }
+
+  constexpr Pattern& operator=(Pattern&&) {
+    r_ = &default_range;
+    return *this;
+  }
+
+  const int* begin() { return r_->data(); }
+  const int* end() { return r_->data() + r_->size(); }
+
+private:
+  const PatternRange* r_;
+};
+
+constexpr bool test() {
+  // Check construction with `view` and `pattern` (glvalues)
+  {
+    View v;
+    Pattern p;
+    std::ranges::join_with_view<View, Pattern> jw{v, p};
+    assert(std::ranges::equal(jw, std::array{6, 5, 7, 7, 4, 3, 7, 7, 2, 1}));
+  }
+
+  // Check construction with `view` and `pattern` (const glvalues)
+  {
+    const View v;
+    const Pattern p;
+    std::ranges::join_with_view<View, Pattern> jw{v, p};
+    assert(std::ranges::equal(jw, std::array{6, 5, 7, 7, 4, 3, 7, 7, 2, 1}));
+  }
+
+  // Check construction with `view` and `pattern` (prvalues)
+  {
+    std::ranges::join_with_view<View, Pattern> jw{View{}, Pattern{}};
+    assert(std::ranges::equal(jw, std::array{6, 5, 7, 7, 4, 3, 7, 7, 2, 1}));
+  }
+
+  // Check construction with `view` and `pattern` (xvalues)
+  {
+    View v;
+    Pattern p;
+    std::ranges::join_with_view<View, Pattern> jw{std::move(v), std::move(p)};
+    assert(std::ranges::equal(jw, std::array{6, 5, 7, 7, 4, 3, 7, 7, 2, 1}));
+  }
+
+  // Check explicitness
+  static_assert(ConstructionIsExplicit<std::ranges::join_with_view<View, Pattern>, View&, Pattern&>);
+  static_assert(ConstructionIsExplicit<std::ranges::join_with_view<View, Pattern>, const View&, const Pattern&>);
+  static_assert(ConstructionIsExplicit<std::ranges::join_with_view<View, Pattern>, View, Pattern>);
+  static_assert(ConstructionIsExplicit<std::ranges::join_with_view<View, Pattern>, View&&, Pattern&&>);
+
+  return true;
+}
+
+int main() {
+  test();
+#if __cpp_lib_variant >= 202106 // TODO RANGES Remove when P2231R1 is implemented.
+  static_assert(test());
+#endif
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/helpers.h b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/helpers.h
new file mode 100644
index 000000000000000..8e508b306aa5c7a
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/helpers.h
@@ -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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_JOIN_WITH_RANGE_JOIN_WITH_VIEW_H
+#define TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_JOIN_WITH_RANGE_JOIN_WITH_VIEW_H
+
+#include <concepts>
+#include <utility>
+
+template <class T>
+void pass(T);
+
+template <class T, class... Args>
+concept ConstructionIsExplicit =
+    std::constructible_from<T, Args...> && !requires(Args&&... args) { pass<T>({std::forward<Args>(args)...}); };
+
+#endif // TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_JOIN_WITH_RANGE_JOIN_WITH_VIEW_H
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/inheritance.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/inheritance.compile.pass.cpp
new file mode 100644
index 000000000000000..62786c39a8caf00
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/inheritance.compile.pass.cpp
@@ -0,0 +1,38 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <ranges>
+
+// class join_with_view : public view_interface<join_with_view<V, Pattern>>
+
+#include <ranges>
+
+#include <concepts>
+#include <string>
+#include <vector>
+
+template <class T>
+struct View : std::ranges::view_base {
+  std::vector<T>* begin();
+  std::vector<T>* end();
+};
+
+template <class T>
+struct Pattern : std::ranges::view_base {
+  T* begin();
+  T* end();
+};
+
+template <class T>
+using JoinWithView = std::ranges::join_with_view<View<T>, Pattern<T>>;
+
+static_assert(std::derived_from<JoinWithView<int>, std::ranges::view_interface<JoinWithView<int>>>);
+static_assert(std::derived_from<JoinWithView<void*>, std::ranges::view_interface<JoinWithView<void*>>>);
+static_assert(std::derived_from<JoinWithView<std::string>, std::ranges::view_interface<JoinWithView<std::string>>>);



More information about the libcxx-commits mailing list