[libcxx-commits] [libcxx] [libc++][RFC] Rewrite CPOs with resolver functions (PR #209104)

Nikolas Klauser via libcxx-commits libcxx-commits at lists.llvm.org
Mon Jul 13 05:56:14 PDT 2026


https://github.com/philnik777 updated https://github.com/llvm/llvm-project/pull/209104

>From 798a8f4223b82de7d5a198c97ed09fe0b84e3a86 Mon Sep 17 00:00:00 2001
From: Nikolas Klauser <nikolasklauser at berlin.de>
Date: Mon, 13 Jul 2026 10:04:56 +0200
Subject: [PATCH] [libc++][RFC] Rewrite CPOs with resolver functions

---
 libcxx/include/CMakeLists.txt                 |   1 +
 libcxx/include/__ranges/access.h              | 179 ++++++++++--------
 libcxx/include/__ranges/data.h                |  64 +++++--
 libcxx/include/__utility/cpo.h                |  37 ++++
 libcxx/include/module.modulemap.in            |   1 +
 .../std/ranges/range.access/begin.verify.cpp  |   4 +-
 .../std/ranges/range.access/end.verify.cpp    |   4 +-
 7 files changed, 184 insertions(+), 106 deletions(-)
 create mode 100644 libcxx/include/__utility/cpo.h

diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index b40f586161e62..19a4b53dd760f 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -934,6 +934,7 @@ set(files
   __utility/cmp.h
   __utility/constant_wrapper.h
   __utility/convert_to_integral.h
+  __utility/cpo.h
   __utility/declval.h
   __utility/default_three_way_comparator.h
   __utility/element_count.h
diff --git a/libcxx/include/__ranges/access.h b/libcxx/include/__ranges/access.h
index 28555ef6ba0d9..29459abf88882 100644
--- a/libcxx/include/__ranges/access.h
+++ b/libcxx/include/__ranges/access.h
@@ -12,15 +12,17 @@
 
 #include <__concepts/class_or_enum.h>
 #include <__config>
-#include <__cstddef/size_t.h>
 #include <__iterator/concepts.h>
-#include <__iterator/readable_traits.h>
 #include <__ranges/enable_borrowed_range.h>
-#include <__type_traits/decay.h>
+#include <__type_traits/extent.h>
+#include <__type_traits/is_array.h>
 #include <__type_traits/is_reference.h>
+#include <__type_traits/remove_all_extents.h>
+#include <__type_traits/remove_cv.h>
 #include <__type_traits/remove_cvref.h>
 #include <__type_traits/remove_reference.h>
 #include <__utility/auto_cast.h>
+#include <__utility/cpo.h>
 #include <__utility/declval.h>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
@@ -40,50 +42,50 @@ concept __can_borrow = is_lvalue_reference_v<_Tp> || enable_borrowed_range<remov
 
 namespace ranges {
 namespace __begin {
-template <class _Tp>
-concept __member_begin = __can_borrow<_Tp> && requires(_Tp&& __t) {
-  { _LIBCPP_AUTO_CAST(__t.begin()) } -> input_or_output_iterator;
-};
-
 void begin() = delete;
 
-template <class _Tp>
-concept __unqualified_begin =
-    !__member_begin<_Tp> && __can_borrow<_Tp> && __class_or_enum<remove_cvref_t<_Tp>> && requires(_Tp&& __t) {
-      { _LIBCPP_AUTO_CAST(begin(__t)) } -> input_or_output_iterator;
-    };
-
-struct __fn {
-  template <class _Tp>
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp (&__t)[]) const noexcept
-    requires(sizeof(_Tp) >= 0) // Disallow incomplete element types.
-  {
-    return __t + 0;
+struct __fn : _CPO<[]<class _Ep> consteval noexcept {
+  // [range.access.begin]
+
+  // Given a subexpression E with type T, let t be an lvalue that denotes the reified object for E. Then:
+  using _Tp = remove_reference_t<_Ep>;
+
+  // If E is an rvalue and enable_borrowed_range<remove_cv_t<T>> is false, ranges​::​begin(E) is ill-formed.
+  if constexpr (is_rvalue_reference_v<_Ep> && !enable_borrowed_range<remove_cv_t<_Tp>>) {
+    return;
+
+    // Otherwise, if T is an array type ([dcl.array]) and remove_all_extents_t<T> is an incomplete type,
+    // ranges​::​begin(E) is ill-formed with no diagnostic required.
+  } else if constexpr (is_array_v<_Tp>) {
+    if constexpr (!requires { sizeof(remove_all_extents_t<_Tp>); })
+      return;
+    else
+      // Otherwise, if T is an array type, ranges​::​begin(E) is expression-equivalent to t + 0.
+      return [](_Ep __v) noexcept { return __v + 0; };
+
+    // Otherwise, if auto(t.begin()) is a valid expression whose type models input_or_output_iterator,
+    // ranges​::​begin(E) is expression-equivalent to auto(t.begin()).
+  } else if constexpr (requires(_Tp& __t) {
+                         { _LIBCPP_AUTO_CAST(__t.begin()) } -> input_or_output_iterator;
+                       }) {
+    return [](_Ep __v) noexcept(noexcept(_LIBCPP_AUTO_CAST(__v.begin()))) { return _LIBCPP_AUTO_CAST(__v.begin()); };
+
+    // Otherwise, if T is a class or enumeration type and auto(begin(t)) is a valid expression whose type models
+    // input_or_output_iterator where the meaning of begin is established as-if by performing argument-dependent lookup
+    // only ([basic.lookup.argdep]), then ranges​::​begin(E) is expression-equivalent to that expression.
+  } else if constexpr (__class_or_enum<_Tp>) {
+    if constexpr (requires(_Tp& __t) {
+                    { _LIBCPP_AUTO_CAST(begin(__t)) } -> input_or_output_iterator;
+                  })
+      return [](_Ep __v) noexcept(noexcept(_LIBCPP_AUTO_CAST(begin(__v)))) { return _LIBCPP_AUTO_CAST(begin(__v)); };
+    else
+      return;
+
+    // Otherwise, ranges​::​begin(E) is ill-formed.
+  } else {
+    return;
   }
-
-  template <class _Tp, size_t _Np>
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp (&__t)[_Np]) const noexcept
-    requires(sizeof(_Tp) >= 0) // Disallow incomplete element types.
-  {
-    return __t + 0;
-  }
-
-  template <class _Tp>
-    requires __member_begin<_Tp>
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp&& __t) const
-      noexcept(noexcept(_LIBCPP_AUTO_CAST(__t.begin()))) {
-    return _LIBCPP_AUTO_CAST(__t.begin());
-  }
-
-  template <class _Tp>
-    requires __unqualified_begin<_Tp>
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp&& __t) const
-      noexcept(noexcept(_LIBCPP_AUTO_CAST(begin(__t)))) {
-    return _LIBCPP_AUTO_CAST(begin(__t));
-  }
-
-  void operator()(auto&&) const = delete;
-};
+}> {};
 } // namespace __begin
 
 inline namespace __cpo {
@@ -102,45 +104,53 @@ using iterator_t = decltype(ranges::begin(std::declval<_Tp&>()));
 
 namespace ranges {
 namespace __end {
-template <class _Tp>
-concept __member_end = __can_borrow<_Tp> && requires(_Tp&& __t) {
-  typename iterator_t<_Tp>;
-  { _LIBCPP_AUTO_CAST(__t.end()) } -> sentinel_for<iterator_t<_Tp>>;
-};
-
-void end() = delete;
-
-template <class _Tp>
-concept __unqualified_end =
-    !__member_end<_Tp> && __can_borrow<_Tp> && __class_or_enum<remove_cvref_t<_Tp>> && requires(_Tp&& __t) {
-      typename iterator_t<_Tp>;
-      { _LIBCPP_AUTO_CAST(end(__t)) } -> sentinel_for<iterator_t<_Tp>>;
-    };
-
-struct __fn {
-  template <class _Tp, size_t _Np>
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp (&__t)[_Np]) const noexcept
-    requires(sizeof(_Tp) >= 0) // Disallow incomplete element types.
-  {
-    return __t + _Np;
-  }
-
-  template <class _Tp>
-    requires __member_end<_Tp>
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp&& __t) const
-      noexcept(noexcept(_LIBCPP_AUTO_CAST(__t.end()))) {
-    return _LIBCPP_AUTO_CAST(__t.end());
+struct __fn : _CPO<[]<class _Ep> consteval noexcept {
+  // [range.access.end]
+
+  // Given a subexpression E with type T, let t be an lvalue that denotes the reified object for E. Then:
+  using _Tp = remove_reference_t<_Ep>;
+
+  // If E is an rvalue and enable_borrowed_range<remove_cv_t<T>> is false, ranges​::​end(E) is ill-formed.
+  if constexpr (is_rvalue_reference_v<_Ep> && !enable_borrowed_range<remove_cv_t<_Tp>>) {
+    return;
+
+    // Otherwise, if T is an array type ([dcl.array]) and remove_all_extents_t<T> is an incomplete type,
+    // ranges​::​end(E) is ill-formed with no diagnostic required.
+  } else if constexpr (is_array_v<_Tp>) {
+    if constexpr (!requires { sizeof(remove_all_extents_t<_Tp>); }) {
+      return;
+
+      // Otherwise, if T is an array of unknown bound, ranges​::​end(E) is ill-formed.
+    } else if constexpr (is_unbounded_array_v<_Tp>) {
+      return;
+
+      // Otherwise, if T is an array, ranges​::​end(E) is expression-equivalent to t + extent_v<T>.
+    } else {
+      return [](_Ep __v) noexcept { return __v + extent_v<_Tp>; };
+    }
+
+    // Otherwise, if auto(t.end()) is a valid expression whose type models sentinel_for<iterator_t<T>> then
+    // ranges​::​end(E) is expression-equivalent to auto(t.end()).
+  } else if constexpr (requires(_Tp& __t) {
+                         { _LIBCPP_AUTO_CAST(__t.end()) } -> sentinel_for<iterator_t<_Tp>>;
+                       }) {
+    return [](_Ep __v) noexcept(noexcept(_LIBCPP_AUTO_CAST(__v.end()))) { return _LIBCPP_AUTO_CAST(__v.end()); };
+
+    // Otherwise, if T is a class or enumeration type and auto(end(t)) is a valid expression whose type models
+    // sentinel_for<iterator_t<T>> where the meaning of end is established as-if by performing argument-dependent lookup
+    // only ([basic.lookup.argdep]), then ranges​::​end(E) is expression-equivalent to that expression.
+  } else if constexpr (__class_or_enum<_Tp>) {
+    if constexpr (requires(_Tp& __t) {
+                    { _LIBCPP_AUTO_CAST(end(__t)) } -> sentinel_for<iterator_t<_Tp>>;
+                  }) {
+      return [](_Ep __v) noexcept(noexcept(_LIBCPP_AUTO_CAST(end(__v)))) { return _LIBCPP_AUTO_CAST(end(__v)); };
+    } else {
+      return;
+    }
+  } else {
+    return;
   }
-
-  template <class _Tp>
-    requires __unqualified_end<_Tp>
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp&& __t) const
-      noexcept(noexcept(_LIBCPP_AUTO_CAST(end(__t)))) {
-    return _LIBCPP_AUTO_CAST(end(__t));
-  }
-
-  void operator()(auto&&) const = delete;
-};
+}> {};
 } // namespace __end
 
 inline namespace __cpo {
@@ -191,8 +201,9 @@ struct __fn {
 
   template <class _Tp>
     requires is_rvalue_reference_v<_Tp&&>
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp&& __t) const noexcept(
-      noexcept(ranges::end(static_cast<const _Tp&&>(__t)))) -> decltype(ranges::end(static_cast<const _Tp&&>(__t))) {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp&& __t) const
+      noexcept(noexcept(ranges::end(static_cast<const _Tp&&>(__t))))
+          -> decltype(ranges::end(static_cast<const _Tp&&>(__t))) {
     return ranges::end(static_cast<const _Tp&&>(__t));
   }
 };
