-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[libc++] Check correctly ref-qualified __is_callable in algorithms #73451
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@llvm/pr-subscribers-libcxx Author: Nhat Nguyen (changkhothuychung) ChangesFull diff: https://github.com/llvm/llvm-project/pull/73451.diff 8 Files Affected:
diff --git a/libcxx/include/__algorithm/equal_range.h b/libcxx/include/__algorithm/equal_range.h
index dc1268a6ff110c7..9528635730f7515 100644
--- a/libcxx/include/__algorithm/equal_range.h
+++ b/libcxx/include/__algorithm/equal_range.h
@@ -60,8 +60,9 @@ __equal_range(_Iter __first, _Sent __last, const _Tp& __value, _Compare&& __comp
template <class _ForwardIterator, class _Tp, class _Compare>
_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 pair<_ForwardIterator, _ForwardIterator>
equal_range(_ForwardIterator __first, _ForwardIterator __last, const _Tp& __value, _Compare __comp) {
- static_assert(__is_callable<_Compare, decltype(*__first), const _Tp&>::value,
- "The comparator has to be callable");
+ static_assert(__is_callable<_Compare&, decltype(*__first), const _Tp&>::value, "The comparator has to be callable");
+ static_assert(
+ __is_callable<_Compare const&, decltype(*__first), const _Tp&>::value, "The comparator has to be const-callable");
static_assert(is_copy_constructible<_ForwardIterator>::value,
"Iterator has to be copy constructible");
return std::__equal_range<_ClassicAlgPolicy>(
diff --git a/libcxx/include/__algorithm/includes.h b/libcxx/include/__algorithm/includes.h
index 88253e2653d27a7..9e20fe6174f337e 100644
--- a/libcxx/include/__algorithm/includes.h
+++ b/libcxx/include/__algorithm/includes.h
@@ -45,8 +45,9 @@ _LIBCPP_NODISCARD_EXT inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
_InputIterator2 __first2,
_InputIterator2 __last2,
_Compare __comp) {
- static_assert(__is_callable<_Compare, decltype(*__first1), decltype(*__first2)>::value,
- "Comparator has to be callable");
+ static_assert(__is_callable<_Compare&, decltype(*__first), const _Tp&>::value, "The comparator has to be callable");
+ static_assert(
+ __is_callable<_Compare const&, decltype(*__first), const _Tp&>::value, "The comparator has to be const-callable");
return std::__includes(
std::move(__first1),
diff --git a/libcxx/include/__algorithm/is_permutation.h b/libcxx/include/__algorithm/is_permutation.h
index 105a0732283c902..17aaf12ceeda2be 100644
--- a/libcxx/include/__algorithm/is_permutation.h
+++ b/libcxx/include/__algorithm/is_permutation.h
@@ -191,8 +191,9 @@ template <class _ForwardIterator1, class _ForwardIterator2, class _BinaryPredica
_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 bool
is_permutation(_ForwardIterator1 __first1, _ForwardIterator1 __last1, _ForwardIterator2 __first2,
_BinaryPredicate __pred) {
- static_assert(__is_callable<_BinaryPredicate, decltype(*__first1), decltype(*__first2)>::value,
- "The predicate has to be callable");
+ static_assert(__is_callable<_Compare&, decltype(*__first), const _Tp&>::value, "The comparator has to be callable");
+ static_assert(
+ __is_callable<_Compare const&, decltype(*__first), const _Tp&>::value, "The comparator has to be const-callable");
return std::__is_permutation<_ClassicAlgPolicy>(
std::move(__first1), std::move(__last1), std::move(__first2), __pred);
diff --git a/libcxx/include/__algorithm/lower_bound.h b/libcxx/include/__algorithm/lower_bound.h
index 91c3bdaafd0cfda..399ebaa88e565de 100644
--- a/libcxx/include/__algorithm/lower_bound.h
+++ b/libcxx/include/__algorithm/lower_bound.h
@@ -49,8 +49,9 @@ _Iter __lower_bound(_Iter __first, _Sent __last, const _Type& __value, _Comp& __
template <class _ForwardIterator, class _Tp, class _Compare>
_LIBCPP_NODISCARD_EXT inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
_ForwardIterator lower_bound(_ForwardIterator __first, _ForwardIterator __last, const _Tp& __value, _Compare __comp) {
- static_assert(__is_callable<_Compare, decltype(*__first), const _Tp&>::value,
- "The comparator has to be callable");
+ static_assert(__is_callable<_Compare&, decltype(*__first), const _Tp&>::value, "The comparator has to be callable");
+ static_assert(
+ __is_callable<_Compare const&, decltype(*__first), const _Tp&>::value, "The comparator has to be const-callable");
auto __proj = std::__identity();
return std::__lower_bound<_ClassicAlgPolicy>(__first, __last, __value, __comp, __proj);
}
diff --git a/libcxx/include/__algorithm/min_element.h b/libcxx/include/__algorithm/min_element.h
index 45f3e85ef92d9fc..3e8f1a31bced402 100644
--- a/libcxx/include/__algorithm/min_element.h
+++ b/libcxx/include/__algorithm/min_element.h
@@ -54,9 +54,9 @@ min_element(_ForwardIterator __first, _ForwardIterator __last, _Compare __comp)
{
static_assert(__has_forward_iterator_category<_ForwardIterator>::value,
"std::min_element requires a ForwardIterator");
- static_assert(__is_callable<_Compare, decltype(*__first), decltype(*__first)>::value,
- "The comparator has to be callable");
-
+ static_assert(__is_callable<_Compare&, decltype(*__first), const _Tp&>::value, "The comparator has to be callable");
+ static_assert(
+ __is_callable<_Compare const&, decltype(*__first), const _Tp&>::value, "The comparator has to be const-callable");
return std::__min_element<__comp_ref_type<_Compare> >(std::move(__first), std::move(__last), __comp);
}
diff --git a/libcxx/include/__algorithm/minmax_element.h b/libcxx/include/__algorithm/minmax_element.h
index 5bcaf8354d9ffb6..49b62b87c6b2602 100644
--- a/libcxx/include/__algorithm/minmax_element.h
+++ b/libcxx/include/__algorithm/minmax_element.h
@@ -85,8 +85,9 @@ pair<_ForwardIterator, _ForwardIterator>
minmax_element(_ForwardIterator __first, _ForwardIterator __last, _Compare __comp) {
static_assert(__has_forward_iterator_category<_ForwardIterator>::value,
"std::minmax_element requires a ForwardIterator");
- static_assert(__is_callable<_Compare, decltype(*__first), decltype(*__first)>::value,
- "The comparator has to be callable");
+ static_assert(__is_callable<_Compare&, decltype(*__first), const _Tp&>::value, "The comparator has to be callable");
+ static_assert(
+ __is_callable<_Compare const&, decltype(*__first), const _Tp&>::value, "The comparator has to be const-callable");
auto __proj = __identity();
return std::__minmax_element_impl(__first, __last, __comp, __proj);
}
diff --git a/libcxx/include/__algorithm/partial_sort_copy.h b/libcxx/include/__algorithm/partial_sort_copy.h
index b9635c51d5fabef..a0b0525b4ee9e98 100644
--- a/libcxx/include/__algorithm/partial_sort_copy.h
+++ b/libcxx/include/__algorithm/partial_sort_copy.h
@@ -65,8 +65,9 @@ _RandomAccessIterator
partial_sort_copy(_InputIterator __first, _InputIterator __last,
_RandomAccessIterator __result_first, _RandomAccessIterator __result_last, _Compare __comp)
{
- static_assert(__is_callable<_Compare, decltype(*__first), decltype(*__result_first)>::value,
- "Comparator has to be callable");
+ static_assert(__is_callable<_Compare&, decltype(*__first), const _Tp&>::value, "The comparator has to be callable");
+ static_assert(
+ __is_callable<_Compare const&, decltype(*__first), const _Tp&>::value, "The comparator has to be const-callable");
auto __result = std::__partial_sort_copy<_ClassicAlgPolicy>(__first, __last, __result_first, __result_last,
static_cast<__comp_ref_type<_Compare> >(__comp), __identity(), __identity());
diff --git a/libcxx/test/libcxx/random/random.uniform.real/test.cpp b/libcxx/test/libcxx/random/random.uniform.real/test.cpp
new file mode 100644
index 000000000000000..bc84e362034ccd7
--- /dev/null
+++ b/libcxx/test/libcxx/random/random.uniform.real/test.cpp
@@ -0,0 +1,19 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// <random>
+
+// template<_RealType = double>
+// class uniform_real_distribution;
+
+// result_type must be floating type, int type is unsupported
+
+#include <random>
+
+// expected-error@*:* {{static assertion failed due to requirement '__libcpp_random_is_valid_realtype<int>::value': RealType must be a supported floating-point type}}
+struct test_random : public std::uniform_real_distribution<int> {};
|
@ldionne ldi |
@@ -0,0 +1,19 @@ | |||
//===----------------------------------------------------------------------===// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a .verify.cpp
test. Please look at other .verify.cpp
tests for inspiration, they're really not that hard to write.
To trigger the static_assert
, you'll have to create a comparator that can't be called as a F const&
and one that can't be called as a F&
. You can do
struct ConstUncallable {
void operator()(...) const& = delete;
void operator()(...) & { implementation }
};
struct NonConstUncallable {
void operator()(...) const& { implementation }
void operator()(...) & = delete;
};
With the proper signature as expected by each algorithm, of course.
@@ -0,0 +1,19 @@ | |||
//===----------------------------------------------------------------------===// | |||
// | |||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You also need to add a test that a predicate like this works for all the algorithms:
struct RvalueRefUncallable {
void operator()(...) && = delete;
void operator()(...) { implementation }
};
Since that was the original bug report IIRC.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ldionne l
did you want to make the second operator const &
? I am getting errors, overloading either requires both functions to have ref-qualifiers or both lack.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a .pass
file, can you take a look and let me know if they are good? if yes, I will add similar tests for the remaining algorithms.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, please add a description of the patch in the PR description!
You can test this locally with the following command:git-clang-format --diff 389146816711ef6d11e54726e8ea300bf9945dc3 c83bcc434b984e630402f54dad26bf6ad540a817 --extensions cpp,h -- libcxx/test/libcxx/algorithms/callable-requirements-rvalue.compile.pass.cpp libcxx/test/libcxx/algorithms/callable-requirements.verify.cpp libcxx/include/__algorithm/equal_range.h libcxx/include/__algorithm/includes.h libcxx/include/__algorithm/is_permutation.h libcxx/include/__algorithm/lower_bound.h libcxx/include/__algorithm/max_element.h libcxx/include/__algorithm/min_element.h libcxx/include/__algorithm/minmax.h libcxx/include/__algorithm/minmax_element.h libcxx/include/__algorithm/partial_sort_copy.h libcxx/include/__algorithm/search.h libcxx/include/__algorithm/search_n.h libcxx/include/__algorithm/upper_bound.h libcxx/src/regex.cpp libcxx/test/libcxx/algorithms/ranges_robust_against_copying_projections.pass.cpp libcxx/test/libcxx/algorithms/robust_against_cpp20_hostile_iterators.compile.pass.cpp libcxx/test/std/algorithms/alg.nonmodifying/alg.is_permutation/is_permutation_pred.pass.cpp libcxx/test/std/algorithms/alg.nonmodifying/alg.search/search_n_pred.pass.cpp libcxx/test/std/algorithms/alg.nonmodifying/alg.search/search_pred.pass.cpp libcxx/test/std/algorithms/alg.sorting/alg.merge/inplace_merge_comp.pass.cpp libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/stable_sort_comp.pass.cpp View the diff from clang-format here.diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.search/search_n_pred.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.search/search_n_pred.pass.cpp
index 2fa3e9ffac..ccc948ae1f 100644
--- a/libcxx/test/std/algorithms/alg.nonmodifying/alg.search/search_n_pred.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.search/search_n_pred.pass.cpp
@@ -32,14 +32,14 @@ TEST_CONSTEXPR bool test_constexpr() {
}
#endif
-struct count_equal {
- static unsigned count;
- template <class T>
- bool operator()(const T& x, const T& y) const {
- ++count;
- return x == y;
- }
-};
+ struct count_equal {
+ static unsigned count;
+ template <class T>
+ bool operator()(const T& x, const T& y) const {
+ ++count;
+ return x == y;
+ }
+ };
unsigned count_equal::count = 0;
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand the motivation behind static asserts like this. They have a number of downsides.
- They cost compile time and memory. The instantiations they perform live in the compilers AST until the end of the TU.
- They don't catch broken code. The broken code is already broken, this just diagnoses it differently.
- They break well formed code.
As evidenced here, when we get the needless checks wrong, they break valid code. Which is a lot worse than providing a slightly better diagnostic to ill-formed code.
I say we get rid of them entirely.
@ldionne l |
@philnik777 Do you remember why you added the |
We added them to disallow the use of functions to member pointers etc. to be passed to the classic algorithms. |
Oh yeah, that's it. Wouldn't that fail to compile already though? |
@ldionne it looks like there is a bug with the use of |
Yes, we should add a const-qualification to @EricWF Now that I think about it some more, I think the issue these static asserts are addressing is that without them, we'll end up using |
72215ed
to
f442f11
Compare
We were only checking that the comparator was rvalue callable, when in reality the algorithms always call comparators as lvalues. This patch also refactors the tests for callable requirements and expands it to a few missing algorithms. Fixes llvm#69554
ce7bbe3
to
c83bcc4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will merge once CI is green.
This one breaks multiple build bot https://lab.llvm.org/buildbot/#/builders/66/builds/2419
|
I'll revert. I think that's a bug in the order of arguments I passed to |
We were only checking that the comparator was rvalue callable, when in reality the algorithms always call comparators as lvalues. This patch also refactors the tests for callable requirements and expands it to a few missing algorithms. This is take 2 of llvm#73451, which was reverted because it broke some CI bots. Fixes llvm#69554
We were only checking that the comparator was rvalue callable, when in reality the algorithms always call comparators as lvalues. This patch also refactors the tests for callable requirements and expands it to a few missing algorithms. This is take 2 of llvm#73451, which was reverted because it broke some CI bots. Fixes llvm#69554
…101553) We were only checking that the comparator was rvalue callable, when in reality the algorithms always call comparators as lvalues. This patch also refactors the tests for callable requirements and expands it to a few missing algorithms. This is take 2 of #73451, which was reverted because it broke some CI bots. The issue was that we checked __is_callable with arguments in the wrong order inside std::upper_bound. This has now been fixed and a test was added. Fixes #69554
We were only checking that the comparator was rvalue callable,
when in reality the algorithms always call comparators as lvalues.
This patch also refactors the tests for callable requirements and
expands it to a few missing algorithms.
Fixes #69554