[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