This rewrites ranges::minmax and ranges::minmax_element so that it performs at most 3*N/2 many comparisons, as required by the standard. In passing, this also fixes PR100387 by avoiding a premature std::move in ranges::minmax and in std::shift_right.
Tested on x86_64-pc-linux-gnu, does this look OK for trunk and perhaps 10/11? libstdc++-v3/ChangeLog: PR libstdc++/100387 * include/bits/ranges_algo.h (__minmax_fn::operator()): Rewrite to limit comparison complexity to 3*N/2. Avoid premature std::move. (__minmax_element_fn::operator()): Likewise. (shift_right): Avoid premature std::move of __result. * testsuite/25_algorithms/minmax/constrained.cc (test04, test05): New tests. * testsuite/25_algorithms/minmax_element/constrained.cc (test02): Likewise. --- libstdc++-v3/include/bits/ranges_algo.h | 87 ++++++++++++++----- .../25_algorithms/minmax/constrained.cc | 31 +++++++ .../minmax_element/constrained.cc | 19 ++++ 3 files changed, 113 insertions(+), 24 deletions(-) diff --git a/libstdc++-v3/include/bits/ranges_algo.h b/libstdc++-v3/include/bits/ranges_algo.h index cda3042c11f..bbd29127e89 100644 --- a/libstdc++-v3/include/bits/ranges_algo.h +++ b/libstdc++-v3/include/bits/ranges_algo.h @@ -3291,18 +3291,39 @@ namespace ranges auto __first = ranges::begin(__r); auto __last = ranges::end(__r); __glibcxx_assert(__first != __last); + auto __comp_proj = __detail::__make_comp_proj(__comp, __proj); minmax_result<range_value_t<_Range>> __result = {*__first, *__first}; while (++__first != __last) { - auto __tmp = *__first; - if (std::__invoke(__comp, - std::__invoke(__proj, __tmp), - std::__invoke(__proj, __result.min))) - __result.min = std::move(__tmp); - if (!(bool)std::__invoke(__comp, - std::__invoke(__proj, __tmp), - std::__invoke(__proj, __result.max))) - __result.max = std::move(__tmp); + // Process two elements at a time so that we perform at most + // 3*N/2 many comparisons in total (each of the N/2 iterations + // of this loop performs three comparisions). + auto __val1 = *__first; + if (++__first == __last) + { + // N is odd; in this final iteration, we perform a just one + // comparison, for a total of 3*(N-1)/2 + 1 < 3*N/2 comparisons. + if (__comp_proj(__val1, __result.min)) + __result.min = std::move(__val1); + else if (!__comp_proj(__val1, __result.max)) + __result.max = std::move(__val1); + break; + } + auto __val2 = *__first; + if (!__comp_proj(__val2, __val1)) + { + if (__comp_proj(__val1, __result.min)) + __result.min = std::move(__val1); + if (!__comp_proj(__val2, __result.max)) + __result.max = std::move(__val2); + } + else + { + if (__comp_proj(__val2, __result.min)) + __result.min = std::move(__val2); + if (!__comp_proj(__val1, __result.max)) + __result.max = std::move(__val1); + } } return __result; } @@ -3408,21 +3429,40 @@ namespace ranges operator()(_Iter __first, _Sent __last, _Comp __comp = {}, _Proj __proj = {}) const { - if (__first == __last) - return {__first, __first}; - + auto __comp_proj = __detail::__make_comp_proj(__comp, __proj); minmax_element_result<_Iter> __result = {__first, __first}; - auto __i = __first; - while (++__i != __last) + if (__first == __last) + return __result; + while (++__first != __last) { - if (std::__invoke(__comp, - std::__invoke(__proj, *__i), - std::__invoke(__proj, *__result.min))) - __result.min = __i; - if (!(bool)std::__invoke(__comp, - std::__invoke(__proj, *__i), - std::__invoke(__proj, *__result.max))) - __result.max = __i; + // Process two elements at a time so that we perform at most + // 3*N/2 many comparisons in total (each of the N/2 iterations + // of this loop performs three comparisions). + auto __prev = __first; + if (++__first == __last) + { + // N is odd; in this final iteration, we perform a just one + // comparison, for a total of 3*(N-1)/2 + 1 < 3*N/2 comparisons. + if (__comp_proj(*__prev, *__result.min)) + __result.min = __prev; + else if (!__comp_proj(*__prev, *__result.max)) + __result.max = __prev; + break; + } + if (!__comp_proj(*__first, *__prev)) + { + if (__comp_proj(*__prev, *__result.min)) + __result.min = __prev; + if (!__comp_proj(*__first, *__result.max)) + __result.max = __first; + } + else + { + if (__comp_proj(*__first, *__result.min)) + __result.min = __first; + if (!__comp_proj(*__prev, *__result.max)) + __result.max = __prev; + } } return __result; } @@ -3749,8 +3789,7 @@ namespace ranges // i.e. we are shifting out at least half of the range. In // this case we can safely perform the shift with a single // move. - std::move(std::move(__first), std::move(__dest_head), - std::move(__result)); + std::move(std::move(__first), std::move(__dest_head), __result); return __result; } ++__dest_head; diff --git a/libstdc++-v3/testsuite/25_algorithms/minmax/constrained.cc b/libstdc++-v3/testsuite/25_algorithms/minmax/constrained.cc index 786922414b5..2373c15a9d2 100644 --- a/libstdc++-v3/testsuite/25_algorithms/minmax/constrained.cc +++ b/libstdc++-v3/testsuite/25_algorithms/minmax/constrained.cc @@ -19,6 +19,8 @@ // { dg-do run { target c++2a } } #include <algorithm> +#include <string> +#include <vector> #include <testsuite_hooks.h> #include <testsuite_iterators.h> @@ -89,10 +91,39 @@ test03() == res_t(1,4) ); } +void +test04() +{ + // Verify we perform most 3*N/2 applications of the comparison predicate. + static int counter; + struct counted_less + { bool operator()(int a, int b) { ++counter; return a < b; } }; + + ranges::minmax({1,2,3,4,5,6,7,8,9,10}, counted_less{}); + VERIFY( counter <= 15 ); + + counter = 0; + ranges::minmax({10,9,8,7,6,5,4,3,2,1}, counted_less{}); + VERIFY( counter <= 15 ); +} + +void +test05() +{ + // PR libstdc++/100387 + std::vector<std::string> v{"b", "a"}; + auto [min, max] = ranges::minmax(v, [](const auto& a, const auto& b) { + return a.size() == b.size() ? a.front() < b.front() : a.size() > b.size(); + }); + VERIFY( min == "a" && max == "b" ); +} + int main() { test01(); test02(); test03(); + test04(); + test05(); } diff --git a/libstdc++-v3/testsuite/25_algorithms/minmax_element/constrained.cc b/libstdc++-v3/testsuite/25_algorithms/minmax_element/constrained.cc index 3b11c0dd96c..89044181530 100644 --- a/libstdc++-v3/testsuite/25_algorithms/minmax_element/constrained.cc +++ b/libstdc++-v3/testsuite/25_algorithms/minmax_element/constrained.cc @@ -61,8 +61,27 @@ test01() static_assert(ranges::minmax_element(y, y+3, {}, &X::i).max->j == 3); } +void +test02() +{ + // Verify we perform most 3*N/2 applications of the comparison predicate. + static int counter; + struct counted_less + { bool operator()(int a, int b) { ++counter; return a < b; } }; + + int x[] = {1,2,3,4,5,6,7,8,9,10}; + ranges::minmax(x, counted_less{}); + VERIFY( counter <= 15 ); + + ranges::reverse(x); + counter = 0; + ranges::minmax(x, counted_less{}); + VERIFY( counter <= 15 ); +} + int main() { test01(); + test02(); } -- 2.31.1.442.g7e39198978