diff --git a/libcxx/include/__ranges/data.h b/libcxx/include/__ranges/data.h
index 354a0aa15903e..66e057d645573 100644
--- a/libcxx/include/__ranges/data.h
+++ b/libcxx/include/__ranges/data.h
@@ -10,19 +10,21 @@
 #ifndef _LIBCPP___RANGES_DATA_H
 #define _LIBCPP___RANGES_DATA_H
 
-#include <__concepts/class_or_enum.h>
 #include <__config>
 #include <__iterator/concepts.h>
-#include <__iterator/iterator_traits.h>
 #include <__memory/pointer_traits.h>
 #include <__ranges/access.h>
-#include <__type_traits/decay.h>
+#include <__ranges/enable_borrowed_range.h>
+#include <__type_traits/is_array.h>
 #include <__type_traits/is_object.h>
 #include <__type_traits/is_pointer.h>
 #include <__type_traits/is_reference.h>
+#include <__type_traits/remove_all_extents.h>
+#include <__type_traits/remove_cv.h>
 #include <__type_traits/remove_pointer.h>
 #include <__type_traits/remove_reference.h>
 #include <__utility/auto_cast.h>
+#include <__utility/cpo.h>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -49,18 +51,47 @@ concept __ranges_begin_invocable = !__member_data<_Tp> && __can_borrow<_Tp> && r
   { ranges::begin(__t) } -> contiguous_iterator;
 };
 
