https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108823
--- Comment #1 from Jonathan Wakely <redi at gcc dot gnu.org> ---
So maybe something like:
--- a/libstdc++-v3/include/bits/ranges_algo.h
+++ b/libstdc++-v3/include/bits/ranges_algo.h
@@ -758,11 +758,21 @@ namespace ranges
_Out __result, _Fp __binary_op,
_Proj1 __proj1 = {}, _Proj2 __proj2 = {}) const
{
- for (; __first1 != __last1 && __first2 != __last2;
- ++__first1, (void)++__first2, ++__result)
- *__result = std::__invoke(__binary_op,
- std::__invoke(__proj1, *__first1),
- std::__invoke(__proj2, *__first2));
+ if constexpr (sized_sentinel_for<_Sent1, _Iter1>
+ && sized_sentinel_for<_Sent2, _Iter2>)
+ for (auto __sz = std::min<size_t>(__last1 - __first1,
+ __last2 - __first2);
+ __sz--;
+ ++__first1, (void)++__first2, ++__result)
+ *__result = std::__invoke(__binary_op,
+ std::__invoke(__proj1, *__first1),
+ std::__invoke(__proj2, *__first2));
+ else
+ for (; __first1 != __last1 && __first2 != __last2;
+ ++__first1, (void)++__first2, ++__result)
+ *__result = std::__invoke(__binary_op,
+ std::__invoke(__proj1, *__first1),
+ std::__invoke(__proj2, *__first2));
return {std::move(__first1), std::move(__first2), std::move(__result)};
}
@@ -778,10 +788,28 @@ namespace ranges
operator()(_Range1&& __r1, _Range2&& __r2, _Out __result, _Fp
__binary_op,
_Proj1 __proj1 = {}, _Proj2 __proj2 = {}) const
{
- return (*this)(ranges::begin(__r1), ranges::end(__r1),
- ranges::begin(__r2), ranges::end(__r2),
- std::move(__result), std::move(__binary_op),
- std::move(__proj1), std::move(__proj2));
+ if constexpr (sized_range<_Range1> && sized_range<_Range2>
+ && (random_access_range<_Range1>
+ || random_access_range<_Range2>))
+ {
+ auto __sz = std::min<size_t>(ranges::size(__r1),
+ ranges::size(__r2));
+ auto __b1 = ranges::begin(__r1);
+ auto __b2 = ranges::begin(__r2);
+ if constexpr (random_access_range<_Range1>)
+ return (*this)(__b1, __b1 + __sz, __b2, unreachable_sentinel,
+ std::move(__result), std::move(__binary_op),
+ std::move(__proj1), std::move(__proj2));
+ else
+ return (*this)(__b1, unreachable_sentinel, __b2, __b2 + __sz,
+ std::move(__result), std::move(__binary_op),
+ std::move(__proj1), std::move(__proj2));
+ }
+ else
+ return (*this)(ranges::begin(__r1), ranges::end(__r1),
+ ranges::begin(__r2), ranges::end(__r2),
+ std::move(__result), std::move(__binary_op),
+ std::move(__proj1), std::move(__proj2));
}
};
So ranges::transform(i1, s1, i2, s2, out, op) calculates the loop count first
if sized_sentinel_for is satisfied for both ranges, and then loops that many
times instead of using i1 != s1 && i2 != s2
And ranges::transform(r1, r2, out, op) calculates the loop count using
ranges::size on both ranges and uses your unreachable_sentinel trick if either
of the ranges is random access. This way we can simplify the i1 != s1 && i2 !=
s2 check even if only one of the ranges is random access, e.g. transforming a
std::vector and std::list. In the latter case we probably won't get
vectorization because the iterators aren't pointers, but it might still be
beneficial to simplify the loop condition.