Re: [PATCH] libstdc++: Make equal and is_permutation short-circuit (LWG 3560)

2024-11-14 Thread Jonathan Wakely
On Thu, 14 Nov 2024 at 17:13, Jonathan Wakely  wrote:
>
> We already implement short-circuiting for random access iterators, but
> we also need to do so for ranges::equal and ranges::is_permutation when
> given sized ranges that are not random access ranges (e.g. std::list).
>
> libstdc++-v3/ChangeLog:
>
> * include/bits/ranges_algo.h (__is_permutation_fn::operator()):
> Short-circuit for sized ranges with different sizes, as per LWG
> 3560.
> * include/bits/ranges_algobase.h (__equal_fn::operator()):
> Likewise.
> * include/bits/stl_algo.h (__is_permutation): Use if-constexpr
> for random access iterator branches.
> * include/bits/stl_algobase.h (__equal4): Likewise.
> * testsuite/25_algorithms/equal/lwg3560.cc: New test.
> * testsuite/25_algorithms/is_permutation/lwg3560.cc: New test.
> ---
>
> Tested x86_64-linux.
>
> Also available for review at:
> https://forge.sourceware.org/gcc/gcc-TEST/pulls/24
>
>  libstdc++-v3/include/bits/ranges_algo.h   |  7 +++
>  libstdc++-v3/include/bits/ranges_algobase.h   |  8 +++
>  libstdc++-v3/include/bits/stl_algo.h  | 13 ++---
>  libstdc++-v3/include/bits/stl_algobase.h  | 44 
>  .../testsuite/25_algorithms/equal/lwg3560.cc  | 49 ++
>  .../25_algorithms/is_permutation/lwg3560.cc   | 51 +++
>  6 files changed, 146 insertions(+), 26 deletions(-)
>  create mode 100644 libstdc++-v3/testsuite/25_algorithms/equal/lwg3560.cc
>  create mode 100644 
> libstdc++-v3/testsuite/25_algorithms/is_permutation/lwg3560.cc
>
> diff --git a/libstdc++-v3/include/bits/ranges_algo.h 
> b/libstdc++-v3/include/bits/ranges_algo.h
> index bae36637b3e..80d4f5a0d57 100644
> --- a/libstdc++-v3/include/bits/ranges_algo.h
> +++ b/libstdc++-v3/include/bits/ranges_algo.h
> @@ -595,6 +595,13 @@ namespace ranges
>operator()(_Range1&& __r1, _Range2&& __r2, _Pred __pred = {},
>  _Proj1 __proj1 = {}, _Proj2 __proj2 = {}) const
>{
> +   // _GLIBCXX_RESOLVE_LIB_DEFECTS
> +   // 3560. ranges::is_permutation should short-circuit for sized_ranges
> +   if constexpr (sized_range<_Range1>)
> + if constexpr (sized_range<_Range2>)
> +   if (ranges::distance(__r1) != ranges::distance(__r2))
> + return false;
> +
> return (*this)(ranges::begin(__r1), ranges::end(__r1),
>ranges::begin(__r2), ranges::end(__r2),
>std::move(__pred),
> diff --git a/libstdc++-v3/include/bits/ranges_algobase.h 
> b/libstdc++-v3/include/bits/ranges_algobase.h
> index df4e770e7a6..3321085c4f6 100644
> --- a/libstdc++-v3/include/bits/ranges_algobase.h
> +++ b/libstdc++-v3/include/bits/ranges_algobase.h
> @@ -172,6 +172,14 @@ namespace ranges
>operator()(_Range1&& __r1, _Range2&& __r2, _Pred __pred = {},
>  _Proj1 __proj1 = {}, _Proj2 __proj2 = {}) const
>{
> +   // _GLIBCXX_RESOLVE_LIB_DEFECTS
> +   // 3560. ranges::equal [...] should short-circuit for sized_ranges
> +   if constexpr (sized_range<_Range1>)
> + if constexpr (sized_range<_Range2>)
> +   if (ranges::distance(__r1) != ranges::distance(__r2))
> + return false;
> +
> +   if constexpr (sized_range<_Range1>)

I'm not sure how this extra line got in the version I posted. I've
removed that locally, and re-pushed to the forge.

> return (*this)(ranges::begin(__r1), ranges::end(__r1),
>ranges::begin(__r2), ranges::end(__r2),
>std::move(__pred),
> diff --git a/libstdc++-v3/include/bits/stl_algo.h 
> b/libstdc++-v3/include/bits/stl_algo.h
> index 04bdaa66981..d8a7668ff83 100644
> --- a/libstdc++-v3/include/bits/stl_algo.h
> +++ b/libstdc++-v3/include/bits/stl_algo.h
> @@ -3471,6 +3471,8 @@ _GLIBCXX_END_INLINE_ABI_NAMESPACE(_V2)
>  }
>
>  #if __cplusplus > 201103L
> +#pragma GCC diagnostic push
> +#pragma GCC diagnostic ignored "-Wc++17-extensions" // if constexpr
>templatetypename _BinaryPredicate>
>  _GLIBCXX20_CONSTEXPR
> @@ -3485,12 +3487,10 @@ _GLIBCXX_END_INLINE_ABI_NAMESPACE(_V2)
> = typename iterator_traits<_ForwardIterator2>::iterator_category;
>using _It1_is_RA = is_same<_Cat1, random_access_iterator_tag>;
>using _It2_is_RA = is_same<_Cat2, random_access_iterator_tag>;
> -  constexpr bool __ra_iters = _It1_is_RA() && _It2_is_RA();
> -  if (__ra_iters)
> +  constexpr bool __ra_iters = __and_<_It1_is_RA, _It2_is_RA>::value;
> +  if constexpr (__ra_iters)
> {
> - auto __d1 = std::distance(__first1, __last1);
> - auto __d2 = std::distance(__first2, __last2);
> - if (__d1 != __d2)
> + if ((__last1 - __first1) != (__last2 - __first2))
> return false;
> }
>
> @@ -3501,7 +3501,7 @@ _GLIBCXX_END_INLINE_ABI_NAMESPACE(_V2)
> if (!__pred(__first1, __first2))
>

[PATCH] libstdc++: Make equal and is_permutation short-circuit (LWG 3560)

2024-11-14 Thread Jonathan Wakely
We already implement short-circuiting for random access iterators, but
we also need to do so for ranges::equal and ranges::is_permutation when
given sized ranges that are not random access ranges (e.g. std::list).

libstdc++-v3/ChangeLog:

* include/bits/ranges_algo.h (__is_permutation_fn::operator()):
Short-circuit for sized ranges with different sizes, as per LWG
3560.
* include/bits/ranges_algobase.h (__equal_fn::operator()):
Likewise.
* include/bits/stl_algo.h (__is_permutation): Use if-constexpr
for random access iterator branches.
* include/bits/stl_algobase.h (__equal4): Likewise.
* testsuite/25_algorithms/equal/lwg3560.cc: New test.
* testsuite/25_algorithms/is_permutation/lwg3560.cc: New test.
---

Tested x86_64-linux.

Also available for review at:
https://forge.sourceware.org/gcc/gcc-TEST/pulls/24

 libstdc++-v3/include/bits/ranges_algo.h   |  7 +++
 libstdc++-v3/include/bits/ranges_algobase.h   |  8 +++
 libstdc++-v3/include/bits/stl_algo.h  | 13 ++---
 libstdc++-v3/include/bits/stl_algobase.h  | 44 
 .../testsuite/25_algorithms/equal/lwg3560.cc  | 49 ++
 .../25_algorithms/is_permutation/lwg3560.cc   | 51 +++
 6 files changed, 146 insertions(+), 26 deletions(-)
 create mode 100644 libstdc++-v3/testsuite/25_algorithms/equal/lwg3560.cc
 create mode 100644 
libstdc++-v3/testsuite/25_algorithms/is_permutation/lwg3560.cc

diff --git a/libstdc++-v3/include/bits/ranges_algo.h 
b/libstdc++-v3/include/bits/ranges_algo.h
index bae36637b3e..80d4f5a0d57 100644
--- a/libstdc++-v3/include/bits/ranges_algo.h
+++ b/libstdc++-v3/include/bits/ranges_algo.h
@@ -595,6 +595,13 @@ namespace ranges
   operator()(_Range1&& __r1, _Range2&& __r2, _Pred __pred = {},
 _Proj1 __proj1 = {}, _Proj2 __proj2 = {}) const
   {
+   // _GLIBCXX_RESOLVE_LIB_DEFECTS
+   // 3560. ranges::is_permutation should short-circuit for sized_ranges
+   if constexpr (sized_range<_Range1>)
+ if constexpr (sized_range<_Range2>)
+   if (ranges::distance(__r1) != ranges::distance(__r2))
+ return false;
+
return (*this)(ranges::begin(__r1), ranges::end(__r1),
   ranges::begin(__r2), ranges::end(__r2),
   std::move(__pred),
diff --git a/libstdc++-v3/include/bits/ranges_algobase.h 
b/libstdc++-v3/include/bits/ranges_algobase.h
index df4e770e7a6..3321085c4f6 100644
--- a/libstdc++-v3/include/bits/ranges_algobase.h
+++ b/libstdc++-v3/include/bits/ranges_algobase.h
@@ -172,6 +172,14 @@ namespace ranges
   operator()(_Range1&& __r1, _Range2&& __r2, _Pred __pred = {},
 _Proj1 __proj1 = {}, _Proj2 __proj2 = {}) const
   {
+   // _GLIBCXX_RESOLVE_LIB_DEFECTS
+   // 3560. ranges::equal [...] should short-circuit for sized_ranges
+   if constexpr (sized_range<_Range1>)
+ if constexpr (sized_range<_Range2>)
+   if (ranges::distance(__r1) != ranges::distance(__r2))
+ return false;
+
+   if constexpr (sized_range<_Range1>)
return (*this)(ranges::begin(__r1), ranges::end(__r1),
   ranges::begin(__r2), ranges::end(__r2),
   std::move(__pred),
diff --git a/libstdc++-v3/include/bits/stl_algo.h 
b/libstdc++-v3/include/bits/stl_algo.h
index 04bdaa66981..d8a7668ff83 100644
--- a/libstdc++-v3/include/bits/stl_algo.h
+++ b/libstdc++-v3/include/bits/stl_algo.h
@@ -3471,6 +3471,8 @@ _GLIBCXX_END_INLINE_ABI_NAMESPACE(_V2)
 }
 
 #if __cplusplus > 201103L
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wc++17-extensions" // if constexpr
   template
 _GLIBCXX20_CONSTEXPR
@@ -3485,12 +3487,10 @@ _GLIBCXX_END_INLINE_ABI_NAMESPACE(_V2)
= typename iterator_traits<_ForwardIterator2>::iterator_category;
   using _It1_is_RA = is_same<_Cat1, random_access_iterator_tag>;
   using _It2_is_RA = is_same<_Cat2, random_access_iterator_tag>;
-  constexpr bool __ra_iters = _It1_is_RA() && _It2_is_RA();
-  if (__ra_iters)
+  constexpr bool __ra_iters = __and_<_It1_is_RA, _It2_is_RA>::value;
+  if constexpr (__ra_iters)
{
- auto __d1 = std::distance(__first1, __last1);
- auto __d2 = std::distance(__first2, __last2);
- if (__d1 != __d2)
+ if ((__last1 - __first1) != (__last2 - __first2))
return false;
}
 
@@ -3501,7 +3501,7 @@ _GLIBCXX_END_INLINE_ABI_NAMESPACE(_V2)
if (!__pred(__first1, __first2))
  break;
 
-  if (__ra_iters)
+  if constexpr (__ra_iters)
{
  if (__first1 == __last1)
return true;
@@ -3532,6 +3532,7 @@ _GLIBCXX_END_INLINE_ABI_NAMESPACE(_V2)
}
   return true;
 }
+#pragma GCC diagnostic pop
 
   /**
*  @brief  Checks whether a permutaion of the second sequence is equal
diff --git a/libstdc++-v3/include/bits/stl_algobase.h 
b/libstdc+