-struct __fn {
-  template <__member_data _Tp>
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp&& __t) const noexcept(noexcept(__t.data())) {
-    return __t.data();
-  }
-
-  template <__ranges_begin_invocable _Tp>
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp&& __t) const
-      noexcept(noexcept(std::to_address(ranges::begin(__t)))) {
-    return std::to_address(ranges::begin(__t));
+struct __fn : _CPO<[]<class _Ep> consteval noexcept {
+  // [range.prim.data]
+
+  // Given a subexpression E with type T, let t be an lvalue that denotes the reified object for E. Then:
+  using _Tp = remove_reference_t<_Ep>;
+
+  // If E is an rvalue and enable_borrowed_range<remove_cv_t<T>> is false, ranges​::​data(E) is ill-formed.
+  if constexpr (is_rvalue_reference_v<_Ep> && !enable_borrowed_range<remove_cv_t<_Tp>>) {
+    return;
+
+    // Otherwise, if T is an array type ([dcl.array]) and remove_all_extents_t<T> is an incomplete type,
+    // ranges​::​data(E) is ill-formed with no diagnostic required.
+  } else if constexpr (is_array_v<_Tp>) {
+    if constexpr (!requires { sizeof(remove_all_extents_t<_Tp>); }) {
+      return;
+    } else {
+      // This is inlined from ranges::begin
+      return [](_Ep __v) noexcept { return __v + 0; };
+    }
+
+    // Otherwise, if auto(t.data()) is a valid expression of pointer to object type,
+    // ranges​::​data(E) is expression-equivalent to auto(t.data()).
+  } else if constexpr (requires(_Tp& __t) {
+                         { _LIBCPP_AUTO_CAST(__t.data()) } -> __ptr_to_object;
+                       }) {
+    return [](_Ep __v) noexcept(noexcept(_LIBCPP_AUTO_CAST(__v.data()))) { return _LIBCPP_AUTO_CAST(__v.data()); };
+
+    // Otherwise, if ranges​::​begin(t) is a valid expression whose type models contiguous_iterator,
+    // ranges​::​data(E) is expression-equivalent to to_address(ranges​::​begin(t)).
+  } else if constexpr (requires(_Tp& __t) {
+                         { ranges::begin(__t) } -> contiguous_iterator;
+                       }) {
+    return [](_Ep __v) noexcept(noexcept(std::to_address(ranges::begin(__v)))) {
+      return std::to_address(ranges::begin(__v));
+    };
+
+    // Otherwise, ranges​::​data(E) is ill-formed.
+  } else {
+    return;
   }
-};
+}> {};
 } // namespace __data
 
 inline namespace __cpo {
@@ -83,8 +114,9 @@ struct __fn {
 
   template <class _Tp>
     requires is_rvalue_reference_v<_Tp&&>
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp&& __t) const noexcept(
-      noexcept(ranges::data(static_cast<const _Tp&&>(__t)))) -> decltype(ranges::data(static_cast<const _Tp&&>(__t))) {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp&& __t) const
+      noexcept(noexcept(ranges::data(static_cast<const _Tp&&>(__t))))
+          -> decltype(ranges::data(static_cast<const _Tp&&>(__t))) {
     return ranges::data(static_cast<const _Tp&&>(__t));
   }
 };
