[libcxx-commits] [libcxx] [libc++][RFC] Rewrite CPOs with resolver functions (PR #209104)
via libcxx-commits
libcxx-commits at lists.llvm.org
Mon Jul 13 01:08:24 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-libcxx
Author: Nikolas Klauser (philnik777)
<details>
<summary>Changes</summary>
This patch rewrites some CPOs to use resolver functions instead of overload sets. This has the benefit that the implementation is much closer to the standard wording. I also find it somewhat easier to understand what the intention is.
I'm not sure whether we actually want to do this, but I thought I'd propose it and see whether people like it.
---
Full diff: https://github.com/llvm/llvm-project/pull/209104.diff
6 Files Affected:
- (modified) libcxx/include/CMakeLists.txt (+1)
- (modified) libcxx/include/__ranges/access.h (+95-85)
- (modified) libcxx/include/__ranges/data.h (+44-16)
- (added) libcxx/include/__utility/cpo.h (+33)
- (modified) libcxx/test/std/ranges/range.access/begin.verify.cpp (+1-3)
- (modified) libcxx/test/std/ranges/range.access/end.verify.cpp (+1-3)
``````````diff
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..314f4d933626b 100644
--- a/libcxx/include/__ranges/access.h
+++ b/libcxx/include/__ranges/access.h
@@ -12,15 +12,16 @@
#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/is_reference.h>
+#include <__type_traits/extent.h>
+#include <__type_traits/is_array.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 +41,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 +103,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 +200,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..075ae7cdaba8a 100644
--- a/libcxx/include/__ranges/data.h
+++ b/libcxx/include/__ranges/data.h
@@ -10,19 +10,17 @@
#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 <__type_traits/is_object.h>
#include <__type_traits/is_pointer.h>
#include <__type_traits/is_reference.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 +47,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 +110,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..e52cdb9a22168
--- /dev/null
+++ b/libcxx/include/__utility/cpo.h
@@ -0,0 +1,33 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 _LIBCPP_STD_VER >= 20
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+template <auto __resolver>
+struct _CPO {
+ template <class... _Args>
+ 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/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'}}
}
``````````
</details>
https://github.com/llvm/llvm-project/pull/209104
More information about the libcxx-commits
mailing list