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

via libcxx-commits libcxx-commits at lists.llvm.org
Fri Jan 26 07:16:40 PST 2024


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

None

>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] [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 f141656eb131a26..1a8d6c3f9402680 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 699a382ff66b736..b6d66acd233796c 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 ed721d467e94f4c..15d4de2fbe6bccd 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 000000000000000..4cc6ef55e8181ef
--- /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 4898c0afc87a6e7..deb87d359c60256 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 194a74a1e07b145..0a3ba7ea49d3b10 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 660d533b2a7830a..2a692567261e4cb 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 000000000000000..7d15a819a381892
--- /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 000000000000000..f63a944bfde19cb
--- /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 000000000000000..ec3f734a0b05bdd
--- /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 000000000000000..c576e6612ececb3
--- /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 000000000000000..96d625ae7dc32e1
--- /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 000000000000000..99a2e6caaa4fb84
--- /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 000000000000000..820c4675b8d2db6
--- /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 000000000000000..3c35de27deb6920
--- /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 000000000000000..c973f7cc4fcbc32
--- /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 000000000000000..02ce0131a78fc5b
--- /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 3b35271d649c381..06186656f01222e 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;
 }



More information about the libcxx-commits mailing list