[libcxx-commits] [libcxx] [libc++][RFC] Rewrite CPOs with resolver functions (PR #209104)
Nikolas Klauser via libcxx-commits
libcxx-commits at lists.llvm.org
Thu Jul 30 06:35:04 PDT 2026
================
@@ -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 {
----------------
philnik777 wrote:
Taking
```c++
#include <ranges>
auto test() {
std::ranges::begin(1);
}
```
After some slight modifications to the code (i.e. adding explicit diagnostic messages) I get this:
before:
```
<source>:4:3: error: call to deleted function call operator in type 'const __begin::__fn'
4 | std::ranges::begin(1);
| ^~~~~~~~~~~~~~~~~~
<__ranges/access.h>:85:8: note: candidate function [with auto:1 = int] has been explicitly deleted
85 | void operator()(auto&&) const = delete;
| ^
<__ranges/access.h>:58:54: note: candidate template ignored: could not match '_Tp[]' against 'int'
58 | [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp (&__t)[]) const noexcept
| ^
<__ranges/access.h>:65:54: note: candidate template ignored: could not match '_Tp[_Np]' against 'int'
65 | [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp (&__t)[_Np]) const noexcept
| ^
<__ranges/access.h>:73:54: note: candidate template ignored: constraints not satisfied [with _Tp = int]
73 | [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp&& __t) const
| ^
<__ranges/access.h>:72:14: note: because 'int' does not satisfy '__member_begin'
72 | requires __member_begin<_Tp>
| ^
<__ranges/access.h>:44:26: note: because 'int' does not satisfy '__can_borrow'
44 | concept __member_begin = __can_borrow<_Tp> && requires(_Tp&& __t) {
| ^
<__ranges/access.h>:36:24: note: because 'is_lvalue_reference_v<int>' evaluated to false
36 | concept __can_borrow = is_lvalue_reference_v<_Tp> || enable_borrowed_range<remove_cvref_t<_Tp>>;
| ^
<__ranges/access.h>:36:54: note: and 'enable_borrowed_range<remove_cvref_t<int>>' evaluated to false
36 | concept __can_borrow = is_lvalue_reference_v<_Tp> || enable_borrowed_range<remove_cvref_t<_Tp>>;
| ^
<__ranges/access.h>:80:54: note: candidate template ignored: constraints not satisfied [with _Tp = int]
80 | [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp&& __t) const
| ^
<__ranges/access.h>:79:14: note: because 'int' does not satisfy '__unqualified_begin'
79 | requires __unqualified_begin<_Tp>
| ^
<__ranges/access.h>:52:29: note: because 'int' does not satisfy '__can_borrow'
52 | !__member_begin<_Tp> && __can_borrow<_Tp> && __class_or_enum<remove_cvref_t<_Tp>> && requires(_Tp&& __t) {
| ^
<__ranges/access.h>:36:24: note: because 'is_lvalue_reference_v<int>' evaluated to false
36 | concept __can_borrow = is_lvalue_reference_v<_Tp> || enable_borrowed_range<remove_cvref_t<_Tp>>;
| ^
<__ranges/access.h>:36:54: note: and 'enable_borrowed_range<remove_cvref_t<int>>' evaluated to false
36 | concept __can_borrow = is_lvalue_reference_v<_Tp> || enable_borrowed_range<remove_cvref_t<_Tp>>;
| ^
<source>:4:21: error: attempt to use a deleted function
4 | std::ranges::begin(1);
| ^
<__ranges/access.h>:85:8: note: 'operator()<int>' has been explicitly marked deleted here
85 | void operator()(auto&&) const = delete;
| ^
2 errors generated.
```
after:
```
<source>:4:3: error: no matching function for call to object of type 'const __begin::__fn'
4 | std::ranges::begin(1);
| ^~~~~~~~~~~~~~~~~~
<__utility/cpo.h>:33:39: note: candidate template ignored: substitution failure [with _Args = <int>]: type '__diagnostic<"calling ranges::begin on an rvalue of a non-borrowed range">' does not provide a call operator
33 | [[nodiscard]] static constexpr auto operator()(_Args&&... __args) noexcept(
| ^
34 | noexcept(__resolver.template operator()<_Args&&...>()(std::forward<_Args>(__args)...)))
35 | -> decltype(__resolver.template operator()<_Args&&...>()(std::forward<_Args>(__args)...)) {
| ~~~~~~~~~~
1 error generated.
```
This can probably be improved, but arguably the new version is already way easier to understand if you know that you have to check the type. Maybe we can modify Clang to detect if substitution failure is on a `__diagnostic` template and provide the message directly instead of sneakily through the type (similar to the detection of `enable_if_t` and `__enable_if_t`).
CC @AaronBallman @cor3ntin for thoughts on improving the diagnostic here.
https://github.com/llvm/llvm-project/pull/209104
More information about the libcxx-commits
mailing list