[libc++] [ranges] ADL-proof the [range.access] CPOs.
For example, `std::ranges::range<Holder<Incomplete>*>` should be
well-formed false, not a hard error at compile time.
Differential Revision: https://reviews.llvm.org/D116239
NOKEYCHECK=True
GitOrigin-RevId: 8507383631f2ce2254e35bb81e03319ede056ed1
diff --git a/include/__concepts/class_or_enum.h b/include/__concepts/class_or_enum.h
index 43c7636..aa8606a 100644
--- a/include/__concepts/class_or_enum.h
+++ b/include/__concepts/class_or_enum.h
@@ -25,6 +25,10 @@
template<class _Tp>
concept __class_or_enum = is_class_v<_Tp> || is_union_v<_Tp> || is_enum_v<_Tp>;
+// Work around Clang bug https://llvm.org/PR52970
+template<class _Tp>
+concept __workaround_52970 = is_class_v<__uncvref_t<_Tp>> || is_union_v<__uncvref_t<_Tp>>;
+
#endif // _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_CONCEPTS)
_LIBCPP_END_NAMESPACE_STD
diff --git a/include/__ranges/access.h b/include/__ranges/access.h
index 4a12421..246f8b2 100644
--- a/include/__ranges/access.h
+++ b/include/__ranges/access.h
@@ -9,6 +9,7 @@
#ifndef _LIBCPP___RANGES_ACCESS_H
#define _LIBCPP___RANGES_ACCESS_H
+#include <__concepts/class_or_enum.h>
#include <__config>
#include <__iterator/concepts.h>
#include <__iterator/readable_traits.h>
@@ -39,6 +40,7 @@
template <class _Tp>
concept __member_begin =
__can_borrow<_Tp> &&
+ __workaround_52970<_Tp> &&
requires(_Tp&& __t) {
{ _LIBCPP_AUTO_CAST(__t.begin()) } -> input_or_output_iterator;
};
@@ -102,6 +104,7 @@
template <class _Tp>
concept __member_end =
__can_borrow<_Tp> &&
+ __workaround_52970<_Tp> &&
requires(_Tp&& __t) {
typename iterator_t<_Tp>;
{ _LIBCPP_AUTO_CAST(__t.end()) } -> sentinel_for<iterator_t<_Tp>>;
diff --git a/include/__ranges/empty.h b/include/__ranges/empty.h
index e8a8aab..8da0b12 100644
--- a/include/__ranges/empty.h
+++ b/include/__ranges/empty.h
@@ -9,6 +9,7 @@
#ifndef _LIBCPP___RANGES_EMPTY_H
#define _LIBCPP___RANGES_EMPTY_H
+#include <__concepts/class_or_enum.h>
#include <__config>
#include <__iterator/concepts.h>
#include <__ranges/access.h>
@@ -28,9 +29,11 @@
namespace ranges {
namespace __empty {
template <class _Tp>
- concept __member_empty = requires(_Tp&& __t) {
- bool(__t.empty());
- };
+ concept __member_empty =
+ __workaround_52970<_Tp> &&
+ requires(_Tp&& __t) {
+ bool(__t.empty());
+ };
template<class _Tp>
concept __can_invoke_size =
diff --git a/include/__ranges/size.h b/include/__ranges/size.h
index fc6641c..f3de5a8 100644
--- a/include/__ranges/size.h
+++ b/include/__ranges/size.h
@@ -9,6 +9,7 @@
#ifndef _LIBCPP___RANGES_SIZE_H
#define _LIBCPP___RANGES_SIZE_H
+#include <__concepts/class_or_enum.h>
#include <__config>
#include <__iterator/concepts.h>
#include <__iterator/iterator_traits.h>
@@ -41,9 +42,12 @@
concept __size_enabled = !disable_sized_range<remove_cvref_t<_Tp>>;
template <class _Tp>
- concept __member_size = __size_enabled<_Tp> && requires(_Tp&& __t) {
- { _LIBCPP_AUTO_CAST(__t.size()) } -> __integer_like;
- };
+ concept __member_size =
+ __size_enabled<_Tp> &&
+ __workaround_52970<_Tp> &&
+ requires(_Tp&& __t) {
+ { _LIBCPP_AUTO_CAST(__t.size()) } -> __integer_like;
+ };
template <class _Tp>
concept __unqualified_size =
diff --git a/test/std/ranges/range.access/begin.pass.cpp b/test/std/ranges/range.access/begin.pass.cpp
index 1a69519..11170fa 100644
--- a/test/std/ranges/range.access/begin.pass.cpp
+++ b/test/std/ranges/range.access/begin.pass.cpp
@@ -303,6 +303,12 @@
static_assert(noexcept(std::ranges::begin(brar)));
static_assert(noexcept(std::ranges::cbegin(brar)));
+// Test ADL-proofing.
+struct Incomplete;
+template<class T> struct Holder { T t; };
+static_assert(!std::is_invocable_v<RangeBeginT, Holder<Incomplete>*>);
+static_assert(!std::is_invocable_v<RangeCBeginT, Holder<Incomplete>*>);
+
int main(int, char**) {
static_assert(testReturnTypes());
diff --git a/test/std/ranges/range.access/data.pass.cpp b/test/std/ranges/range.access/data.pass.cpp
index 40d2d3a..6d0b718 100644
--- a/test/std/ranges/range.access/data.pass.cpp
+++ b/test/std/ranges/range.access/data.pass.cpp
@@ -176,6 +176,11 @@
return true;
}
+// Test ADL-proofing.
+struct Incomplete;
+template<class T> struct Holder { T t; };
+static_assert(!std::is_invocable_v<RangeDataT, Holder<Incomplete>*>);
+
struct RandomButNotContiguous {
random_access_iterator<int*> begin() const;
random_access_iterator<int*> end() const;
diff --git a/test/std/ranges/range.access/empty.pass.cpp b/test/std/ranges/range.access/empty.pass.cpp
index 18cdce0..5724acc 100644
--- a/test/std/ranges/range.access/empty.pass.cpp
+++ b/test/std/ranges/range.access/empty.pass.cpp
@@ -168,6 +168,11 @@
return true;
}
+// Test ADL-proofing.
+struct Incomplete;
+template<class T> struct Holder { T t; };
+static_assert(!std::is_invocable_v<RangeEmptyT, Holder<Incomplete>*>);
+
int main(int, char**) {
testEmptyMember();
static_assert(testEmptyMember());
diff --git a/test/std/ranges/range.access/end.pass.cpp b/test/std/ranges/range.access/end.pass.cpp
index 27eaf74..4b1d4e3 100644
--- a/test/std/ranges/range.access/end.pass.cpp
+++ b/test/std/ranges/range.access/end.pass.cpp
@@ -350,6 +350,12 @@
static_assert(noexcept(std::ranges::end(erar)));
static_assert(noexcept(std::ranges::cend(erar)));
+// Test ADL-proofing.
+struct Incomplete;
+template<class T> struct Holder { T t; };
+static_assert(!std::is_invocable_v<RangeEndT, Holder<Incomplete>*>);
+static_assert(!std::is_invocable_v<RangeCEndT, Holder<Incomplete>*>);
+
int main(int, char**) {
static_assert(testReturnTypes());
diff --git a/test/std/ranges/range.access/size.pass.cpp b/test/std/ranges/range.access/size.pass.cpp
index 0a45a2d..915e67e 100644
--- a/test/std/ranges/range.access/size.pass.cpp
+++ b/test/std/ranges/range.access/size.pass.cpp
@@ -314,6 +314,11 @@
return true;
}
+// Test ADL-proofing.
+struct Incomplete;
+template<class T> struct Holder { T t; };
+static_assert(!std::is_invocable_v<RangeSizeT, Holder<Incomplete>*>);
+
int main(int, char**) {
testArrayType();
static_assert(testArrayType());
diff --git a/test/std/ranges/range.access/ssize.pass.cpp b/test/std/ranges/range.access/ssize.pass.cpp
index 39e7b80..c351928 100644
--- a/test/std/ranges/range.access/ssize.pass.cpp
+++ b/test/std/ranges/range.access/ssize.pass.cpp
@@ -78,6 +78,11 @@
return true;
}
+// Test ADL-proofing.
+struct Incomplete;
+template<class T> struct Holder { T t; };
+static_assert(!std::is_invocable_v<RangeSSizeT, Holder<Incomplete>*>);
+
int main(int, char**) {
test();
static_assert(test());
diff --git a/test/std/ranges/range.req/range.range/range.compile.pass.cpp b/test/std/ranges/range.req/range.range/range.compile.pass.cpp
index ecc8048..adf1caa 100644
--- a/test/std/ranges/range.req/range.range/range.compile.pass.cpp
+++ b/test/std/ranges/range.req/range.range/range.compile.pass.cpp
@@ -46,3 +46,8 @@
int* end();
};
static_assert(!std::ranges::range<int_begin_iterator_end>);
+
+// Test ADL-proofing.
+struct Incomplete;
+template<class T> struct Holder { T t; };
+static_assert(!std::ranges::range<Holder<Incomplete>*>);