diff --git a/libcxx/include/__utility/cpo.h b/libcxx/include/__utility/cpo.h
new file mode 100644
index 0000000000000..5ec4c62abd704
--- /dev/null
+++ b/libcxx/include/__utility/cpo.h
@@ -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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___UTILITY_CPO_H
+#define _LIBCPP___UTILITY_CPO_H
+
+#include <__config>
+#include <__utility/forward.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+#if _LIBCPP_STD_VER >= 20
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+template <auto __resolver>
+struct _CPO {
+  template <class... _Args>
+  [[nodiscard]] static constexpr auto operator()(_Args&&... __args) noexcept(
+      noexcept(__resolver.template operator()<_Args&&...>()(std::forward<_Args>(__args)...)))
+      -> decltype(__resolver.template operator()<_Args&&...>()(std::forward<_Args>(__args)...)) {
+    return __resolver.template operator()<_Args&&...>()(std::forward<_Args>(__args)...);
+  }
+};
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif
+
+#endif // _LIBCPP___UTILITY_CPO_H
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 39b4e0bb986c6..df9777b470916 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -2207,6 +2207,7 @@ module std {
     module cmp                             { header "__utility/cmp.h" }
     module constant_wrapper                { header "__utility/constant_wrapper.h" }
     module convert_to_integral             { header "__utility/convert_to_integral.h" }
+    module cpo                             { header "__utility/cpo.h" }
     module default_three_way_comparator    { header "__utility/default_three_way_comparator.h" }
     module element_count                   { header "__utility/element_count.h" }
     module exception_guard                 { header "__utility/exception_guard.h" }
diff --git a/libcxx/test/std/ranges/range.access/begin.verify.cpp b/libcxx/test/std/ranges/range.access/begin.verify.cpp
index 433404b4ad277..df6fce0402215 100644
--- a/libcxx/test/std/ranges/range.access/begin.verify.cpp
+++ b/libcxx/test/std/ranges/range.access/begin.verify.cpp
@@ -20,7 +20,5 @@ static_assert(!std::ranges::enable_borrowed_range<NonBorrowedRange>);
 
 // Verify that if the expression is an rvalue and `enable_borrowed_range` is false, `ranges::begin` is ill-formed.
 void test() {
-  std::ranges::begin(NonBorrowedRange());
-  // expected-error-re at -1 {{{{call to deleted function call operator in type 'const (std::ranges::)?__begin::__fn'}}}}
-  // expected-error at -2  {{attempt to use a deleted function}}
+  std::ranges::begin(NonBorrowedRange()); // expected-error {{no matching function for call to object of type 'const __begin::__fn'}}
 }
diff --git a/libcxx/test/std/ranges/range.access/end.verify.cpp b/libcxx/test/std/ranges/range.access/end.verify.cpp
index a4a7f78b8c6b1..30cbd85243f24 100644
--- a/libcxx/test/std/ranges/range.access/end.verify.cpp
+++ b/libcxx/test/std/ranges/range.access/end.verify.cpp
@@ -20,7 +20,5 @@ static_assert(!std::ranges::enable_borrowed_range<NonBorrowedRange>);
 
 // Verify that if the expression is an rvalue and `enable_borrowed_range` is false, `ranges::end` is ill-formed.
 void test() {
-  std::ranges::end(NonBorrowedRange());
-  // expected-error-re at -1 {{{{call to deleted function call operator in type 'const (std::ranges::)?__end::__fn'}}}}
-  // expected-error at -2  {{attempt to use a deleted function}}
+  std::ranges::end(NonBorrowedRange()); // expected-error {{no matching function for call to object of type 'const __end::__fn'}}
 }



More information about the libcxx-commits mailing list