[libcxx-commits] [libcxx] [libc++][ranges] implement `std::ranges::zip_transform_view` (PR #79605)

via libcxx-commits libcxx-commits at lists.llvm.org
Mon Jan 29 14:35:49 PST 2024


https://github.com/huixie90 updated https://github.com/llvm/llvm-project/pull/79605

>From 5fd084bf56e4da90fecb88d8fa71d3b2041e89cf Mon Sep 17 00:00:00 2001
From: Hui <hui.xie0621 at gmail.com>
Date: Fri, 26 Jan 2024 15:12:33 +0000
Subject: [PATCH 1/2] [libc++][ranges] implement
 `std::ranges::zip_transform_view`

---
 libcxx/docs/Status/RangesViews.csv            |   2 +-
 libcxx/docs/Status/ZipProjects.csv            |   2 +-
 libcxx/include/CMakeLists.txt                 |   1 +
 libcxx/include/__ranges/zip_transform_view.h  | 357 +++++++++++++
 libcxx/include/__ranges/zip_view.h            |   9 +
 libcxx/include/module.modulemap.in            |   1 +
 libcxx/include/ranges                         |  11 +
 .../no_unique_address.compile.pass.cpp        |  38 ++
 .../range.zip.transform/begin.pass.cpp        |  74 +++
 .../range.zip.transform/cpo.pass.cpp          | 101 ++++
 .../range.zip.transform/ctad.compile.pass.cpp |  37 ++
 .../range.zip.transform/ctor.default.pass.cpp |  86 +++
 .../range.zip.transform/ctor.views.pass.cpp   |  89 ++++
 .../range.zip.transform/end.pass.cpp          | 100 ++++
 .../range.zip.transform/general.pass.cpp      |  29 +
 .../range.zip.transform/size.pass.cpp         |  92 ++++
 .../range.zip.transform/types.h               | 497 ++++++++++++++++++
 ..._robust_against_no_unique_address.pass.cpp |   1 +
 18 files changed, 1525 insertions(+), 2 deletions(-)
 create mode 100644 libcxx/include/__ranges/zip_transform_view.h
 create mode 100644 libcxx/test/libcxx/ranges/range.adaptors/range.zip.transform/no_unique_address.compile.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.zip.transform/begin.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.zip.transform/cpo.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.zip.transform/ctad.compile.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.zip.transform/ctor.default.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.zip.transform/ctor.views.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.zip.transform/end.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.zip.transform/general.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.zip.transform/size.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.zip.transform/types.h

diff --git a/libcxx/docs/Status/RangesViews.csv b/libcxx/docs/Status/RangesViews.csv
index f141656eb131a2..1a8d6c3f940268 100644
--- a/libcxx/docs/Status/RangesViews.csv
+++ b/libcxx/docs/Status/RangesViews.csv
@@ -25,7 +25,7 @@ C++20,`istream <https://wg21.link/P1035R7>`_,Hui Xie,`D133317 <https://llvm.org/
 C++23,`repeat <https://wg21.link/P2474R2>`_,Yrong,`D141699 <https://llvm.org/D141699>`_,✅
 C++23,`cartesian_product <https://wg21.link/P2374R4>`_,Unassigned,No patch yet,Not started
 C++23,`zip <https://wg21.link/P2321R2>`_,Hui Xie,`D122806 <https://llvm.org/D122806>`_,✅
-C++23,`zip_transform <https://wg21.link/P2321R2>`_,Hui Xie,No patch yet,Not started
+C++23,`zip_transform <https://wg21.link/P2321R2>`_,Hui Xie,No patch yet,✅
 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>`_,Jakub Mazurkiewicz,`65536 <https://github.com/llvm/llvm-project/pull/65536>`_,In progress
diff --git a/libcxx/docs/Status/ZipProjects.csv b/libcxx/docs/Status/ZipProjects.csv
index 699a382ff66b73..b6d66acd233796 100644
--- a/libcxx/docs/Status/ZipProjects.csv
+++ b/libcxx/docs/Status/ZipProjects.csv
@@ -12,7 +12,7 @@ Section,Description,Dependencies,Assignee,Complete
 | `[range.zip.iterator] <https://wg21.link/range.zip.iterator>`_, "`zip_view::iterator <https://reviews.llvm.org/D122806>`_", None, Hui Xie, |Complete|
 | `[range.zip.sentinel] <https://wg21.link/range.zip.sentinel>`_, "`zip_view::sentinel <https://reviews.llvm.org/D122806>`_", None, Hui Xie, |Complete|
 | `[range.zip.transform.view] <https://wg21.link/range.zip.transform.view>`_, "zip_transform_view", "| `zip_transform_view::iterator`
-| `zip_transform_view::sentinel`", Hui Xie, |Not Started|
+| `zip_transform_view::sentinel`", Hui Xie, |Complete|
 | `[range.zip.transform.iterator] <https://wg21.link/range.zip.transform.iterator>`_, "zip_transform_view::iterator", None, Hui Xie, |Not Started|
 | `[range.zip.transform.sentinel] <https://wg21.link/range.zip.transform.sentinel>`_, "zip_transform_view::sentinel", None, Hui Xie, |Not Started|
 | `[range.adjacent.view] <https://wg21.link/range.adjacent.view>`_, "adjacent_view", "| `adjacent_view::iterator`
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index ed721d467e94f4..15d4de2fbe6bcc 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -653,6 +653,7 @@ set(files
   __ranges/view_interface.h
   __ranges/views.h
   __ranges/zip_view.h
+  __ranges/zip_transform_view.h
   __split_buffer
   __std_clang_module
   __std_mbstate_t.h
diff --git a/libcxx/include/__ranges/zip_transform_view.h b/libcxx/include/__ranges/zip_transform_view.h
new file mode 100644
index 00000000000000..4cc6ef55e8181e
--- /dev/null
+++ b/libcxx/include/__ranges/zip_transform_view.h
@@ -0,0 +1,357 @@
+// -*- 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_ZIP_TRANSFORM_VIEW_H
+#define _LIBCPP___RANGES_ZIP_TRANSFORM_VIEW_H
+
+#include <__config>
+
+#include <__concepts/constructible.h>
+#include <__concepts/convertible_to.h>
+#include <__concepts/derived_from.h>
+#include <__concepts/equality_comparable.h>
+#include <__concepts/invocable.h>
+#include <__functional/invoke.h>
+#include <__iterator/concepts.h>
+#include <__iterator/incrementable_traits.h>
+#include <__iterator/iterator_traits.h>
+#include <__memory/addressof.h>
+#include <__ranges/access.h>
+#include <__ranges/all.h>
+#include <__ranges/concepts.h>
+#include <__ranges/empty_view.h>
+#include <__ranges/movable_box.h>
+#include <__ranges/view_interface.h>
+#include <__ranges/zip_view.h>
+#include <__type_traits/decay.h>
+#include <__type_traits/invoke.h>
+#include <__type_traits/is_object.h>
+#include <__type_traits/is_reference.h>
+#include <__type_traits/maybe_const.h>
+#include <__type_traits/remove_cvref.h>
+#include <__utility/forward.h>
+#include <__utility/in_place.h>
+#include <__utility/move.h>
+#include <tuple>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER >= 23
+
+namespace ranges {
+
+template <move_constructible _Fn, input_range... _Views>
+  requires(view<_Views> && ...) &&
+          (sizeof...(_Views) > 0) && is_object_v<_Fn> && regular_invocable<_Fn&, range_reference_t<_Views>...> &&
+          __can_reference<invoke_result_t<_Fn&, range_reference_t<_Views>...>>
+class zip_transform_view : public view_interface<zip_transform_view<_Fn, _Views...>> {
+  _LIBCPP_NO_UNIQUE_ADDRESS zip_view<_Views...> __zip_;
+  _LIBCPP_NO_UNIQUE_ADDRESS __movable_box<_Fn> __fun_;
+
+  using _InnerView = zip_view<_Views...>;
+  template <bool _Const>
+  using __ziperator = iterator_t<__maybe_const<_Const, _InnerView>>;
+  template <bool Const>
+  using __zentinel = sentinel_t<__maybe_const<Const, _InnerView>>;
+
+  template <bool>
+  class __iterator;
+
+  template <bool>
+  class __sentinel;
+
+public:
+  _LIBCPP_HIDE_FROM_ABI zip_transform_view() = default;
+
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit zip_transform_view(_Fn __fun, _Views... __views)
+      : __zip_(std::move(__views)...), __fun_(in_place, std::move(__fun)) {}
+
+  _LIBCPP_HIDE_FROM_ABI constexpr auto begin() { return __iterator<false>(*this, __zip_.begin()); }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr auto begin() const
+    requires range<const _InnerView> && regular_invocable<const _Fn&, range_reference_t<const _Views>...>
+  {
+    return __iterator<true>(*this, __zip_.begin());
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr auto end() {
+    if constexpr (common_range<_InnerView>) {
+      return __iterator<false>(*this, __zip_.end());
+    } else {
+      return __sentinel<false>(__zip_.end());
+    }
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr auto end() const
+    requires range<const _InnerView> && regular_invocable<const _Fn&, range_reference_t<const _Views>...>
+  {
+    if constexpr (common_range<const _InnerView>) {
+      return __iterator<true>(*this, __zip_.end());
+    } else {
+      return __sentinel<true>(__zip_.end());
+    }
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr auto size()
+    requires sized_range<_InnerView>
+  {
+    return __zip_.size();
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr auto size() const
+    requires sized_range<const _InnerView>
+  {
+    return __zip_.size();
+  }
+};
+
+template <class _Fn, class... _Ranges>
+zip_transform_view(_Fn, _Ranges&&...) -> zip_transform_view<_Fn, views::all_t<_Ranges>...>;
+
+template <bool _Const, class... _Views>
+concept __base_forward = forward_range<__maybe_const<_Const, zip_view<_Views...>>>;
+
+template <bool _Const, class _Fn, class... _Views>
+struct __zip_transform_iterator_category_base {};
+
+template <bool _Const, class _Fn, class... _Views>
+  requires __base_forward<_Const, _Views...>
+struct __zip_transform_iterator_category_base<_Const, _Fn, _Views...> {
+private:
+  template <class _View>
+  using __tag = typename iterator_traits<iterator_t<__maybe_const<_Const, _View>>>::iterator_category;
+
+  static consteval auto __get_iterator_category() {
+    if constexpr (!is_reference_v<invoke_result_t<__maybe_const<_Const, _Fn>&,
+                                                  range_reference_t<__maybe_const<_Const, _Views>>...>>) {
+      return input_iterator_tag();
+    } else if constexpr ((derived_from<__tag<_Views>, random_access_iterator_tag> && ...)) {
+      return random_access_iterator_tag();
+    } else if constexpr ((derived_from<__tag<_Views>, bidirectional_iterator_tag> && ...)) {
+      return bidirectional_iterator_tag();
+    } else if constexpr ((derived_from<__tag<_Views>, forward_iterator_tag> && ...)) {
+      return forward_iterator_tag();
+    } else {
+      return input_iterator_tag();
+    }
+  }
+
+public:
+  using iterator_category = decltype(__get_iterator_category());
+};
+
+template <move_constructible _Fn, input_range... _Views>
+  requires(view<_Views> && ...) &&
+          (sizeof...(_Views) > 0) && is_object_v<_Fn> && regular_invocable<_Fn&, range_reference_t<_Views>...> &&
+          __can_reference<invoke_result_t<_Fn&, range_reference_t<_Views>...>>
+template <bool _Const>
+class zip_transform_view<_Fn, _Views...>::__iterator
+    : public __zip_transform_iterator_category_base<_Const, _Fn, _Views...> {
+  using _Parent = __maybe_const<_Const, zip_transform_view>;
+  using _Base   = __maybe_const<_Const, _InnerView>;
+
+  friend zip_transform_view<_Fn, _Views...>;
+
+  _Parent* __parent_ = nullptr;
+  __ziperator<_Const> __inner_;
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator(_Parent& __parent, __ziperator<_Const> __inner)
+      : __parent_(std::addressof(__parent)), __inner_(std::move(__inner)) {}
+
+  _LIBCPP_HIDE_FROM_ABI constexpr auto __get_deref_and_invoke() const noexcept {
+    return [this](const auto&... __iters) noexcept(
+               noexcept(std::invoke(*__parent_->__fun_, *__iters...))) -> decltype(auto) {
+      return std::invoke(*__parent_->__fun_, *__iters...);
+    };
+  }
+
+public:
+  using iterator_concept = typename __ziperator<_Const>::iterator_concept;
+  using value_type =
+      remove_cvref_t<invoke_result_t<__maybe_const<_Const, _Fn>&, range_reference_t<__maybe_const<_Const, _Views>>...>>;
+  using difference_type = range_difference_t<_Base>;
+
+  _LIBCPP_HIDE_FROM_ABI __iterator() = default;
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator(__iterator<!_Const> __i)
+    requires _Const && convertible_to<__ziperator<false>, __ziperator<_Const>>
+      : __parent_(__i.__parent_), __inner_(std::move(__i.__inner_)) {}
+
+  _LIBCPP_HIDE_FROM_ABI constexpr decltype(auto) operator*() const
+      noexcept(noexcept(std::apply(__get_deref_and_invoke(), __zip_view_iterator_access::__get_underlying(__inner_)))) {
+    return std::apply(__get_deref_and_invoke(), __zip_view_iterator_access::__get_underlying(__inner_));
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator++() {
+    ++__inner_;
+    return *this;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr void operator++(int) { ++*this; }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator operator++(int)
+    requires forward_range<_Base>
+  {
+    auto __tmp = *this;
+    ++*this;
+    return __tmp;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator--()
+    requires bidirectional_range<_Base>
+  {
+    --__inner_;
+    return *this;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator operator--(int)
+    requires bidirectional_range<_Base>
+  {
+    auto __tmp = *this;
+    --*this;
+    return __tmp;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator+=(difference_type __x)
+    requires random_access_range<_Base>
+  {
+    __inner_ += __x;
+    return *this;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator-=(difference_type __x)
+    requires random_access_range<_Base>
+  {
+    __inner_ -= __x;
+    return *this;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr decltype(auto) operator[](difference_type __n) const
+    requires random_access_range<_Base>
+  {
+    return std::apply(
+        [&]<class... _Is>(const _Is&... __iters) -> decltype(auto) {
+          return std::invoke(*__parent_->__fun_, __iters[iter_difference_t<_Is>(__n)]...);
+        },
+        __zip_view_iterator_access::__get_underlying(__inner_));
+  }
+
+  _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const __iterator& __x, const __iterator& __y)
+    requires equality_comparable<__ziperator<_Const>>
+  {
+    return __x.__inner_ == __y.__inner_;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator<=>(const __iterator& __x, const __iterator& __y)
+    requires random_access_range<_Base>
+  {
+    return __x.__inner_ <=> __y.__inner_;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI friend constexpr __iterator operator+(const __iterator& __i, difference_type __n)
+    requires random_access_range<_Base>
+  {
+    return __iterator(*__i.__parent_, __i.__inner_ + __n);
+  }
+
+  _LIBCPP_HIDE_FROM_ABI friend constexpr __iterator operator+(difference_type __n, const __iterator& __i)
+    requires random_access_range<_Base>
+  {
+    return __iterator(*__i.__parent_, __i.__inner_ + __n);
+  }
+
+  _LIBCPP_HIDE_FROM_ABI friend constexpr __iterator operator-(const __iterator& __i, difference_type __n)
+    requires random_access_range<_Base>
+  {
+    return __iterator(*__i.__parent_, __i.__inner_ - __n);
+  }
+
+  _LIBCPP_HIDE_FROM_ABI friend constexpr difference_type operator-(const __iterator& __x, const __iterator& __y)
+    requires sized_sentinel_for<__ziperator<_Const>, __ziperator<_Const>>
+  {
+    return __x.__inner_ - __y.__inner_;
+  }
+};
+
+template <move_constructible _Fn, input_range... _Views>
+  requires(view<_Views> && ...) &&
+          (sizeof...(_Views) > 0) && is_object_v<_Fn> && regular_invocable<_Fn&, range_reference_t<_Views>...> &&
+          __can_reference<invoke_result_t<_Fn&, range_reference_t<_Views>...>>
+template <bool _Const>
+class zip_transform_view<_Fn, _Views...>::__sentinel {
+  __zentinel<_Const> __inner_;
+
+  friend zip_transform_view<_Fn, _Views...>;
+
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit __sentinel(__zentinel<_Const> __inner) : __inner_(__inner) {}
+
+public:
+  _LIBCPP_HIDE_FROM_ABI __sentinel() = default;
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __sentinel(__sentinel<!_Const> __i)
+    requires _Const && convertible_to<__zentinel<false>, __zentinel<_Const>>
+      : __inner_(__i.__inner_) {}
+
+  template <bool _OtherConst>
+    requires sentinel_for<__zentinel<_Const>, __ziperator<_OtherConst>>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const __iterator<_OtherConst>& __x, const __sentinel& __y) {
+    return __x.__inner_ == __y.__inner_;
+  }
+
+  template <bool _OtherConst>
+    requires sized_sentinel_for<__zentinel<_Const>, __ziperator<_OtherConst>>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr range_difference_t<__maybe_const<_OtherConst, _InnerView>>
+  operator-(const __iterator<_OtherConst>& __x, const __sentinel& __y) {
+    return __x.__inner_ - __y.__inner_;
+  }
+
+  template <bool _OtherConst>
+    requires sized_sentinel_for<__zentinel<_Const>, __ziperator<_OtherConst>>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr range_difference_t<__maybe_const<_OtherConst, _InnerView>>
+  operator-(const __sentinel& __x, const __iterator<_OtherConst>& __y) {
+    return __x.__inner_ - __y.__inner_;
+  }
+};
+
+namespace views {
+namespace __zip_transform {
+
+struct __fn {
+  template <class _Fn>
+    requires(move_constructible<decay_t<_Fn>> && regular_invocable<decay_t<_Fn>&> &&
+             is_object_v<invoke_result_t<decay_t<_Fn>&>>)
+  _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Fn&&) const
+      noexcept(noexcept(auto(views::empty<decay_t<invoke_result_t<decay_t<_Fn>&>>>))) {
+    return views::empty<decay_t<invoke_result_t<decay_t<_Fn>&>>>;
+  }
+
+  template <class _Fn, class... _Ranges>
+    requires(sizeof...(_Ranges) > 0)
+  _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Fn&& __fun, _Ranges&&... __rs) const
+      noexcept(noexcept(zip_transform_view(std::forward<_Fn>(__fun), std::forward<_Ranges>(__rs)...)))
+          -> decltype(zip_transform_view(std::forward<_Fn>(__fun), std::forward<_Ranges>(__rs)...)) {
+    return zip_transform_view(std::forward<_Fn>(__fun), std::forward<_Ranges>(__rs)...);
+  }
+};
+
+} // namespace __zip_transform
+inline namespace __cpo {
+inline constexpr auto zip_transform = __zip_transform::__fn{};
+} // namespace __cpo
+} // namespace views
+} // namespace ranges
+
+#endif // _LIBCPP_STD_VER >= 23
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___RANGES_ZIP_TRANSFORM_VIEW_H
diff --git a/libcxx/include/__ranges/zip_view.h b/libcxx/include/__ranges/zip_view.h
index 4898c0afc87a6e..deb87d359c6025 100644
--- a/libcxx/include/__ranges/zip_view.h
+++ b/libcxx/include/__ranges/zip_view.h
@@ -245,6 +245,13 @@ struct __zip_view_iterator_category_base<_Const, _Views...> {
   using iterator_category = input_iterator_tag;
 };
 
+struct __zip_view_iterator_access {
+  template <class _Iter>
+  _LIBCPP_HIDE_FROM_ABI static constexpr decltype(auto) __get_underlying(_Iter& __iter) noexcept {
+    return (__iter.__current_);
+  }
+};
+
 template <input_range... _Views>
   requires(view<_Views> && ...) && (sizeof...(_Views) > 0)
 template <bool _Const>
@@ -263,6 +270,8 @@ class zip_view<_Views...>::__iterator : public __zip_view_iterator_category_base
 
   friend class zip_view<_Views...>;
 
+  friend __zip_view_iterator_access;
+
 public:
   using iterator_concept = decltype(__get_zip_view_iterator_tag<_Const, _Views...>());
   using value_type       = __tuple_or_pair<range_value_t<__maybe_const<_Const, _Views>>...>;
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 194a74a1e07b14..0a3ba7ea49d3b1 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -1719,6 +1719,7 @@ module std_private_ranges_transform_view             [system] {
 module std_private_ranges_view_interface             [system] { header "__ranges/view_interface.h" }
 module std_private_ranges_views                      [system] { header "__ranges/views.h" }
 module std_private_ranges_zip_view                   [system] { header "__ranges/zip_view.h" }
+module std_private_ranges_zip_transform_view         [system] { header "__ranges/zip_transform_view.h" }
 
 module std_private_span_span_fwd [system] { header "__fwd/span.h" }
 
diff --git a/libcxx/include/ranges b/libcxx/include/ranges
index 660d533b2a7830..2a692567261e4c 100644
--- a/libcxx/include/ranges
+++ b/libcxx/include/ranges
@@ -325,6 +325,16 @@ namespace std::ranges {
 
   namespace views { inline constexpr unspecified zip = unspecified; }       // C++23
 
+  // [range.zip.transform], zip transform view
+  template<move_constructible F, input_range... Views>
+    requires (view<Views> && ...) && (sizeof...(Views) > 0) && is_object_v<F> &&
+             regular_invocable<F&, range_reference_t<Views>...> &&
+             can-reference<invoke_result_t<F&, range_reference_t<Views>...>>
+  class zip_transform_view;                                                         // C++23
+
+  namespace views { inline constexpr unspecified zip_transform = unspecified; }     // C++23
+
+
   // [range.as.rvalue]
   template <view V>
     requires input_range<V>
@@ -413,6 +423,7 @@ namespace std {
 #include <__ranges/transform_view.h>
 #include <__ranges/view_interface.h>
 #include <__ranges/views.h>
+#include <__ranges/zip_transform_view.h>
 #include <__ranges/zip_view.h>
 #include <version>
 
diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.zip.transform/no_unique_address.compile.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.zip.transform/no_unique_address.compile.pass.cpp
new file mode 100644
index 00000000000000..7d15a819a38189
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.zip.transform/no_unique_address.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
+// XFAIL: msvc
+
+// This test ensures that we use `[[no_unique_address]]` in `zip_transform_view`.
+
+#include <ranges>
+
+struct View : std::ranges::view_base {
+  int* begin() const;
+  int* end() const;
+};
+
+struct Pred {
+  template <class... Args>
+  bool operator()(const Args&...) const;
+};
+
+template <class View>
+struct Test {
+  [[no_unique_address]] View view;
+  char c;
+};
+
+// [[no_unique_address]] applied to movable-box
+struct PredWithPadding : Pred {
+  alignas(128) char c;
+};
+
+static_assert(sizeof(Test<std::ranges::zip_transform_view<PredWithPadding, View>>) ==
+              sizeof(std::ranges::zip_transform_view<PredWithPadding, View>));
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/begin.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/begin.pass.cpp
new file mode 100644
index 00000000000000..f63a944bfde19c
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/begin.pass.cpp
@@ -0,0 +1,74 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// constexpr auto begin();
+// constexpr auto begin() const
+//   requires range<const InnerView> &&
+//            regular_invocable<const F&, range_reference_t<const Views>...>;
+
+#include <ranges>
+
+#include <cassert>
+#include <concepts>
+#include <tuple>
+#include <utility>
+
+#include "types.h"
+
+template <class T>
+concept HasConstBegin = requires(const T& ct) { ct.begin(); };
+
+template <class T>
+concept HasBegin = requires(T& t) { t.begin(); };
+
+constexpr bool test() {
+  int buffer[8] = {1, 2, 3, 4, 5, 6, 7, 8};
+  {
+    // all underlying iterators should be at the begin position
+    std::ranges::zip_transform_view v(
+        MakeTuple{}, SimpleCommon{buffer}, std::views::iota(0), std::ranges::single_view(2.));
+    auto it = v.begin();
+    assert(*it == std::make_tuple(1, 0, 2.0));
+
+    auto const_it = std::as_const(v).begin();
+    assert(*const_it == *it);
+
+    static_assert(!std::same_as<decltype(it), decltype(const_it)>);
+  }
+
+  {
+    // with empty range
+    std::ranges::zip_transform_view v(MakeTuple{}, SimpleCommon{buffer}, std::ranges::empty_view<int>());
+    assert(v.begin() == v.end());
+    assert(std::as_const(v).begin() == std::as_const(v).end());
+  }
+
+  {
+    // underlying const R is not a range
+    using View = std::ranges::zip_transform_view<MakeTuple, SimpleCommon, NoConstBeginView>;
+    static_assert(HasBegin<View>);
+    static_assert(!HasConstBegin<View>);
+  }
+
+  {
+    // Fn cannot be invoked on const range
+    using View = std::ranges::zip_transform_view<NonConstOnlyFn, ConstNonConstDifferentView>;
+    static_assert(HasBegin<View>);
+    static_assert(!HasConstBegin<View>);
+  }
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/cpo.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/cpo.pass.cpp
new file mode 100644
index 00000000000000..ec3f734a0b05bd
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/cpo.pass.cpp
@@ -0,0 +1,101 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// std::views::zip_transform
+
+#include <ranges>
+
+#include <array>
+#include <algorithm>
+#include <cassert>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+struct NotMoveConstructible {
+  NotMoveConstructible()                       = default;
+  NotMoveConstructible(NotMoveConstructible&&) = delete;
+  int operator()() const { return 5; }
+};
+
+struct NotInvocable {};
+
+template <class... Args>
+struct Invocable {
+  int operator()(Args...) const { return 5; }
+};
+
+struct ReturnNotObject {
+  void operator()() const {}
+};
+
+static_assert(!std::is_invocable_v<decltype((std::views::zip_transform))>);
+static_assert(!std::is_invocable_v<decltype((std::views::zip_transform)), NotMoveConstructible>);
+static_assert(!std::is_invocable_v<decltype((std::views::zip_transform)), NotInvocable>);
+static_assert(std::is_invocable_v<decltype((std::views::zip_transform)), Invocable<>>);
+static_assert(!std::is_invocable_v<decltype((std::views::zip_transform)), ReturnNotObject>);
+
+static_assert(std::is_invocable_v<decltype((std::views::zip_transform)), //
+                                  Invocable<int>,                        //
+                                  std::ranges::iota_view<int, int>>);
+static_assert(!std::is_invocable_v<decltype((std::views::zip_transform)), //
+                                   Invocable<>,                           //
+                                   std::ranges::iota_view<int, int>>);
+static_assert(!std::is_invocable_v<decltype((std::views::zip_transform)),
+                                   Invocable<int>,
+                                   std::ranges::iota_view<int, int>,
+                                   std::ranges::iota_view<int, int>>);
+static_assert(std::is_invocable_v<decltype((std::views::zip_transform)),
+                                  Invocable<int, int>,
+                                  std::ranges::iota_view<int, int>,
+                                  std::ranges::iota_view<int, int>>);
+
+constexpr bool test() {
+  {
+    // zip_transform function with no ranges
+    auto v = std::views::zip_transform(Invocable<>{});
+    assert(std::ranges::empty(v));
+    static_assert(std::is_same_v<decltype(v), std::ranges::empty_view<int>>);
+  }
+
+  {
+    // zip_transform views
+    int buffer1[] = {1, 2, 3, 4, 5, 6, 7, 8};
+    int buffer2[] = {9, 10, 11, 12};
+    auto view1    = std::views::all(buffer1);
+    auto view2    = std::views::all(buffer2);
+    std::same_as<std::ranges::zip_transform_view<std::plus<>, decltype(view1), decltype(view2)>> decltype(auto) v =
+        std::views::zip_transform(std::plus{}, buffer1, buffer2);
+    assert(std::ranges::size(v) == 4);
+    auto expected = {10, 12, 14, 16};
+    assert(std::ranges::equal(v, expected));
+    static_assert(std::is_same_v<std::ranges::range_reference_t<decltype(v)>, int>);
+  }
+
+  {
+    // zip_transform a viewable range
+    std::array a{1, 2, 3};
+    auto id = [](auto& x) -> decltype(auto) { return (x); };
+    std::same_as<
+        std::ranges::zip_transform_view<decltype(id), std::ranges::ref_view<std::array<int, 3>>>> decltype(auto) v =
+        std::views::zip_transform(id, a);
+    assert(&v[0] == &a[0]);
+    static_assert(std::is_same_v<std::ranges::range_reference_t<decltype(v)>, int&>);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/ctad.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/ctad.compile.pass.cpp
new file mode 100644
index 00000000000000..c576e6612ececb
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/ctad.compile.pass.cpp
@@ -0,0 +1,37 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// template<class F, class... Rs>
+// zip_transform_view(F, Rs&&...) -> zip_transform_view<F, views::all_t<Rs>...>;
+
+#include <cassert>
+#include <ranges>
+#include <utility>
+
+#include "types.h"
+struct Container {
+  int* begin() const;
+  int* end() const;
+};
+
+void testCTAD() {
+  static_assert(std::is_same_v<decltype(std::ranges::zip_transform_view(Fn{}, Container{})),
+                               std::ranges::zip_transform_view<Fn, std::ranges::owning_view<Container>>>);
+
+  static_assert(std::is_same_v<decltype(std::ranges::zip_transform_view(Fn{}, Container{}, View{})),
+                               std::ranges::zip_transform_view<Fn, std::ranges::owning_view<Container>, View>>);
+
+  Container c{};
+  static_assert(
+      std::is_same_v<
+          decltype(std::ranges::zip_transform_view(Fn{}, Container{}, View{}, c)),
+          std::ranges::
+              zip_transform_view<Fn, std::ranges::owning_view<Container>, View, std::ranges::ref_view<Container>>>);
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/ctor.default.pass.cpp
new file mode 100644
index 00000000000000..96d625ae7dc32e
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/ctor.default.pass.cpp
@@ -0,0 +1,86 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// zip_transform_view() = default;
+
+#include <ranges>
+
+#include <cassert>
+#include <type_traits>
+#include <utility>
+
+constexpr int buff[] = {1, 2, 3};
+
+struct DefaultConstructibleView : std::ranges::view_base {
+  constexpr DefaultConstructibleView() : begin_(buff), end_(buff + 3) {}
+  constexpr int const* begin() const { return begin_; }
+  constexpr int const* end() const { return end_; }
+
+private:
+  int const* begin_;
+  int const* end_;
+};
+
+struct NonDefaultConstructibleView : std::ranges::view_base {
+  NonDefaultConstructibleView() = delete;
+  int* begin() const;
+  int* end() const;
+};
+
+struct DefaultConstructibleFn {
+  constexpr int operator()(const auto&... x) const { return (x + ...); }
+};
+
+struct NonDefaultConstructibleFn {
+  NonDefaultConstructibleFn() = delete;
+  constexpr int operator()(const auto&... x) const;
+};
+
+// The default constructor requires all underlying views to be default constructible.
+// It is implicitly required by the zip_view's constructor.
+static_assert(std::is_default_constructible_v<std::ranges::zip_transform_view< //
+                  DefaultConstructibleFn,                                      //
+                  DefaultConstructibleView>>);
+static_assert(std::is_default_constructible_v<std::ranges::zip_transform_view< //
+                  DefaultConstructibleFn,                                      //
+                  DefaultConstructibleView,
+                  DefaultConstructibleView>>);
+static_assert(!std::is_default_constructible_v<std::ranges::zip_transform_view< //
+                  NonDefaultConstructibleFn,                                    //
+                  DefaultConstructibleView>>);
+static_assert(!std::is_default_constructible_v<std::ranges::zip_transform_view< //
+                  DefaultConstructibleFn,                                       //
+                  NonDefaultConstructibleView>>);
+static_assert(!std::is_default_constructible_v<std::ranges::zip_transform_view< //
+                  DefaultConstructibleFn,                                       //
+                  DefaultConstructibleView,
+                  NonDefaultConstructibleView>>);
+
+constexpr bool test() {
+  {
+    using View =
+        std::ranges::zip_transform_view<DefaultConstructibleFn, DefaultConstructibleView, DefaultConstructibleView>;
+    View v = View(); // the default constructor is not explicit
+    assert(v.size() == 3);
+    auto it = v.begin();
+    assert(*it++ == 2);
+    assert(*it++ == 4);
+    assert(*it == 6);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/ctor.views.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/ctor.views.pass.cpp
new file mode 100644
index 00000000000000..99a2e6caaa4fb8
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/ctor.views.pass.cpp
@@ -0,0 +1,89 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// constexpr explicit zip_transform_view(F, Views...)
+
+#include <ranges>
+#include <tuple>
+
+#include "types.h"
+
+template <class T, class... Args>
+concept IsImplicitlyConstructible = requires(T val, Args... args) { val = {std::forward<Args>(args)...}; };
+
+// test constructor is explicit
+static_assert(std::constructible_from<std::ranges::zip_transform_view<Fn, View>, Fn, View>);
+static_assert(!IsImplicitlyConstructible<std::ranges::zip_transform_view<Fn, View>, Fn, View>);
+
+static_assert(std::constructible_from<std::ranges::zip_transform_view<Fn, View, View>, Fn, View, View>);
+static_assert(!IsImplicitlyConstructible<std::ranges::zip_transform_view<Fn, View, View>, Fn, View, View>);
+
+struct MoveAwareView : std::ranges::view_base {
+  int moves                 = 0;
+  constexpr MoveAwareView() = default;
+  constexpr MoveAwareView(MoveAwareView&& other) : moves(other.moves + 1) { other.moves = 1; }
+  constexpr MoveAwareView& operator=(MoveAwareView&& other) {
+    moves       = other.moves + 1;
+    other.moves = 0;
+    return *this;
+  }
+  constexpr const int* begin() const { return &moves; }
+  constexpr const int* end() const { return &moves + 1; }
+};
+
+template <class View1, class View2>
+constexpr void constructorTest(auto&& buffer1, auto&& buffer2) {
+  std::ranges::zip_transform_view v{MakeTuple{}, View1{buffer1}, View2{buffer2}};
+  auto [i, j] = *v.begin();
+  assert(i == buffer1[0]);
+  assert(j == buffer2[0]);
+};
+
+constexpr bool test() {
+  int buffer[8]  = {1, 2, 3, 4, 5, 6, 7, 8};
+  int buffer2[4] = {9, 8, 7, 6};
+
+  {
+    // constructor from views
+    std::ranges::zip_transform_view v(
+        MakeTuple{}, SizedRandomAccessView{buffer}, std::views::iota(0), std::ranges::single_view(2.));
+    auto [i, j, k] = *v.begin();
+    assert(i == 1);
+    assert(j == 0);
+    assert(k == 2.0);
+  }
+
+  {
+    // arguments are moved once
+    MoveAwareView mv;
+    std::ranges::zip_transform_view v{MakeTuple{}, std::move(mv), MoveAwareView{}};
+    auto [numMoves1, numMoves2] = *v.begin();
+    assert(numMoves1 == 3); // one move from the local variable to parameter, one move from parameter to member
+    assert(numMoves2 == 2);
+  }
+
+  // input and forward
+  { constructorTest<InputCommonView, ForwardSizedView>(buffer, buffer2); }
+
+  // bidi and random_access
+  { constructorTest<BidiCommonView, SizedRandomAccessView>(buffer, buffer2); }
+
+  // contiguous
+  { constructorTest<ContiguousCommonView, ContiguousCommonView>(buffer, buffer2); }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  //static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/end.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/end.pass.cpp
new file mode 100644
index 00000000000000..820c4675b8d2db
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/end.pass.cpp
@@ -0,0 +1,100 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// constexpr auto end()
+// constexpr auto end() const
+//   requires range<const InnerView> &&
+//            regular_invocable<const F&, range_reference_t<const Views>...>;
+
+#include <ranges>
+#include <tuple>
+
+#include "types.h"
+
+template <class T>
+concept HasConstEnd = requires(const T& ct) { ct.end(); };
+
+template <class T>
+concept HasEnd = requires(T& t) { t.end(); };
+
+constexpr bool test() {
+  int buffer[8] = {1, 2, 3, 4, 5, 6, 7, 8};
+  {
+    // simple test
+    std::ranges::zip_transform_view v(
+        MakeTuple{}, SimpleCommon{buffer}, std::views::iota(0), std::ranges::single_view(2.));
+    assert(v.begin() != v.end());
+    assert(std::as_const(v).begin() != std::as_const(v).end());
+    assert(v.begin() + 1 == v.end());
+    assert(std::as_const(v).begin() + 1 == std::as_const(v).end());
+  }
+
+  {
+    // with empty range
+    std::ranges::zip_transform_view v(MakeTuple{}, SimpleCommon{buffer}, std::ranges::empty_view<int>());
+    assert(v.begin() == v.end());
+    assert(std::as_const(v).begin() == std::as_const(v).end());
+  }
+
+  {
+    // common_range<InnerView>
+    std::ranges::zip_transform_view v(MakeTuple{}, SimpleCommon{buffer});
+    auto it       = v.begin();
+    auto const_it = std::as_const(v).begin();
+    auto st       = v.end();
+    auto const_st = std::as_const(v).end();
+
+    static_assert(!std::same_as<decltype(it), decltype(const_it)>);
+    static_assert(!std::same_as<decltype(st), decltype(const_st)>);
+    static_assert(std::same_as<decltype(it), decltype(st)>);
+    static_assert(std::same_as<decltype(const_it), decltype(const_st)>);
+
+    assert(it + 8 == st);
+    assert(const_it + 8 == const_st);
+  }
+  {
+    // !common_range<InnerView>
+    std::ranges::zip_transform_view v(MakeTuple{}, SimpleNonCommon{buffer});
+    auto it       = v.begin();
+    auto const_it = std::as_const(v).begin();
+    auto st       = v.end();
+    auto const_st = std::as_const(v).end();
+
+    static_assert(!std::same_as<decltype(it), decltype(const_it)>);
+    static_assert(!std::same_as<decltype(st), decltype(const_st)>);
+    static_assert(!std::same_as<decltype(it), decltype(st)>);
+    static_assert(!std::same_as<decltype(const_it), decltype(const_st)>);
+
+    assert(it + 8 == st);
+    assert(const_it + 8 == const_st);
+  }
+
+  {
+    // underlying const R is not a range
+    using View = std::ranges::zip_transform_view<MakeTuple, SimpleCommon, NoConstBeginView>;
+    static_assert(HasEnd<View>);
+    static_assert(!HasConstEnd<View>);
+  }
+
+  {
+    // Fn cannot invoke on const range
+    using View = std::ranges::zip_transform_view<NonConstOnlyFn, ConstNonConstDifferentView>;
+    static_assert(HasEnd<View>);
+    static_assert(!HasConstEnd<View>);
+  }
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/general.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/general.pass.cpp
new file mode 100644
index 00000000000000..3c35de27deb692
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/general.pass.cpp
@@ -0,0 +1,29 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// Some basic examples of how zip_tranform_view might be used in the wild. This is a general
+// collection of sample algorithms and functions that try to mock general usage of
+// this view.
+
+#include <ranges>
+
+#include <algorithm>
+#include <cassert>
+#include <functional>
+#include <vector>
+
+int main(int, char**) {
+  std::vector v1 = {1, 2};
+  std::vector v2 = {4, 5, 6};
+  auto ztv       = std::views::zip_transform(std::plus(), v1, v2);
+  auto expected  = {5, 7};
+  assert(std::ranges::equal(ztv, expected));
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/size.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/size.pass.cpp
new file mode 100644
index 00000000000000..c973f7cc4fcbc3
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/size.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
+
+// constexpr auto size() requires sized_range<InnerView>
+// constexpr auto size() const requires sized_range<const InnerView>
+
+#include <ranges>
+
+#include <cassert>
+#include <tuple>
+#include <utility>
+
+#include "test_iterators.h"
+#include "types.h"
+
+int buffer[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+struct SizedView : std::ranges::view_base {
+  std::size_t size_ = 0;
+  constexpr SizedView(std::size_t s) : size_(s) {}
+  constexpr auto begin() const { return buffer; }
+  constexpr auto end() const { return buffer + size_; }
+};
+
+struct SizedNonConst : std::ranges::view_base {
+  using iterator    = forward_iterator<int*>;
+  std::size_t size_ = 0;
+  constexpr SizedNonConst(std::size_t s) : size_(s) {}
+  constexpr auto begin() const { return iterator{buffer}; }
+  constexpr auto end() const { return iterator{buffer + size_}; }
+  constexpr std::size_t size() { return size_; }
+};
+
+struct ConstNonConstDifferentSize : std::ranges::view_base {
+  constexpr auto begin() const { return buffer; }
+  constexpr auto end() const { return buffer + 8; }
+
+  constexpr auto size() { return 5; }
+  constexpr auto size() const { return 6; }
+};
+
+constexpr bool test() {
+  {
+    // single range
+    std::ranges::zip_transform_view v(MakeTuple{}, SizedView(8));
+    assert(v.size() == 8);
+    assert(std::as_const(v).size() == 8);
+  }
+
+  {
+    // multiple ranges
+    std::ranges::zip_transform_view v(MakeTuple{}, SizedView(2), SizedView(3));
+    assert(v.size() == 2);
+    assert(std::as_const(v).size() == 2);
+  }
+
+  {
+    // const-view non-sized range
+    std::ranges::zip_transform_view v(MakeTuple{}, SizedNonConst(2), SizedView(3));
+    assert(v.size() == 2);
+    static_assert(std::ranges::sized_range<decltype(v)>);
+    static_assert(!std::ranges::sized_range<decltype(std::as_const(v))>);
+  }
+
+  {
+    // const/non-const has different sizes
+    std::ranges::zip_transform_view v(MakeTuple{}, ConstNonConstDifferentSize{});
+    assert(v.size() == 5);
+    assert(std::as_const(v).size() == 6);
+  }
+
+  {
+    // underlying range not sized
+    std::ranges::zip_transform_view v(MakeTuple{}, InputCommonView{buffer});
+    static_assert(!std::ranges::sized_range<decltype(v)>);
+    static_assert(!std::ranges::sized_range<decltype(std::as_const(v))>);
+  }
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/types.h b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/types.h
new file mode 100644
index 00000000000000..02ce0131a78fc5
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/types.h
@@ -0,0 +1,497 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_ZIP_TRANSFORM_TYPES_H
+#define TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_ZIP_TRANSFORM_TYPES_H
+
+#include <functional>
+#include <ranges>
+
+#include "test_macros.h"
+#include "test_iterators.h"
+#include "test_range.h"
+
+#if TEST_STD_VER <= 20
+#  error "range.zip.transform/types.h" can only be included in builds supporting C++20
+#endif // TEST_STD_VER <= 20
+
+struct View : std::ranges::view_base {
+  int* begin() const;
+  int* end() const;
+};
+
+struct Fn {
+  int operator()(auto&&...) const;
+};
+
+struct MakeTuple {
+  constexpr auto operator()(auto&&... args) const { return std::tuple(std::forward<decltype(args)>(args)...); }
+};
+
+struct NoConstBeginView : std::ranges::view_base {
+  int* begin();
+  int* end();
+};
+
+struct ConstNonConstDifferentView : std::ranges::view_base {
+  int* begin();
+  const int* begin() const;
+  int* end();
+  const int* end() const;
+};
+
+struct NonConstOnlyFn {
+  int operator()(int&) const;
+  int operator()(const int&) const = delete;
+};
+
+
+template <class T>
+struct BufferView : std::ranges::view_base {
+  T* buffer_;
+  std::size_t size_;
+
+  template <std::size_t N>
+  constexpr BufferView(T (&b)[N]) : buffer_(b), size_(N) {}
+};
+
+using IntBufferView = BufferView<int>;
+
+template <bool Simple>
+struct Common : IntBufferView {
+  using IntBufferView::IntBufferView;
+
+  constexpr int* begin()
+    requires(!Simple)
+  {
+    return buffer_;
+  }
+  constexpr const int* begin() const { return buffer_; }
+  constexpr int* end()
+    requires(!Simple)
+  {
+    return buffer_ + size_;
+  }
+  constexpr const int* end() const { return buffer_ + size_; }
+};
+using SimpleCommon    = Common<true>;
+using NonSimpleCommon = Common<false>;
+
+using SimpleCommonRandomAccessSized    = SimpleCommon;
+using NonSimpleCommonRandomAccessSized = NonSimpleCommon;
+
+static_assert(std::ranges::common_range<Common<true>>);
+static_assert(std::ranges::random_access_range<SimpleCommon>);
+static_assert(std::ranges::sized_range<SimpleCommon>);
+static_assert(simple_view<SimpleCommon>);
+static_assert(!simple_view<NonSimpleCommon>);
+
+template <bool Simple>
+struct CommonNonRandom : IntBufferView {
+  using IntBufferView::IntBufferView;
+  using const_iterator = forward_iterator<const int*>;
+  using iterator       = forward_iterator<int*>;
+  constexpr iterator begin()
+    requires(!Simple)
+  {
+    return iterator(buffer_);
+  }
+  constexpr const_iterator begin() const { return const_iterator(buffer_); }
+  constexpr iterator end()
+    requires(!Simple)
+  {
+    return iterator(buffer_ + size_);
+  }
+  constexpr const_iterator end() const { return const_iterator(buffer_ + size_); }
+};
+
+using SimpleCommonNonRandom    = CommonNonRandom<true>;
+using NonSimpleCommonNonRandom = CommonNonRandom<false>;
+
+static_assert(std::ranges::common_range<SimpleCommonNonRandom>);
+static_assert(!std::ranges::random_access_range<SimpleCommonNonRandom>);
+static_assert(!std::ranges::sized_range<SimpleCommonNonRandom>);
+static_assert(simple_view<SimpleCommonNonRandom>);
+static_assert(!simple_view<NonSimpleCommonNonRandom>);
+
+template <bool Simple>
+struct NonCommon : IntBufferView {
+  using IntBufferView::IntBufferView;
+  constexpr int* begin()
+    requires(!Simple)
+  {
+    return buffer_;
+  }
+  constexpr const int* begin() const { return buffer_; }
+  constexpr sentinel_wrapper<int*> end()
+    requires(!Simple)
+  {
+    return sentinel_wrapper<int*>(buffer_ + size_);
+  }
+  constexpr sentinel_wrapper<const int*> end() const { return sentinel_wrapper<const int*>(buffer_ + size_); }
+};
+
+using SimpleNonCommon    = NonCommon<true>;
+using NonSimpleNonCommon = NonCommon<false>;
+
+static_assert(!std::ranges::common_range<SimpleNonCommon>);
+static_assert(std::ranges::random_access_range<SimpleNonCommon>);
+static_assert(!std::ranges::sized_range<SimpleNonCommon>);
+static_assert(simple_view<SimpleNonCommon>);
+static_assert(!simple_view<NonSimpleNonCommon>);
+
+template <bool Simple>
+struct NonCommonSized : IntBufferView {
+  using IntBufferView::IntBufferView;
+  constexpr int* begin()
+    requires(!Simple)
+  {
+    return buffer_;
+  }
+  constexpr const int* begin() const { return buffer_; }
+  constexpr sentinel_wrapper<int*> end()
+    requires(!Simple)
+  {
+    return sentinel_wrapper<int*>(buffer_ + size_);
+  }
+  constexpr sentinel_wrapper<const int*> end() const { return sentinel_wrapper<const int*>(buffer_ + size_); }
+  constexpr std::size_t size() const { return size_; }
+};
+
+using SimpleNonCommonSized                = NonCommonSized<true>;
+using SimpleNonCommonRandomAccessSized    = SimpleNonCommonSized;
+using NonSimpleNonCommonSized             = NonCommonSized<false>;
+using NonSimpleNonCommonRandomAccessSized = NonSimpleNonCommonSized;
+
+static_assert(!std::ranges::common_range<SimpleNonCommonSized>);
+static_assert(std::ranges::random_access_range<SimpleNonCommonSized>);
+static_assert(std::ranges::sized_range<SimpleNonCommonSized>);
+static_assert(simple_view<SimpleNonCommonSized>);
+static_assert(!simple_view<NonSimpleNonCommonSized>);
+
+template <bool Simple>
+struct NonCommonNonRandom : IntBufferView {
+  using IntBufferView::IntBufferView;
+
+  using const_iterator = forward_iterator<const int*>;
+  using iterator       = forward_iterator<int*>;
+
+  constexpr iterator begin()
+    requires(!Simple)
+  {
+    return iterator(buffer_);
+  }
+  constexpr const_iterator begin() const { return const_iterator(buffer_); }
+  constexpr sentinel_wrapper<iterator> end()
+    requires(!Simple)
+  {
+    return sentinel_wrapper<iterator>(iterator(buffer_ + size_));
+  }
+  constexpr sentinel_wrapper<const_iterator> end() const {
+    return sentinel_wrapper<const_iterator>(const_iterator(buffer_ + size_));
+  }
+};
+
+using SimpleNonCommonNonRandom    = NonCommonNonRandom<true>;
+using NonSimpleNonCommonNonRandom = NonCommonNonRandom<false>;
+
+static_assert(!std::ranges::common_range<SimpleNonCommonNonRandom>);
+static_assert(!std::ranges::random_access_range<SimpleNonCommonNonRandom>);
+static_assert(!std::ranges::sized_range<SimpleNonCommonNonRandom>);
+static_assert(simple_view<SimpleNonCommonNonRandom>);
+static_assert(!simple_view<NonSimpleNonCommonNonRandom>);
+
+template <class Iter, class Sent = Iter, class NonConstIter = Iter, class NonConstSent = Sent>
+struct BasicView : IntBufferView {
+  using IntBufferView::IntBufferView;
+
+  constexpr NonConstIter begin()
+    requires(!std::is_same_v<Iter, NonConstIter>)
+  {
+    return NonConstIter(buffer_);
+  }
+  constexpr Iter begin() const { return Iter(buffer_); }
+
+  constexpr NonConstSent end()
+    requires(!std::is_same_v<Sent, NonConstSent>)
+  {
+    if constexpr (std::is_same_v<NonConstIter, NonConstSent>) {
+      return NonConstIter(buffer_ + size_);
+    } else {
+      return NonConstSent(NonConstIter(buffer_ + size_));
+    }
+  }
+
+  constexpr Sent end() const {
+    if constexpr (std::is_same_v<Iter, Sent>) {
+      return Iter(buffer_ + size_);
+    } else {
+      return Sent(Iter(buffer_ + size_));
+    }
+  }
+};
+
+template <class Base = int*>
+struct forward_sized_iterator {
+  Base it_ = nullptr;
+
+  using iterator_category = std::forward_iterator_tag;
+  using value_type        = int;
+  using difference_type   = std::intptr_t;
+  using pointer           = Base;
+  using reference         = decltype(*Base{});
+
+  forward_sized_iterator() = default;
+  constexpr forward_sized_iterator(Base it) : it_(it) {}
+
+  constexpr reference operator*() const { return *it_; }
+
+  constexpr forward_sized_iterator& operator++() {
+    ++it_;
+    return *this;
+  }
+  constexpr forward_sized_iterator operator++(int) { return forward_sized_iterator(it_++); }
+
+  friend constexpr bool operator==(const forward_sized_iterator&, const forward_sized_iterator&) = default;
+
+  friend constexpr difference_type operator-(const forward_sized_iterator& x, const forward_sized_iterator& y) {
+    return x.it_ - y.it_;
+  }
+};
+static_assert(std::forward_iterator<forward_sized_iterator<>>);
+static_assert(std::sized_sentinel_for<forward_sized_iterator<>, forward_sized_iterator<>>);
+
+using ForwardSizedView = BasicView<forward_sized_iterator<>>;
+static_assert(std::ranges::forward_range<ForwardSizedView>);
+static_assert(std::ranges::sized_range<ForwardSizedView>);
+static_assert(std::ranges::common_range<ForwardSizedView>);
+static_assert(!std::ranges::random_access_range<ForwardSizedView>);
+static_assert(simple_view<ForwardSizedView>);
+
+using NonSimpleForwardSizedView =
+    BasicView<forward_sized_iterator<const int*>,
+              forward_sized_iterator<const int*>,
+              forward_sized_iterator<int*>,
+              forward_sized_iterator<int*>>;
+static_assert(std::ranges::forward_range<NonSimpleForwardSizedView>);
+static_assert(std::ranges::sized_range<NonSimpleForwardSizedView>);
+static_assert(std::ranges::common_range<NonSimpleForwardSizedView>);
+static_assert(!std::ranges::random_access_range<NonSimpleForwardSizedView>);
+static_assert(!simple_view<NonSimpleForwardSizedView>);
+
+using ForwardSizedNonCommon = BasicView<forward_sized_iterator<>, sized_sentinel<forward_sized_iterator<>>>;
+static_assert(std::ranges::forward_range<ForwardSizedNonCommon>);
+static_assert(std::ranges::sized_range<ForwardSizedNonCommon>);
+static_assert(!std::ranges::common_range<ForwardSizedNonCommon>);
+static_assert(!std::ranges::random_access_range<ForwardSizedNonCommon>);
+static_assert(simple_view<ForwardSizedNonCommon>);
+
+using NonSimpleForwardSizedNonCommon =
+    BasicView<forward_sized_iterator<const int*>,
+              sized_sentinel<forward_sized_iterator<const int*>>,
+              forward_sized_iterator<int*>,
+              sized_sentinel<forward_sized_iterator<int*>>>;
+static_assert(std::ranges::forward_range<NonSimpleForwardSizedNonCommon>);
+static_assert(std::ranges::sized_range<NonSimpleForwardSizedNonCommon>);
+static_assert(!std::ranges::common_range<NonSimpleForwardSizedNonCommon>);
+static_assert(!std::ranges::random_access_range<NonSimpleForwardSizedNonCommon>);
+static_assert(!simple_view<NonSimpleForwardSizedNonCommon>);
+
+struct SizedRandomAccessView : IntBufferView {
+  using IntBufferView::IntBufferView;
+  using iterator = random_access_iterator<int*>;
+
+  constexpr auto begin() const { return iterator(buffer_); }
+  constexpr auto end() const { return sized_sentinel<iterator>(iterator(buffer_ + size_)); }
+
+  constexpr decltype(auto) operator[](std::size_t n) const { return *(begin() + n); }
+};
+static_assert(std::ranges::view<SizedRandomAccessView>);
+static_assert(std::ranges::random_access_range<SizedRandomAccessView>);
+static_assert(std::ranges::sized_range<SizedRandomAccessView>);
+
+using NonSizedRandomAccessView =
+    BasicView<random_access_iterator<int*>, sentinel_wrapper<random_access_iterator<int*>>>;
+static_assert(!std::ranges::contiguous_range<NonSizedRandomAccessView>);
+static_assert(std::ranges::random_access_range<SizedRandomAccessView>);
+static_assert(!std::ranges::common_range<NonSizedRandomAccessView>);
+static_assert(!std::ranges::sized_range<NonSizedRandomAccessView>);
+static_assert(simple_view<NonSizedRandomAccessView>);
+
+using NonSimpleNonSizedRandomAccessView =
+    BasicView<random_access_iterator<const int*>,
+              sentinel_wrapper<random_access_iterator<const int*>>,
+              random_access_iterator<int*>,
+              sentinel_wrapper<random_access_iterator<int*>> >;
+static_assert(!std::ranges::contiguous_range<NonSimpleNonSizedRandomAccessView>);
+static_assert(std::ranges::random_access_range<NonSimpleNonSizedRandomAccessView>);
+static_assert(!std::ranges::common_range<NonSimpleNonSizedRandomAccessView>);
+static_assert(!std::ranges::sized_range<NonSimpleNonSizedRandomAccessView>);
+static_assert(!simple_view<NonSimpleNonSizedRandomAccessView>);
+
+using ContiguousCommonView = BasicView<int*>;
+static_assert(std::ranges::contiguous_range<ContiguousCommonView>);
+static_assert(std::ranges::common_range<ContiguousCommonView>);
+static_assert(std::ranges::sized_range<ContiguousCommonView>);
+
+using ContiguousNonCommonView = BasicView<int*, sentinel_wrapper<int*>>;
+static_assert(std::ranges::contiguous_range<ContiguousNonCommonView>);
+static_assert(!std::ranges::common_range<ContiguousNonCommonView>);
+static_assert(!std::ranges::sized_range<ContiguousNonCommonView>);
+
+using ContiguousNonCommonSized = BasicView<int*, sized_sentinel<int*>>;
+
+static_assert(std::ranges::contiguous_range<ContiguousNonCommonSized>);
+static_assert(!std::ranges::common_range<ContiguousNonCommonSized>);
+static_assert(std::ranges::sized_range<ContiguousNonCommonSized>);
+
+using InputCommonView = BasicView<common_input_iterator<int*>>;
+static_assert(std::ranges::input_range<InputCommonView>);
+static_assert(!std::ranges::forward_range<InputCommonView>);
+static_assert(std::ranges::common_range<InputCommonView>);
+static_assert(simple_view<InputCommonView>);
+
+using NonSimpleInputCommonView =
+    BasicView<common_input_iterator<const int*>,
+              common_input_iterator<const int*>,
+              common_input_iterator<int*>,
+              common_input_iterator<int*>>;
+static_assert(std::ranges::input_range<NonSimpleInputCommonView>);
+static_assert(!std::ranges::forward_range<NonSimpleInputCommonView>);
+static_assert(std::ranges::common_range<NonSimpleInputCommonView>);
+static_assert(!simple_view<NonSimpleInputCommonView>);
+
+using InputNonCommonView = BasicView<common_input_iterator<int*>, sentinel_wrapper<common_input_iterator<int*>>>;
+static_assert(std::ranges::input_range<InputNonCommonView>);
+static_assert(!std::ranges::forward_range<InputNonCommonView>);
+static_assert(!std::ranges::common_range<InputNonCommonView>);
+static_assert(simple_view<InputNonCommonView>);
+
+using NonSimpleInputNonCommonView =
+    BasicView<common_input_iterator<const int*>,
+              sentinel_wrapper<common_input_iterator<const int*>>,
+              common_input_iterator<int*>,
+              sentinel_wrapper<common_input_iterator<int*>>>;
+static_assert(std::ranges::input_range<InputNonCommonView>);
+static_assert(!std::ranges::forward_range<InputNonCommonView>);
+static_assert(!std::ranges::common_range<InputNonCommonView>);
+static_assert(!simple_view<NonSimpleInputNonCommonView>);
+
+using BidiCommonView = BasicView<bidirectional_iterator<int*>>;
+static_assert(!std::ranges::sized_range<BidiCommonView>);
+static_assert(std::ranges::bidirectional_range<BidiCommonView>);
+static_assert(!std::ranges::random_access_range<BidiCommonView>);
+static_assert(std::ranges::common_range<BidiCommonView>);
+static_assert(simple_view<BidiCommonView>);
+
+using NonSimpleBidiCommonView =
+    BasicView<bidirectional_iterator<const int*>,
+              bidirectional_iterator<const int*>,
+              bidirectional_iterator<int*>,
+              bidirectional_iterator<int*>>;
+static_assert(!std::ranges::sized_range<NonSimpleBidiCommonView>);
+static_assert(std::ranges::bidirectional_range<NonSimpleBidiCommonView>);
+static_assert(!std::ranges::random_access_range<NonSimpleBidiCommonView>);
+static_assert(std::ranges::common_range<NonSimpleBidiCommonView>);
+static_assert(!simple_view<NonSimpleBidiCommonView>);
+
+struct SizedBidiCommon : BidiCommonView {
+  using BidiCommonView::BidiCommonView;
+  std::size_t size() const { return base(end()) - base(begin()); }
+};
+static_assert(std::ranges::sized_range<SizedBidiCommon>);
+static_assert(std::ranges::bidirectional_range<SizedBidiCommon>);
+static_assert(!std::ranges::random_access_range<SizedBidiCommon>);
+static_assert(std::ranges::common_range<SizedBidiCommon>);
+static_assert(simple_view<SizedBidiCommon>);
+
+struct NonSimpleSizedBidiCommon : NonSimpleBidiCommonView {
+  using NonSimpleBidiCommonView::NonSimpleBidiCommonView;
+  std::size_t size() const { return base(end()) - base(begin()); }
+};
+static_assert(std::ranges::sized_range<NonSimpleSizedBidiCommon>);
+static_assert(std::ranges::bidirectional_range<NonSimpleSizedBidiCommon>);
+static_assert(!std::ranges::random_access_range<NonSimpleSizedBidiCommon>);
+static_assert(std::ranges::common_range<NonSimpleSizedBidiCommon>);
+static_assert(!simple_view<NonSimpleSizedBidiCommon>);
+
+using BidiNonCommonView = BasicView<bidirectional_iterator<int*>, sentinel_wrapper<bidirectional_iterator<int*>>>;
+static_assert(!std::ranges::sized_range<BidiNonCommonView>);
+static_assert(std::ranges::bidirectional_range<BidiNonCommonView>);
+static_assert(!std::ranges::random_access_range<BidiNonCommonView>);
+static_assert(!std::ranges::common_range<BidiNonCommonView>);
+static_assert(simple_view<BidiNonCommonView>);
+
+using NonSimpleBidiNonCommonView =
+    BasicView<bidirectional_iterator<const int*>,
+              sentinel_wrapper<bidirectional_iterator<const int*>>,
+              bidirectional_iterator<int*>,
+              sentinel_wrapper<bidirectional_iterator<int*>>>;
+static_assert(!std::ranges::sized_range<NonSimpleBidiNonCommonView>);
+static_assert(std::ranges::bidirectional_range<NonSimpleBidiNonCommonView>);
+static_assert(!std::ranges::random_access_range<NonSimpleBidiNonCommonView>);
+static_assert(!std::ranges::common_range<NonSimpleBidiNonCommonView>);
+static_assert(!simple_view<NonSimpleBidiNonCommonView>);
+
+using SizedBidiNonCommonView = BasicView<bidirectional_iterator<int*>, sized_sentinel<bidirectional_iterator<int*>>>;
+static_assert(std::ranges::sized_range<SizedBidiNonCommonView>);
+static_assert(std::ranges::bidirectional_range<SizedBidiNonCommonView>);
+static_assert(!std::ranges::random_access_range<SizedBidiNonCommonView>);
+static_assert(!std::ranges::common_range<SizedBidiNonCommonView>);
+static_assert(simple_view<SizedBidiNonCommonView>);
+
+using NonSimpleSizedBidiNonCommonView =
+    BasicView<bidirectional_iterator<const int*>,
+              sized_sentinel<bidirectional_iterator<const int*>>,
+              bidirectional_iterator<int*>,
+              sized_sentinel<bidirectional_iterator<int*>>>;
+static_assert(std::ranges::sized_range<NonSimpleSizedBidiNonCommonView>);
+static_assert(std::ranges::bidirectional_range<NonSimpleSizedBidiNonCommonView>);
+static_assert(!std::ranges::random_access_range<NonSimpleSizedBidiNonCommonView>);
+static_assert(!std::ranges::common_range<NonSimpleSizedBidiNonCommonView>);
+static_assert(!simple_view<NonSimpleSizedBidiNonCommonView>);
+
+namespace adltest {
+struct iter_move_swap_iterator {
+  std::reference_wrapper<int> iter_move_called_times;
+  std::reference_wrapper<int> iter_swap_called_times;
+  int i = 0;
+
+  using iterator_category = std::input_iterator_tag;
+  using value_type        = int;
+  using difference_type   = std::intptr_t;
+
+  constexpr int operator*() const { return i; }
+
+  constexpr iter_move_swap_iterator& operator++() {
+    ++i;
+    return *this;
+  }
+  constexpr void operator++(int) { ++i; }
+
+  friend constexpr bool operator==(const iter_move_swap_iterator& x, std::default_sentinel_t) { return x.i == 5; }
+
+  friend constexpr int iter_move(iter_move_swap_iterator const& it) {
+    ++it.iter_move_called_times;
+    return it.i;
+  }
+  friend constexpr void iter_swap(iter_move_swap_iterator const& x, iter_move_swap_iterator const& y) {
+    ++x.iter_swap_called_times;
+    ++y.iter_swap_called_times;
+  }
+};
+
+struct IterMoveSwapRange {
+  int iter_move_called_times = 0;
+  int iter_swap_called_times = 0;
+  constexpr auto begin() { return iter_move_swap_iterator{iter_move_called_times, iter_swap_called_times}; }
+  constexpr auto end() const { return std::default_sentinel; }
+};
+} // namespace adltest
+
+#endif // TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_ZIP_TRANSFORM_TYPES_H
diff --git a/libcxx/test/std/ranges/ranges_robust_against_no_unique_address.pass.cpp b/libcxx/test/std/ranges/ranges_robust_against_no_unique_address.pass.cpp
index 3b35271d649c38..06186656f01222 100644
--- a/libcxx/test/std/ranges/ranges_robust_against_no_unique_address.pass.cpp
+++ b/libcxx/test/std/ranges/ranges_robust_against_no_unique_address.pass.cpp
@@ -58,6 +58,7 @@ constexpr bool test() {
 #if TEST_STD_VER >= 23
   testOne<std::ranges::chunk_by_view<View, Pred>>();
   testOne<std::ranges::repeat_view<Pred>>();
+  testOne<std::ranges::zip_transform_view<Pred, View>>();
 #endif
   return true;
 }

>From ba11db4eea274850371a20385653ae17a0180f6d Mon Sep 17 00:00:00 2001
From: Hui <hui.xie0621 at gmail.com>
Date: Mon, 29 Jan 2024 22:35:37 +0000
Subject: [PATCH 2/2] add tests

---
 .../range.zip.transform/begin.pass.cpp        |   2 -
 .../range.zip.transform/cpo.pass.cpp          |   4 +-
 .../range.zip.transform/ctad.compile.pass.cpp |   1 -
 .../range.zip.transform/ctor.default.pass.cpp |   1 -
 .../range.zip.transform/ctor.views.pass.cpp   |   1 -
 .../range.zip.transform/end.pass.cpp          |   1 -
 .../iterator/arithmetic.pass.cpp              | 139 ++++++++++++
 .../iterator/compare.pass.cpp                 | 160 ++++++++++++++
 .../iterator/ctor.default.pass.cpp            |  50 +++++
 .../iterator/ctor.other.pass.cpp              |  63 ++++++
 .../iterator/decrement.pass.cpp               |  84 +++++++
 .../iterator/deref.pass.cpp                   |  97 ++++++++
 .../iterator/increment.pass.cpp               |  80 +++++++
 .../iterator/member_types.compile.pass.cpp    | 158 +++++++++++++
 .../iterator/subscript.pass.cpp               |  68 ++++++
 .../sentinel/ctor.default.pass.cpp            |  51 +++++
 .../sentinel/ctor.other.pass.cpp              |  76 +++++++
 .../range.zip.transform/sentinel/eq.pass.cpp  | 142 ++++++++++++
 .../sentinel/minus.pass.cpp                   | 209 ++++++++++++++++++
 .../range.zip.transform/size.pass.cpp         |   2 -
 .../range.zip.transform/types.h               |   7 +-
 21 files changed, 1383 insertions(+), 13 deletions(-)
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/arithmetic.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/compare.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/ctor.default.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/ctor.other.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/decrement.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/deref.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/increment.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/member_types.compile.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/subscript.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.zip.transform/sentinel/ctor.default.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.zip.transform/sentinel/ctor.other.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.zip.transform/sentinel/eq.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.zip.transform/sentinel/minus.pass.cpp

diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/begin.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/begin.pass.cpp
index f63a944bfde19c..fe91fe09830131 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/begin.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/begin.pass.cpp
@@ -17,8 +17,6 @@
 
 #include <cassert>
 #include <concepts>
-#include <tuple>
-#include <utility>
 
 #include "types.h"
 
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/cpo.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/cpo.pass.cpp
index ec3f734a0b05bd..9d6be5815fa35f 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/cpo.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/cpo.pass.cpp
@@ -12,12 +12,10 @@
 
 #include <ranges>
 
-#include <array>
 #include <algorithm>
+#include <array>
 #include <cassert>
-#include <tuple>
 #include <type_traits>
-#include <utility>
 
 struct NotMoveConstructible {
   NotMoveConstructible()                       = default;
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/ctad.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/ctad.compile.pass.cpp
index c576e6612ececb..9eed2fb37d49f8 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/ctad.compile.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/ctad.compile.pass.cpp
@@ -13,7 +13,6 @@
 
 #include <cassert>
 #include <ranges>
-#include <utility>
 
 #include "types.h"
 struct Container {
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/ctor.default.pass.cpp
index 96d625ae7dc32e..87d05d49e956df 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/ctor.default.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/ctor.default.pass.cpp
@@ -14,7 +14,6 @@
 
 #include <cassert>
 #include <type_traits>
-#include <utility>
 
 constexpr int buff[] = {1, 2, 3};
 
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/ctor.views.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/ctor.views.pass.cpp
index 99a2e6caaa4fb8..0c101431513794 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/ctor.views.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/ctor.views.pass.cpp
@@ -11,7 +11,6 @@
 // constexpr explicit zip_transform_view(F, Views...)
 
 #include <ranges>
-#include <tuple>
 
 #include "types.h"
 
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/end.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/end.pass.cpp
index 820c4675b8d2db..c887adcf4a1d36 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/end.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/end.pass.cpp
@@ -14,7 +14,6 @@
 //            regular_invocable<const F&, range_reference_t<const Views>...>;
 
 #include <ranges>
-#include <tuple>
 
 #include "types.h"
 
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/arithmetic.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/arithmetic.pass.cpp
new file mode 100644
index 00000000000000..99e604307b9a62
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/arithmetic.pass.cpp
@@ -0,0 +1,139 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+//  constexpr iterator& operator+=(difference_type x) requires random_access_range<Base>;
+//  constexpr iterator& operator-=(difference_type x) requires random_access_range<Base>;
+//  friend constexpr iterator operator+(const iterator& i, difference_type n)
+//    requires random_access_range<Base>;
+//  friend constexpr iterator operator+(difference_type n, const iterator& i)
+//    requires random_access_range<Base>;
+//  friend constexpr iterator operator-(const iterator& i, difference_type n)
+//    requires random_access_range<Base>;
+//  friend constexpr difference_type operator-(const iterator& x, const iterator& y)
+//    requires sized_sentinel_for<ziperator<Const>, ziperator<Const>>;
+
+#include <ranges>
+
+#include <array>
+#include <concepts>
+#include <functional>
+
+#include "../types.h"
+
+template <class T, class U>
+concept canPlusEqual = requires(T& t, U& u) { t += u; };
+
+template <class T, class U>
+concept canPlus = requires(T& t, U& u) { t + u; };
+
+template <class T, class U>
+concept canMinusEqual = requires(T& t, U& u) { t -= u; };
+
+template <class T, class U>
+concept canMinus = requires(T& t, U& u) { t - u; };
+
+constexpr bool test() {
+  int buffer1[5] = {1, 2, 3, 4, 5};
+  SizedRandomAccessView a{buffer1};
+  static_assert(std::ranges::random_access_range<decltype(a)>);
+
+  std::array b{4.1, 3.2, 4.3, 0.1, 0.2};
+  static_assert(std::ranges::contiguous_range<decltype(b)>);
+
+  {
+    // operator+(x, n) and operator+=
+    std::ranges::zip_transform_view v(MakeTuple{}, a, b);
+    auto it1   = v.begin();
+    using Iter = decltype(it1);
+
+    std::same_as<Iter> decltype(auto) it2 = it1 + 3;
+    assert(*it2 == std::tuple(4, 0.1));
+
+    std::same_as<Iter> decltype(auto) it3 = 3 + it1;
+    assert(*it3 == std::tuple(4, 0.1));
+
+    std::same_as<Iter&> decltype(auto) it1_ref = it1 += 3;
+    assert(&it1_ref == &it1);
+    assert(*it1_ref == std::tuple(4, 0.1));
+    assert(*it1 == std::tuple(4, 0.1));
+
+    static_assert(canPlus<Iter, std::intptr_t>);
+    static_assert(canPlusEqual<Iter, std::intptr_t>);
+  }
+
+  {
+    // operator-(x, n) and operator-=
+    std::ranges::zip_transform_view v(MakeTuple{}, a, b);
+    auto it1   = v.end();
+    using Iter = decltype(it1);
+
+    std::same_as<Iter> decltype(auto) it2 = it1 - 3;
+    assert(*it2 == std::tuple(3, 4.3));
+
+    std::same_as<Iter&> decltype(auto) it1_ref = it1 -= 3;
+    assert(&it1_ref == &it1);
+    assert(*it1_ref == std::tuple(3, 4.3));
+    assert(*it1 == std::tuple(3, 4.3));
+
+    static_assert(canMinusEqual<Iter, std::intptr_t>);
+    static_assert(canMinus<Iter, std::intptr_t>);
+  }
+
+  {
+    // operator-(x, y)
+    std::ranges::zip_transform_view v(MakeTuple{}, a, b);
+    assert((v.end() - v.begin()) == 5);
+
+    auto it1 = v.begin() + 2;
+    auto it2 = v.end() - 1;
+
+    using Iter = decltype(it1);
+
+    std::same_as<std::iter_difference_t<Iter>> decltype(auto) n = it1 - it2;
+    assert(n == -2);
+  }
+
+  {
+    // One of the ranges is not random access
+    std::ranges::zip_transform_view v(MakeTuple{}, a, b, ForwardSizedView{buffer1});
+    auto it1   = v.begin();
+    using Iter = decltype(it1);
+    static_assert(!canPlus<Iter, std::intptr_t>);
+    static_assert(!canPlus<std::intptr_t, Iter>);
+    static_assert(!canPlusEqual<Iter, std::intptr_t>);
+    static_assert(!canMinus<Iter, std::intptr_t>);
+    static_assert(canMinus<Iter, Iter>);
+    static_assert(!canMinusEqual<Iter, std::intptr_t>);
+
+    auto it2 = ++v.begin();
+    assert((it2 - it1) == 1);
+  }
+
+  {
+    // One of the ranges does not have sized sentinel
+    std::ranges::zip_transform_view v(MakeTuple{}, a, b, InputCommonView{buffer1});
+    using Iter = decltype(v.begin());
+    static_assert(!canPlus<Iter, std::intptr_t>);
+    static_assert(!canPlus<std::intptr_t, Iter>);
+    static_assert(!canPlusEqual<Iter, std::intptr_t>);
+    static_assert(!canMinus<Iter, std::intptr_t>);
+    static_assert(!canMinus<Iter, Iter>);
+    static_assert(!canMinusEqual<Iter, std::intptr_t>);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/compare.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/compare.pass.cpp
new file mode 100644
index 00000000000000..e879a181bbf562
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/compare.pass.cpp
@@ -0,0 +1,160 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// friend constexpr bool operator==(const iterator& x, const iterator& y)
+//   requires equality_comparable<ziperator<Const>>;
+
+// friend constexpr auto operator<=>(const iterator& x, const iterator& y)
+//   requires random_access_range<Base>;
+
+#include <ranges>
+
+#include <compare>
+
+#include "test_iterators.h"
+#include "../types.h"
+
+constexpr void compareOperatorTest(auto&& iter1, auto&& iter2) {
+  assert(!(iter1 < iter1));
+  assert(iter1 < iter2);
+  assert(!(iter2 < iter1));
+  assert(iter1 <= iter1);
+  assert(iter1 <= iter2);
+  assert(!(iter2 <= iter1));
+  assert(!(iter1 > iter1));
+  assert(!(iter1 > iter2));
+  assert(iter2 > iter1);
+  assert(iter1 >= iter1);
+  assert(!(iter1 >= iter2));
+  assert(iter2 >= iter1);
+  assert(iter1 == iter1);
+  assert(!(iter1 == iter2));
+  assert(iter2 == iter2);
+  assert(!(iter1 != iter1));
+  assert(iter1 != iter2);
+  assert(!(iter2 != iter2));
+}
+
+constexpr void inequalityOperatorsDoNotExistTest(auto&& iter1, auto&& iter2) {
+  using Iter1 = decltype(iter1);
+  using Iter2 = decltype(iter2);
+  static_assert(!std::is_invocable_v<std::less<>, Iter1, Iter2>);
+  static_assert(!std::is_invocable_v<std::less_equal<>, Iter1, Iter2>);
+  static_assert(!std::is_invocable_v<std::greater<>, Iter1, Iter2>);
+  static_assert(!std::is_invocable_v<std::greater_equal<>, Iter1, Iter2>);
+}
+
+constexpr bool test() {
+  {
+    // Test a new-school iterator with operator<=>; the iterator should also have operator<=>.
+    using It       = three_way_contiguous_iterator<int*>;
+    using SubRange = std::ranges::subrange<It>;
+    static_assert(std::three_way_comparable<It>);
+
+    int a[]    = {1, 2, 3, 4};
+    int b[]    = {5, 6, 7, 8, 9};
+    auto r     = std::views::zip_transform(MakeTuple{}, SubRange(It(a), It(a + 4)), SubRange(It(b), It(b + 5)));
+    auto iter1 = r.begin();
+    auto iter2 = iter1 + 1;
+    using Iter = decltype(iter1);
+    static_assert(std::three_way_comparable<Iter>);
+    compareOperatorTest(iter1, iter2);
+
+    assert((iter1 <=> iter2) == std::strong_ordering::less);
+    assert((iter1 <=> iter1) == std::strong_ordering::equal);
+    assert((iter2 <=> iter1) == std::strong_ordering::greater);
+  }
+
+  {
+    // Test an old-school iterator with no operator<=>; the transform iterator shouldn't have
+    // operator<=> either.
+    using It       = random_access_iterator<int*>;
+    using Subrange = std::ranges::subrange<It>;
+    static_assert(!std::three_way_comparable<It>);
+
+    int a[]    = {1, 2, 3, 4};
+    int b[]    = {5, 6, 7, 8, 9};
+    auto r     = std::views::zip_transform(MakeTuple{}, Subrange(It(a), It(a + 4)), Subrange(It(b), It(b + 5)));
+    auto iter1 = r.begin();
+    using Iter = decltype(iter1);
+#ifndef _LIBCPP_VERSION
+    // libc++ hasn't implemented LWG-3692 "zip_transform_view::iterator's operator<=> is overconstrained"
+    auto iter2 = iter1 + 1;
+
+    compareOperatorTest(iter1, iter2);
+    static_assert(std::three_way_comparable<Iter>);
+    assert((iter1 <=> iter2) == std::strong_ordering::less);
+    assert((iter1 <=> iter1) == std::strong_ordering::equal);
+    assert((iter2 <=> iter1) == std::strong_ordering::greater);
+#endif
+  }
+
+  {
+    // non random_access_range
+    int buffer1[1] = {1};
+    int buffer2[2] = {1, 2};
+
+    std::ranges::zip_transform_view v{MakeTuple{}, InputCommonView(buffer1), InputCommonView(buffer2)};
+    using View = decltype(v);
+    static_assert(!std::ranges::forward_range<View>);
+    static_assert(std::ranges::input_range<View>);
+    static_assert(std::ranges::common_range<View>);
+
+    auto it1 = v.begin();
+    auto it2 = v.end();
+    assert(it1 != it2);
+
+    ++it1;
+    assert(it1 == it2);
+
+    inequalityOperatorsDoNotExistTest(it1, it2);
+  }
+
+  {
+    // in this case sentinel is computed by getting each of the underlying sentinel, so only one
+    // underlying iterator is comparing equal
+    int buffer1[1] = {1};
+    int buffer2[2] = {1, 2};
+    std::ranges::zip_transform_view v{MakeTuple{}, ForwardSizedView(buffer1), ForwardSizedView(buffer2)};
+    using View = decltype(v);
+    static_assert(std::ranges::common_range<View>);
+    static_assert(!std::ranges::bidirectional_range<View>);
+
+    auto it1 = v.begin();
+    auto it2 = v.end();
+    assert(it1 != it2);
+
+    ++it1;
+    // it1:  <buffer1 + 1, buffer2 + 1>
+    // it2:  <buffer1 + 1, buffer2 + 2>
+    assert(it1 == it2);
+
+    inequalityOperatorsDoNotExistTest(it1, it2);
+  }
+
+  {
+    // underlying iterator does not support ==
+    using IterNoEqualView = BasicView<cpp20_input_iterator<int*>, sentinel_wrapper<cpp20_input_iterator<int*>>>;
+    int buffer[]          = {1};
+    std::ranges::zip_transform_view r(MakeTuple{}, IterNoEqualView{buffer});
+    auto it    = r.begin();
+    using Iter = decltype(it);
+    static_assert(!std::invocable<std::equal_to<>, Iter, Iter>);
+    inequalityOperatorsDoNotExistTest(it, it);
+  }
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  //static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/ctor.default.pass.cpp
new file mode 100644
index 00000000000000..4e16d839204cd5
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/ctor.default.pass.cpp
@@ -0,0 +1,50 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// iterator() = default;
+
+#include <ranges>
+
+#include "../types.h"
+
+struct IterDefaultCtrView : std::ranges::view_base {
+  int* begin() const;
+  int* end() const;
+};
+
+struct IterNoDefaultCtrView : std::ranges::view_base {
+  cpp20_input_iterator<int*> begin() const;
+  sentinel_wrapper<cpp20_input_iterator<int*>> end() const;
+};
+
+template <class... Views>
+using Iter = std::ranges::iterator_t<std::ranges::zip_transform_view<MakeTuple, Views...>>;
+
+static_assert(!std::default_initializable<Iter<IterNoDefaultCtrView>>);
+static_assert(!std::default_initializable<Iter<IterNoDefaultCtrView, IterDefaultCtrView>>);
+static_assert(!std::default_initializable<Iter<IterNoDefaultCtrView, IterNoDefaultCtrView>>);
+static_assert(std::default_initializable<Iter<IterDefaultCtrView>>);
+static_assert(std::default_initializable<Iter<IterDefaultCtrView, IterDefaultCtrView>>);
+
+constexpr bool test() {
+  using ZipTransformIter = std::ranges::iterator_t<std::ranges::zip_transform_view<MakeTuple, IterDefaultCtrView>>;
+  ZipTransformIter iter1 = {};
+  ZipTransformIter iter2;
+  assert(iter1 == iter2);
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/ctor.other.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/ctor.other.pass.cpp
new file mode 100644
index 00000000000000..ce7b9eb1d486cc
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/ctor.other.pass.cpp
@@ -0,0 +1,63 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// constexpr iterator(iterator<!Const> i)
+//     requires Const && convertible_to<ziperator<false>, ziperator<Const>>;
+
+#include <ranges>
+
+#include <cassert>
+
+#include "../types.h"
+
+using ConstIterIncompatibleView =
+    BasicView<forward_iterator<int*>,
+              forward_iterator<int*>,
+              random_access_iterator<const int*>,
+              random_access_iterator<const int*>>;
+static_assert(!std::convertible_to<std::ranges::iterator_t<ConstIterIncompatibleView>,
+                                   std::ranges::iterator_t<const ConstIterIncompatibleView>>);
+
+constexpr bool test() {
+  int buffer[3] = {1, 2, 3};
+
+  {
+    std::ranges::zip_transform_view v(MakeTuple{}, NonSimpleCommon{buffer});
+    auto iter1                                       = v.begin();
+    std::ranges::iterator_t<const decltype(v)> iter2 = iter1;
+    assert(iter1 == iter2);
+
+    static_assert(!std::is_same_v<decltype(iter1), decltype(iter2)>);
+
+    // We cannot create a non-const iterator from a const iterator.
+    static_assert(!std::constructible_from<decltype(iter1), decltype(iter2)>);
+  }
+
+  {
+    // underlying non-const to const not convertible
+    std::ranges::zip_transform_view v(MakeTuple{}, ConstIterIncompatibleView{buffer});
+    auto iter1 = v.begin();
+    auto iter2 = std::as_const(v).begin();
+
+    static_assert(!std::is_same_v<decltype(iter1), decltype(iter2)>);
+
+    static_assert(!std::constructible_from<decltype(iter1), decltype(iter2)>);
+    static_assert(!std::constructible_from<decltype(iter2), decltype(iter1)>);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/decrement.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/decrement.pass.cpp
new file mode 100644
index 00000000000000..15d5e7d8c92832
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/decrement.pass.cpp
@@ -0,0 +1,84 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// constexpr iterator& operator--() requires bidirectional_range<Base>;
+// constexpr iterator operator--(int) requires bidirectional_range<Base>;
+
+#include <array>
+#include <cassert>
+#include <ranges>
+
+#include "../types.h"
+
+template <class Iter>
+concept canDecrement = requires(Iter it) { --it; } || requires(Iter it) { it--; };
+
+constexpr bool test() {
+  std::array a{1, 2, 3, 4};
+  std::array b{4.1, 3.2, 4.3};
+  {
+    // all random access
+    std::ranges::zip_transform_view v(MakeTuple{}, a, b, std::views::iota(0, 5));
+    auto it    = v.end();
+    using Iter = decltype(it);
+    static_assert(canDecrement<Iter>);
+
+    std::same_as<Iter&> decltype(auto) it_ref = --it;
+    assert(&it_ref == &it);
+
+    assert(*it == std::tuple(3, 4.3, 2));
+
+    auto original                         = it;
+    std::same_as<Iter> decltype(auto) it2 = it--;
+    assert(original == it2);
+    assert(*it == std::tuple(2, 3.2, 1));
+  }
+
+  {
+    // all bidi+
+    int buffer[2] = {1, 2};
+
+    std::ranges::zip_transform_view v(MakeTuple{}, BidiCommonView{buffer}, std::views::iota(0, 5));
+    auto it    = v.begin();
+    using Iter = decltype(it);
+    static_assert(canDecrement<Iter>);
+
+    ++it;
+    ++it;
+
+    std::same_as<Iter&> decltype(auto) it_ref = --it;
+    assert(&it_ref == &it);
+
+    assert(it == ++v.begin());
+    assert(*it == std::tuple(2, 1));
+
+    auto original                         = it;
+    std::same_as<Iter> decltype(auto) it2 = it--;
+    assert(original == it2);
+    assert(*it == std::tuple(1, 0));
+  }
+
+  {
+    // non bidi
+    int buffer[3] = {4, 5, 6};
+    std::ranges::zip_transform_view v(MakeTuple{}, a, InputCommonView{buffer});
+    using Iter = std::ranges::iterator_t<decltype(v)>;
+    static_assert(!canDecrement<Iter>);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/deref.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/deref.pass.cpp
new file mode 100644
index 00000000000000..de8dde76982a6a
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/deref.pass.cpp
@@ -0,0 +1,97 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// constexpr decltype(auto) operator*() const noexcept(see below);
+
+#include <array>
+#include <cassert>
+#include <ranges>
+
+#include "../types.h"
+
+// Test noexcept
+// Remarks: Let Is be the pack 0, 1, …, (sizeof...(Views)-1). The exception specification is equivalent to:
+//   noexcept(invoke(*parent_->fun_, *std::get<Is>(inner_.current_)...)).
+
+template <class ZipTransformView>
+concept DerefNoexcept = requires(std::ranges::iterator_t<ZipTransformView> iter) { requires noexcept(*iter); };
+
+struct ThrowingDerefIter {
+  using iterator_category = std::forward_iterator_tag;
+  using value_type        = int;
+  using difference_type   = std::intptr_t;
+
+  int operator*() const noexcept(false);
+
+  ThrowingDerefIter& operator++();
+  void operator++(int);
+
+  friend constexpr bool operator==(const ThrowingDerefIter&, const ThrowingDerefIter&) = default;
+};
+
+using NoexceptDerefIter = int*;
+
+template <bool NoExceptDeref>
+struct TestView : std::ranges::view_base {
+  using Iter = std::conditional_t<NoExceptDeref, NoexceptDerefIter, ThrowingDerefIter>;
+  Iter begin() const;
+  Iter end() const;
+};
+
+template <bool NoExceptCall>
+struct TestFn {
+  int operator()(auto&&...) const noexcept(NoExceptCall);
+};
+
+static_assert(DerefNoexcept<std::ranges::zip_transform_view<TestFn<true>, TestView<true>>>);
+static_assert(DerefNoexcept<std::ranges::zip_transform_view<TestFn<true>, TestView<true>, TestView<true>>>);
+static_assert(!DerefNoexcept<std::ranges::zip_transform_view<TestFn<true>, TestView<false>>>);
+static_assert(!DerefNoexcept<std::ranges::zip_transform_view<TestFn<false>, TestView<true>>>);
+static_assert(!DerefNoexcept<std::ranges::zip_transform_view<TestFn<false>, TestView<false>>>);
+static_assert(!DerefNoexcept<std::ranges::zip_transform_view<TestFn<false>, TestView<false>, TestView<true>>>);
+static_assert(!DerefNoexcept<std::ranges::zip_transform_view<TestFn<true>, TestView<false>, TestView<true>>>);
+static_assert(!DerefNoexcept<std::ranges::zip_transform_view<TestFn<false>, TestView<false>, TestView<false>>>);
+
+constexpr bool test() {
+  std::array a{1, 2, 3, 4};
+  std::array b{4.1, 3.2, 4.3};
+  {
+    // Function returns reference
+    std::ranges::zip_transform_view v(GetFirst{}, a);
+    auto it                               = v.begin();
+    std::same_as<int&> decltype(auto) val = *it;
+    assert(&val == &a[0]);
+  }
+
+  {
+    // function returns PRValue
+    std::ranges::zip_transform_view v(MakeTuple{}, a, b);
+    auto it                                                  = v.begin();
+    std::same_as<std::tuple<int, double>> decltype(auto) val = *it;
+    assert(val == std::tuple(1, 4.1));
+  }
+
+  {
+    // operator* is const
+    std::ranges::zip_transform_view v(GetFirst{}, a);
+    const auto it                         = v.begin();
+    std::same_as<int&> decltype(auto) val = *it;
+    assert(&val == &a[0]);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/increment.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/increment.pass.cpp
new file mode 100644
index 00000000000000..f4ba3a157d6dc3
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/increment.pass.cpp
@@ -0,0 +1,80 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// constexpr iterator& operator++();
+// constexpr void operator++(int);
+// constexpr iterator operator++(int) requires forward_range<Base>;;
+
+#include <array>
+#include <cassert>
+#include <ranges>
+
+#include "../types.h"
+
+struct InputRange : IntBufferView {
+  using IntBufferView::IntBufferView;
+  using iterator = cpp20_input_iterator<int*>;
+  constexpr iterator begin() const { return iterator(buffer_); }
+  constexpr sentinel_wrapper<iterator> end() const { return sentinel_wrapper<iterator>(iterator(buffer_ + size_)); }
+};
+
+template <class View>
+constexpr void testForwardPlus() {
+  int buffer[] = {1, 2, 3, 4};
+
+  std::ranges::zip_transform_view v(GetFirst{}, View{buffer}, View{buffer});
+  auto it    = v.begin();
+  using Iter = decltype(it);
+
+  assert(&(*it) == &(buffer[0]));
+
+  std::same_as<Iter&> decltype(auto) it_ref = ++it;
+  assert(&it_ref == &it);
+  assert(&(*it) == &(buffer[1]));
+
+  static_assert(std::is_same_v<decltype(it++), Iter>);
+  auto original                          = it;
+  std::same_as<Iter> decltype(auto) copy = it++;
+  assert(original == copy);
+  assert(&(*it) == &(buffer[2]));
+}
+
+constexpr bool test() {
+  testForwardPlus<SizedRandomAccessView>();
+  testForwardPlus<BidiCommonView>();
+  testForwardPlus<ForwardSizedView>();
+
+  {
+    // test input_range
+    int buffer[3] = {4, 5, 6};
+    std::ranges::zip_transform_view v(MakeTuple{}, InputRange{buffer}, InputRange{buffer});
+    auto it    = v.begin();
+    using Iter = decltype(it);
+
+    assert(*it == std::tuple(4, 4));
+
+    std::same_as<Iter&> decltype(auto) it_ref = ++it;
+    assert(&it_ref == &it);
+    assert(*it == std::tuple(5, 5));
+
+    static_assert(std::is_same_v<decltype(it++), void>);
+    it++;
+    assert(*it == std::tuple(6, 6));
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/member_types.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/member_types.compile.pass.cpp
new file mode 100644
index 00000000000000..8976d7e0ded2a5
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/member_types.compile.pass.cpp
@@ -0,0 +1,158 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// Iterator traits and member typedefs in zip_transform_view::iterator.
+
+#include <array>
+#include <ranges>
+
+#include "test_iterators.h"
+
+#include "../types.h"
+
+template <class T>
+concept HasIterCategory = requires { typename T::iterator_category; };
+
+template <class T>
+struct DiffTypeIter {
+  using iterator_category = std::input_iterator_tag;
+  using value_type        = int;
+  using difference_type   = T;
+
+  int operator*() const;
+  DiffTypeIter& operator++();
+  void operator++(int);
+  friend constexpr bool operator==(DiffTypeIter, DiffTypeIter) = default;
+};
+
+template <class T>
+struct DiffTypeRange {
+  DiffTypeIter<T> begin() const;
+  DiffTypeIter<T> end() const;
+};
+
+struct Foo {};
+struct Bar {};
+
+void test() {
+  int buffer[] = {1, 2, 3, 4};
+  {
+    // C++20 random_access C++17 random_access
+    std::ranges::zip_transform_view v(GetFirst{}, buffer);
+    using Iter = decltype(v.begin());
+
+    static_assert(std::is_same_v<Iter::iterator_concept, std::random_access_iterator_tag>);
+    static_assert(std::is_same_v<Iter::iterator_category, std::random_access_iterator_tag>);
+    static_assert(std::is_same_v<Iter::difference_type, std::ptrdiff_t>);
+    static_assert(std::is_same_v<Iter::value_type, int>);
+    static_assert(HasIterCategory<Iter>);
+  }
+
+  {
+    // C++20 random_access C++17 input
+    std::ranges::zip_transform_view v(MakeTuple{}, buffer);
+    using Iter = decltype(v.begin());
+
+    static_assert(std::is_same_v<Iter::iterator_concept, std::random_access_iterator_tag>);
+    static_assert(std::is_same_v<Iter::iterator_category, std::input_iterator_tag>);
+    static_assert(std::is_same_v<Iter::difference_type, std::ptrdiff_t>);
+    static_assert(std::is_same_v<Iter::value_type, std::tuple<int>>);
+    static_assert(HasIterCategory<Iter>);
+  }
+
+  {
+    // C++20 bidirectional C++17 bidirectional
+    std::ranges::zip_transform_view v(GetFirst{}, BidiCommonView{buffer});
+    using Iter = decltype(v.begin());
+
+    static_assert(std::is_same_v<Iter::iterator_concept, std::bidirectional_iterator_tag>);
+    static_assert(std::is_same_v<Iter::iterator_category, std::bidirectional_iterator_tag>);
+    static_assert(std::is_same_v<Iter::difference_type, std::ptrdiff_t>);
+    static_assert(std::is_same_v<Iter::value_type, int>);
+    static_assert(HasIterCategory<Iter>);
+  }
+
+  {
+    // C++20 bidirectional C++17 input
+    std::ranges::zip_transform_view v(MakeTuple{}, BidiCommonView{buffer});
+    using Iter = decltype(v.begin());
+
+    static_assert(std::is_same_v<Iter::iterator_concept, std::bidirectional_iterator_tag>);
+    static_assert(std::is_same_v<Iter::iterator_category, std::input_iterator_tag>);
+    static_assert(std::is_same_v<Iter::difference_type, std::ptrdiff_t>);
+    static_assert(std::is_same_v<Iter::value_type, std::tuple<int>>);
+    static_assert(HasIterCategory<Iter>);
+  }
+
+  {
+    // C++20 forward C++17 bidirectional
+    std::ranges::zip_transform_view v(GetFirst{}, ForwardSizedView{buffer});
+    using Iter = decltype(v.begin());
+
+    static_assert(std::is_same_v<Iter::iterator_concept, std::forward_iterator_tag>);
+    static_assert(std::is_same_v<Iter::iterator_category, std::forward_iterator_tag>);
+    static_assert(std::is_same_v<Iter::difference_type, std::ptrdiff_t>);
+    static_assert(std::is_same_v<Iter::value_type, int>);
+    static_assert(HasIterCategory<Iter>);
+  }
+
+  {
+    // C++20 forward C++17 input
+    std::ranges::zip_transform_view v(MakeTuple{}, ForwardSizedView{buffer});
+    using Iter = decltype(v.begin());
+
+    static_assert(std::is_same_v<Iter::iterator_concept, std::forward_iterator_tag>);
+    static_assert(std::is_same_v<Iter::iterator_category, std::input_iterator_tag>);
+    static_assert(std::is_same_v<Iter::difference_type, std::ptrdiff_t>);
+    static_assert(std::is_same_v<Iter::value_type, std::tuple<int>>);
+    static_assert(HasIterCategory<Iter>);
+  }
+
+  {
+    // C++20 input C++17 not a range
+    std::ranges::zip_transform_view v(GetFirst{}, InputCommonView{buffer});
+    using Iter = decltype(v.begin());
+
+    static_assert(std::is_same_v<Iter::iterator_concept, std::input_iterator_tag>);
+    static_assert(std::is_same_v<Iter::difference_type, std::ptrdiff_t>);
+    static_assert(std::is_same_v<Iter::value_type, int>);
+    static_assert(!HasIterCategory<Iter>);
+  }
+
+  {
+    // difference_type of one view
+    std::ranges::zip_transform_view v{MakeTuple{}, DiffTypeRange<std::intptr_t>{}};
+    using Iter = decltype(v.begin());
+    static_assert(std::is_same_v<Iter::difference_type, std::intptr_t>);
+  }
+
+  {
+    // difference_type of multiple views should be the common type
+    std::ranges::zip_transform_view v{MakeTuple{}, DiffTypeRange<std::intptr_t>{}, DiffTypeRange<std::ptrdiff_t>{}};
+    using Iter = decltype(v.begin());
+    static_assert(std::is_same_v<Iter::difference_type, std::common_type_t<std::intptr_t, std::ptrdiff_t>>);
+  }
+
+  const std::array foos{Foo{}};
+  std::array bars{Bar{}, Bar{}};
+  {
+    // value_type of one view
+    std::ranges::zip_transform_view v{MakeTuple{}, foos};
+    using Iter = decltype(v.begin());
+    static_assert(std::is_same_v<Iter::value_type, std::tuple<Foo>>);
+  }
+
+  {
+    // value_type of multiple views with different value_type
+    std::ranges::zip_transform_view v{MakeTuple{}, foos, bars};
+    using Iter = decltype(v.begin());
+    static_assert(std::is_same_v<Iter::value_type, std::tuple<Foo, Bar>>);
+  }
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/subscript.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/subscript.pass.cpp
new file mode 100644
index 00000000000000..b624177b400243
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/iterator/subscript.pass.cpp
@@ -0,0 +1,68 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+//  constexpr decltype(auto) operator[](difference_type n) const
+//    requires random_access_range<Base>;
+
+#include <ranges>
+#include <cassert>
+
+#include "../types.h"
+
+template <class T>
+concept CanSubscript = requires(T t) { t[0]; };
+
+constexpr bool test() {
+  int buffer[8] = {1, 2, 3, 4, 5, 6, 7, 8};
+
+  {
+    // F returns PR value
+    std::ranges::zip_transform_view v(MakeTuple{}, SizedRandomAccessView{buffer}, std::views::iota(0));
+    auto it    = v.begin();
+    using Iter = decltype(it);
+    static_assert(CanSubscript<Iter>);
+
+    std::same_as<std::tuple<int, int>> decltype(auto) val = it[0];
+    assert(val == *it);
+    assert(it[2] == *(it + 2));
+    assert(it[4] == *(it + 4));
+  }
+
+  {
+    // F return by reference
+    std::ranges::zip_transform_view v(GetFirst{}, ContiguousCommonView{buffer}, ContiguousCommonView{buffer});
+    auto it    = v.begin();
+    using Iter = decltype(it);
+    static_assert(CanSubscript<Iter>);
+
+    std::same_as<int&> decltype(auto) val = it[0];
+    assert(&val == &buffer[0]);
+    assert(val == *it);
+    assert(it[2] == *(it + 2));
+    assert(it[4] == *(it + 4));
+  }
+
+  {
+    // non random_access_range
+    std::ranges::zip_transform_view v(GetFirst{}, BidiCommonView{buffer});
+    auto it    = v.begin();
+    using Iter = decltype(it);
+    static_assert(!CanSubscript<Iter>);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/sentinel/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/sentinel/ctor.default.pass.cpp
new file mode 100644
index 00000000000000..91c327a57ecf3d
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/sentinel/ctor.default.pass.cpp
@@ -0,0 +1,51 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// sentinel() = default;
+
+#include <cassert>
+#include <ranges>
+
+#include "../types.h"
+
+struct PODSentinel {
+  bool b; // deliberately uninitialised
+
+  friend constexpr bool operator==(int*, const PODSentinel& s) { return s.b; }
+};
+
+struct Range : std::ranges::view_base {
+  int* begin() const;
+  PODSentinel end();
+};
+
+constexpr bool test() {
+  {
+    using R        = std::ranges::zip_transform_view<Fn, Range>;
+    using Sentinel = std::ranges::sentinel_t<R>;
+    static_assert(!std::is_same_v<Sentinel, std::ranges::iterator_t<R>>);
+
+    std::ranges::iterator_t<R> it;
+
+    Sentinel s1;
+    assert(it != s1); // PODSentinel.b is initialised to false
+
+    Sentinel s2 = {};
+    assert(it != s2); // PODSentinel.b is initialised to false
+  }
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/sentinel/ctor.other.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/sentinel/ctor.other.pass.cpp
new file mode 100644
index 00000000000000..943359c29fecf1
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/sentinel/ctor.other.pass.cpp
@@ -0,0 +1,76 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+//  constexpr sentinel(sentinel<!Const> i)
+//    requires Const && convertible_to<zentinel<false>, zentinel<Const>>;
+
+#include <cassert>
+#include <ranges>
+
+#include "../types.h"
+
+template <class T>
+struct convertible_sentinel_wrapper {
+  explicit convertible_sentinel_wrapper() = default;
+  constexpr convertible_sentinel_wrapper(const T& it) : it_(it) {}
+
+  template <class U>
+    requires std::convertible_to<const U&, T>
+  constexpr convertible_sentinel_wrapper(const convertible_sentinel_wrapper<U>& other) : it_(other.it_) {}
+
+  constexpr friend bool operator==(convertible_sentinel_wrapper const& self, const T& other) {
+    return self.it_ == other;
+  }
+  T it_;
+};
+
+struct NonSimpleNonCommonConvertibleView : IntBufferView {
+  using IntBufferView::IntBufferView;
+
+  constexpr int* begin() { return buffer_; }
+  constexpr const int* begin() const { return buffer_; }
+  constexpr convertible_sentinel_wrapper<int*> end() { return convertible_sentinel_wrapper<int*>(buffer_ + size_); }
+  constexpr convertible_sentinel_wrapper<const int*> end() const {
+    return convertible_sentinel_wrapper<const int*>(buffer_ + size_);
+  }
+};
+
+// convertible_to<zentinel<false>, zentinel<Const>>
+static_assert(std::convertible_to< //
+              std::ranges::sentinel_t<std::ranges::zip_view<NonSimpleNonCommonConvertibleView>>,
+              std::ranges::sentinel_t<std::ranges::zip_view<NonSimpleNonCommonConvertibleView> const>>);
+
+constexpr bool test() {
+  int buffer1[4] = {1, 2, 3, 4};
+  int buffer2[5] = {1, 2, 3, 4, 5};
+  std::ranges::zip_transform_view v{
+      MakeTuple{}, NonSimpleNonCommonConvertibleView(buffer1), NonSimpleNonCommonConvertibleView(buffer2)};
+  using ZipTransformView = decltype(v);
+  static_assert(!std::ranges::common_range<ZipTransformView>);
+  auto sent1                                            = v.end();
+  std::ranges::sentinel_t<const ZipTransformView> sent2 = sent1;
+  static_assert(!std::is_same_v<decltype(sent1), decltype(sent2)>);
+
+  assert(v.begin() != sent2);
+  assert(std::as_const(v).begin() != sent2);
+  assert(v.begin() + 4 == sent2);
+  assert(std::as_const(v).begin() + 4 == sent2);
+
+  // Cannot create a non-const iterator from a const iterator.
+  static_assert(!std::constructible_from<decltype(sent1), decltype(sent2)>);
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/sentinel/eq.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/sentinel/eq.pass.cpp
new file mode 100644
index 00000000000000..88f1a7d1089d03
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/sentinel/eq.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
+
+// template<bool OtherConst>
+//   requires sentinel_for<zentinel<Const>, ziperator<OtherConst>>
+// friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y);
+
+#include <cassert>
+#include <compare>
+#include <ranges>
+
+#include "../types.h"
+
+using Iterator      = random_access_iterator<int*>;
+using ConstIterator = random_access_iterator<const int*>;
+
+template <bool Const>
+struct ComparableSentinel {
+  using Iter = std::conditional_t<Const, ConstIterator, Iterator>;
+  Iter iter_;
+
+  explicit ComparableSentinel() = default;
+  constexpr explicit ComparableSentinel(const Iter& it) : iter_(it) {}
+
+  constexpr friend bool operator==(const Iterator& i, const ComparableSentinel& s) { return base(i) == base(s.iter_); }
+
+  constexpr friend bool operator==(const ConstIterator& i, const ComparableSentinel& s) {
+    return base(i) == base(s.iter_);
+  }
+};
+
+struct ComparableView : IntBufferView {
+  using IntBufferView::IntBufferView;
+
+  constexpr auto begin() { return Iterator(buffer_); }
+  constexpr auto begin() const { return ConstIterator(buffer_); }
+  constexpr auto end() { return ComparableSentinel<false>(Iterator(buffer_ + size_)); }
+  constexpr auto end() const { return ComparableSentinel<true>(ConstIterator(buffer_ + size_)); }
+};
+
+struct ConstIncompatibleView : std::ranges::view_base {
+  cpp17_input_iterator<int*> begin();
+  forward_iterator<const int*> begin() const;
+  sentinel_wrapper<cpp17_input_iterator<int*>> end();
+  sentinel_wrapper<forward_iterator<const int*>> end() const;
+};
+
+template <class Iter, class Sent>
+concept EqualComparable = std::invocable<std::equal_to<>, const Iter&, const Sent&>;
+
+constexpr bool test() {
+  int buffer1[4] = {1, 2, 3, 4};
+  int buffer2[5] = {1, 2, 3, 4, 5};
+  int buffer3[8] = {1, 2, 3, 4, 5, 6, 7, 8};
+  {
+    // const and non-const have different iterator/sentinel types
+    std::ranges::zip_transform_view v{
+        MakeTuple{}, NonSimpleNonCommon(buffer1), SimpleNonCommon(buffer2), SimpleNonCommon(buffer3)};
+    using ZipTransformView = decltype(v);
+    static_assert(!std::ranges::common_range<ZipTransformView>);
+    static_assert(!simple_view<ZipTransformView>);
+
+    assert(v.begin() != v.end());
+    assert(v.begin() + 4 == v.end());
+
+    // const_iterator (const int*) converted to iterator (int*)
+    assert(v.begin() + 4 == std::as_const(v).end());
+
+    using Iter      = std::ranges::iterator_t<decltype(v)>;
+    using ConstIter = std::ranges::iterator_t<const decltype(v)>;
+    static_assert(!std::is_same_v<Iter, ConstIter>);
+    using Sentinel      = std::ranges::sentinel_t<decltype(v)>;
+    using ConstSentinel = std::ranges::sentinel_t<const decltype(v)>;
+    static_assert(!std::is_same_v<Sentinel, ConstSentinel>);
+
+    static_assert(EqualComparable<Iter, Sentinel>);
+    static_assert(!EqualComparable<ConstIter, Sentinel>);
+    static_assert(EqualComparable<Iter, ConstSentinel>);
+    static_assert(EqualComparable<ConstIter, ConstSentinel>);
+  }
+
+  {
+    // underlying const/non-const sentinel can be compared with both const/non-const iterator
+    std::ranges::zip_transform_view v{MakeTuple{}, ComparableView(buffer1), ComparableView(buffer2)};
+    using ZipTransformView = decltype(v);
+    static_assert(!std::ranges::common_range<ZipTransformView>);
+    static_assert(!simple_view<ZipTransformView>);
+
+    assert(v.begin() != v.end());
+    assert(v.begin() + 4 == v.end());
+    assert(std::as_const(v).begin() + 4 == v.end());
+    assert(std::as_const(v).begin() + 4 == std::as_const(v).end());
+    assert(v.begin() + 4 == std::as_const(v).end());
+
+    using Iter      = std::ranges::iterator_t<decltype(v)>;
+    using ConstIter = std::ranges::iterator_t<const decltype(v)>;
+    static_assert(!std::is_same_v<Iter, ConstIter>);
+    using Sentinel      = std::ranges::sentinel_t<decltype(v)>;
+    using ConstSentinel = std::ranges::sentinel_t<const decltype(v)>;
+    static_assert(!std::is_same_v<Sentinel, ConstSentinel>);
+
+    static_assert(EqualComparable<Iter, Sentinel>);
+    static_assert(EqualComparable<ConstIter, Sentinel>);
+    static_assert(EqualComparable<Iter, ConstSentinel>);
+    static_assert(EqualComparable<ConstIter, ConstSentinel>);
+  }
+
+  {
+    // underlying const/non-const sentinel cannot be compared with non-const/const iterator
+    std::ranges::zip_transform_view v{MakeTuple{}, ComparableView(buffer1), ConstIncompatibleView{}};
+    using ZipTransformView = decltype(v);
+    static_assert(!std::ranges::common_range<ZipTransformView>);
+    static_assert(!simple_view<ZipTransformView>);
+
+    using Iter      = std::ranges::iterator_t<decltype(v)>;
+    using ConstIter = std::ranges::iterator_t<const decltype(v)>;
+    static_assert(!std::is_same_v<Iter, ConstIter>);
+    using Sentinel      = std::ranges::sentinel_t<decltype(v)>;
+    using ConstSentinel = std::ranges::sentinel_t<const decltype(v)>;
+    static_assert(!std::is_same_v<Sentinel, ConstSentinel>);
+
+    static_assert(EqualComparable<Iter, Sentinel>);
+    static_assert(!EqualComparable<ConstIter, Sentinel>);
+    static_assert(!EqualComparable<Iter, ConstSentinel>);
+    static_assert(EqualComparable<ConstIter, ConstSentinel>);
+  }
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/sentinel/minus.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/sentinel/minus.pass.cpp
new file mode 100644
index 00000000000000..58c35e22db46ac
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/sentinel/minus.pass.cpp
@@ -0,0 +1,209 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// template<bool OtherConst>
+//   requires sized_sentinel_for<zentinel<Const>, ziperator<OtherConst>>
+// friend constexpr range_difference_t<maybe-const<OtherConst, InnerView>>
+//   operator-(const sentinel& x, const iterator<OtherConst>& y);
+
+#include <cassert>
+#include <concepts>
+#include <functional>
+#include <ranges>
+#include <tuple>
+
+#include "../types.h"
+
+template <class Base = int*>
+struct convertible_forward_sized_iterator {
+  Base it_ = nullptr;
+
+  using iterator_category = std::forward_iterator_tag;
+  using value_type        = int;
+  using difference_type   = std::intptr_t;
+
+  convertible_forward_sized_iterator() = default;
+  constexpr convertible_forward_sized_iterator(Base it) : it_(it) {}
+
+  template <std::convertible_to<Base> U>
+  constexpr convertible_forward_sized_iterator(const convertible_forward_sized_iterator<U>& it) : it_(it.it_) {}
+
+  constexpr decltype(*Base{}) operator*() const { return *it_; }
+
+  constexpr convertible_forward_sized_iterator& operator++() {
+    ++it_;
+    return *this;
+  }
+  constexpr convertible_forward_sized_iterator operator++(int) { return forward_sized_iterator(it_++); }
+
+  friend constexpr bool
+  operator==(const convertible_forward_sized_iterator&, const convertible_forward_sized_iterator&) = default;
+
+  friend constexpr difference_type
+  operator-(const convertible_forward_sized_iterator& x, const convertible_forward_sized_iterator& y) {
+    return x.it_ - y.it_;
+  }
+};
+static_assert(std::forward_iterator<convertible_forward_sized_iterator<>>);
+
+template <class Base>
+struct convertible_sized_sentinel {
+  Base base_;
+  explicit convertible_sized_sentinel() = default;
+  constexpr convertible_sized_sentinel(const Base& it) : base_(it) {}
+
+  template <std::convertible_to<Base> U>
+  constexpr convertible_sized_sentinel(const convertible_sized_sentinel<U>& other) : base_(other.base_) {}
+
+  template <class U>
+    requires(std::convertible_to<Base, U> || std::convertible_to<U, Base>)
+  friend constexpr bool operator==(const convertible_sized_sentinel& s, const U& base) {
+    return s.base_ == base;
+  }
+  template <class U>
+    requires(std::convertible_to<Base, U> || std::convertible_to<U, Base>)
+  friend constexpr auto operator-(const convertible_sized_sentinel& s, const U& i) {
+    return s.base_ - i;
+  }
+
+  template <class U>
+    requires(std::convertible_to<Base, U> || std::convertible_to<U, Base>)
+  friend constexpr auto operator-(const U& i, const convertible_sized_sentinel& s) {
+    return i - s.base_;
+  }
+};
+static_assert(std::sized_sentinel_for<convertible_sized_sentinel<convertible_forward_sized_iterator<>>,
+                                      convertible_forward_sized_iterator<>>);
+static_assert(std::sized_sentinel_for<convertible_sized_sentinel<convertible_forward_sized_iterator<const int*>>,
+                                      convertible_forward_sized_iterator<int*>>);
+static_assert(std::sized_sentinel_for<convertible_sized_sentinel<convertible_forward_sized_iterator<int*>>,
+                                      convertible_forward_sized_iterator<const int*>>);
+
+struct ConstCompatibleForwardSized : IntBufferView {
+  using IntBufferView::IntBufferView;
+
+  using iterator       = convertible_forward_sized_iterator<int*>;
+  using const_iterator = convertible_forward_sized_iterator<const int*>;
+
+  constexpr iterator begin() { return {buffer_}; }
+  constexpr const_iterator begin() const { return {buffer_}; }
+  constexpr convertible_sized_sentinel<iterator> end() { return iterator{buffer_ + size_}; }
+  constexpr convertible_sized_sentinel<const_iterator> end() const { return const_iterator{buffer_ + size_}; }
+};
+
+template <class T, class U>
+concept HasMinus = std::invocable<std::minus<>, const T&, const U&>;
+
+template <class T>
+concept SentinelHasMinus = HasMinus<std::ranges::sentinel_t<T>, std::ranges::iterator_t<T>>;
+
+constexpr bool test() {
+  int buffer1[5] = {1, 2, 3, 4, 5};
+
+  {
+    // shortest range
+    std::ranges::zip_transform_view v(MakeTuple{}, std::views::iota(0, 3), ForwardSizedNonCommon(buffer1));
+    static_assert(!std::ranges::common_range<decltype(v)>);
+    auto it = v.begin();
+    auto st = v.end();
+    assert(st - it == 3);
+    assert(st - std::ranges::next(it, 1) == 2);
+
+    assert(it - st == -3);
+    assert(std::ranges::next(it, 1) - st == -2);
+    static_assert(SentinelHasMinus<decltype(v)>);
+  }
+
+  {
+    // underlying sentinel does not model sized_sentinel_for
+    std::ranges::zip_transform_view v(MakeTuple{}, std::views::iota(0), SizedRandomAccessView(buffer1));
+    static_assert(!std::ranges::common_range<decltype(v)>);
+    static_assert(!SentinelHasMinus<decltype(v)>);
+  }
+
+  {
+    // const incompatible:
+    // underlying const sentinels cannot subtract underlying iterators
+    // underlying sentinels cannot subtract underlying const iterators
+    std::ranges::zip_transform_view v(MakeTuple{}, NonSimpleForwardSizedNonCommon{buffer1});
+    static_assert(!std::ranges::common_range<decltype(v)>);
+    static_assert(!simple_view<decltype(v)>);
+
+    using Iter      = std::ranges::iterator_t<decltype(v)>;
+    using ConstIter = std::ranges::iterator_t<const decltype(v)>;
+    static_assert(!std::is_same_v<Iter, ConstIter>);
+    using Sentinel      = std::ranges::sentinel_t<decltype(v)>;
+    using ConstSentinel = std::ranges::sentinel_t<const decltype(v)>;
+    static_assert(!std::is_same_v<Sentinel, ConstSentinel>);
+
+    static_assert(HasMinus<Iter, Sentinel>);
+    static_assert(HasMinus<Sentinel, Iter>);
+    static_assert(HasMinus<ConstIter, ConstSentinel>);
+    static_assert(HasMinus<ConstSentinel, ConstIter>);
+    auto it       = v.begin();
+    auto const_it = std::as_const(v).begin();
+    auto st       = v.end();
+    auto const_st = std::as_const(v).end();
+    assert(it - st == -5);
+    assert(st - it == 5);
+    assert(const_it - const_st == -5);
+    assert(const_st - const_it == 5);
+
+    static_assert(!HasMinus<Iter, ConstSentinel>);
+    static_assert(!HasMinus<ConstSentinel, Iter>);
+    static_assert(!HasMinus<ConstIter, Sentinel>);
+    static_assert(!HasMinus<Sentinel, ConstIter>);
+  }
+
+  {
+    // const compatible allow non-const to const conversion
+    std::ranges::zip_transform_view v(MakeTuple{}, ConstCompatibleForwardSized{buffer1});
+    static_assert(!std::ranges::common_range<decltype(v)>);
+    static_assert(!simple_view<decltype(v)>);
+
+    using Iter      = std::ranges::iterator_t<decltype(v)>;
+    using ConstIter = std::ranges::iterator_t<const decltype(v)>;
+    static_assert(!std::is_same_v<Iter, ConstIter>);
+    using Sentinel      = std::ranges::sentinel_t<decltype(v)>;
+    using ConstSentinel = std::ranges::sentinel_t<const decltype(v)>;
+    static_assert(!std::is_same_v<Sentinel, ConstSentinel>);
+
+    static_assert(HasMinus<Iter, Sentinel>);
+    static_assert(HasMinus<Sentinel, Iter>);
+    static_assert(HasMinus<ConstIter, ConstSentinel>);
+    static_assert(HasMinus<ConstSentinel, ConstIter>);
+    static_assert(HasMinus<Iter, ConstSentinel>);
+    static_assert(HasMinus<ConstSentinel, Iter>);
+    static_assert(HasMinus<ConstIter, Sentinel>);
+    static_assert(HasMinus<Sentinel, ConstIter>);
+
+    auto it       = v.begin();
+    auto const_it = std::as_const(v).begin();
+    auto st       = v.end();
+    auto const_st = std::as_const(v).end();
+
+    assert(it - st == -5);
+    assert(st - it == 5);
+    assert(const_it - const_st == -5);
+    assert(const_st - const_it == 5);
+    assert(it - const_st == -5);
+    assert(const_st - it == 5);
+    assert(const_it - st == -5);
+    assert(st - const_it == 5);
+  }
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/size.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/size.pass.cpp
index c973f7cc4fcbc3..bbe469f2e3e0d9 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/size.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/size.pass.cpp
@@ -14,8 +14,6 @@
 #include <ranges>
 
 #include <cassert>
-#include <tuple>
-#include <utility>
 
 #include "test_iterators.h"
 #include "types.h"
diff --git a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/types.h b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/types.h
index 02ce0131a78fc5..0184d78873b1e6 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.zip.transform/types.h
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip.transform/types.h
@@ -26,13 +26,17 @@ struct View : std::ranges::view_base {
 };
 
 struct Fn {
-  int operator()(auto&&...) const;
+  int operator()(auto&&...) const { return 5; }
 };
 
 struct MakeTuple {
   constexpr auto operator()(auto&&... args) const { return std::tuple(std::forward<decltype(args)>(args)...); }
 };
 
+struct GetFirst {
+  constexpr decltype(auto) operator()(auto&& first, auto&&...) const { return std::forward<decltype(first)>(first); }
+};
+
 struct NoConstBeginView : std::ranges::view_base {
   int* begin();
   int* end();
@@ -50,7 +54,6 @@ struct NonConstOnlyFn {
   int operator()(const int&) const = delete;
 };
 
-
 template <class T>
 struct BufferView : std::ranges::view_base {
   T* buffer_;



More information about the libcxx-commits mailing list