From 165931cfb5ea47c4bf9e09e0b9602dcd5ce364ee Mon Sep 17 00:00:00 2001 From: Mirion <51433180+mirion-dev@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:20:12 +0800 Subject: [PATCH 1/9] Fix excessive comparison in std::{ranges::}minmax_element --- stl/inc/algorithm | 62 ++++++++++++------- .../Dev11_0532622_minmax_element/test.cpp | 16 +++++ .../tests/P0896R4_ranges_alg_minmax/test.cpp | 21 +++++++ 3 files changed, 75 insertions(+), 24 deletions(-) diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 582f7d627cc..4fab531663d 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -11112,35 +11112,43 @@ constexpr pair<_FwdIt, _FwdIt> _Minmax_element_unchecked(_FwdIt _First, _FwdIt _ // find smallest and largest elements pair<_FwdIt, _FwdIt> _Found(_First, _First); - if (_First != _Last) { - while (++_First != _Last) { // process one or two elements - _FwdIt _Next = _First; - if (++_Next == _Last) { // process last element + if (_First == _Last || ++_First == _Last) { + return _Found; + } + + if (_DEBUG_LT_PRED(_Pred, *_First, *_Found.first)) { + _Found.first = _First; + } else { + _Found.second = _First; + } + + while (++_First != _Last) { // process one or two elements + _FwdIt _Next = _First; + if (++_Next == _Last) { // process last element + if (_DEBUG_LT_PRED(_Pred, *_First, *_Found.first)) { + _Found.first = _First; + } else if (!_DEBUG_LT_PRED(_Pred, *_First, *_Found.second)) { + _Found.second = _First; + } + } else { // process next two elements + if (_DEBUG_LT_PRED(_Pred, *_Next, *_First)) { // test _Next for new smallest + if (_DEBUG_LT_PRED(_Pred, *_Next, *_Found.first)) { + _Found.first = _Next; + } + + if (!_DEBUG_LT_PRED(_Pred, *_First, *_Found.second)) { + _Found.second = _First; + } + } else { // test _First for new smallest if (_DEBUG_LT_PRED(_Pred, *_First, *_Found.first)) { _Found.first = _First; - } else if (!_DEBUG_LT_PRED(_Pred, *_First, *_Found.second)) { - _Found.second = _First; } - } else { // process next two elements - if (_DEBUG_LT_PRED(_Pred, *_Next, *_First)) { // test _Next for new smallest - if (_DEBUG_LT_PRED(_Pred, *_Next, *_Found.first)) { - _Found.first = _Next; - } - - if (!_DEBUG_LT_PRED(_Pred, *_First, *_Found.second)) { - _Found.second = _First; - } - } else { // test _First for new smallest - if (_DEBUG_LT_PRED(_Pred, *_First, *_Found.first)) { - _Found.first = _First; - } - if (!_DEBUG_LT_PRED(_Pred, *_Next, *_Found.second)) { - _Found.second = _Next; - } + if (!_DEBUG_LT_PRED(_Pred, *_Next, *_Found.second)) { + _Found.second = _Next; } - _First = _Next; } + _First = _Next; } } @@ -11239,10 +11247,16 @@ namespace ranges { min_max_result<_It> _Found{_First, _First}; - if (_First == _Last) { + if (_First == _Last || ++_First == _Last) { return _Found; } + if (_STD invoke(_Pred, _STD invoke(_Proj, *_First), _STD invoke(_Proj, *_Found.min))) { + _Found.min = _First; + } else { + _Found.max = _First; + } + while (++_First != _Last) { // process one or two elements _It _Prev = _First; if (++_First == _Last) { // process last element diff --git a/tests/std/tests/Dev11_0532622_minmax_element/test.cpp b/tests/std/tests/Dev11_0532622_minmax_element/test.cpp index 91ccf599855..07842d87aaf 100644 --- a/tests/std/tests/Dev11_0532622_minmax_element/test.cpp +++ b/tests/std/tests/Dev11_0532622_minmax_element/test.cpp @@ -37,6 +37,15 @@ void test_all_permutations(vector& v) { } while (next_permutation(v.begin(), v.end())); } +void test_cmp_count(std::initializer_list v) { + size_t count = 0; + auto _ = minmax_element(v.begin(), v.end(), [&count](int left, int right) { + ++count; + return left < right; + }); + assert(count <= 3 * (v.size() - 1) / 2); +} + int main() { vector v; @@ -92,4 +101,11 @@ int main() { assert(max_element(begin(data), end(data)) == begin(data) + 3); assert(minmax_element(begin(data), end(data)) == make_pair(begin(data) + 9, begin(data) + 16)); } + +#if _ITERATOR_DEBUG_LEVEL < 2 // _DEBUG_LT_PRED affects comparison count + test_cmp_count({1, 2, 3}); + test_cmp_count({1, 2, 3, 4}); + test_cmp_count({3, 2, 1}); + test_cmp_count({4, 3, 2, 1}); +#endif } diff --git a/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp b/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp index dcead329c3c..ed0a8ed0b5f 100644 --- a/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp @@ -339,6 +339,22 @@ constexpr void mm_constexpr_tests() { ProxyRef::no>>(); } +constexpr void test_cmp_count(std::initializer_list v) { + size_t count = 0; + auto _ = ranges::minmax_element(v.begin(), v.end(), [&count](int left, int right) { + ++count; + return left < right; + }); + ASSERT(count <= 3 * (v.size() - 1) / 2); +} + +constexpr void cmp_count_tests() { + test_cmp_count({1, 2, 3}); + test_cmp_count({1, 2, 3, 4}); + test_cmp_count({3, 2, 1}); + test_cmp_count({4, 3, 2, 1}); +} + void test_gh_1893() { // GH-1893: ranges::clamp was sometimes performing too many projections, // and we should conform at least in release mode. @@ -437,6 +453,11 @@ int main() { static_assert((mm_constexpr_tests(), true)); test_in(); +#if _ITERATOR_DEBUG_LEVEL < 2 // _DEBUG_LT_PRED affects comparison count + static_assert((cmp_count_tests(), true)); + cmp_count_tests(); +#endif + test_gh_1893(); test_gh_2900(); } From 7e931a4509bde2f8f338d6acd1cb25254b1db757 Mon Sep 17 00:00:00 2001 From: Mirion <51433180+mirion-dev@users.noreply.github.com> Date: Tue, 28 Apr 2026 19:28:23 +0800 Subject: [PATCH 2/9] Optimize the number of comparisons in std::ranges::minmax --- stl/inc/algorithm | 8 ++- .../tests/P0896R4_ranges_alg_minmax/test.cpp | 71 +++++++++++++++++-- 2 files changed, 72 insertions(+), 7 deletions(-) diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 4fab531663d..f118a82aa17 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -11445,10 +11445,16 @@ namespace ranges { } else { // This initialization is correct, similar to the N4950 [dcl.init.aggr]/6 example minmax_result<_Vty> _Found = {static_cast<_Vty>(*_UFirst), _Found.min}; - if (_UFirst == _ULast) { + if (++_UFirst == _ULast) { return _Found; } + if (_STD invoke(_Pred, _STD invoke(_Proj, *_UFirst), _STD invoke(_Proj, _Found.min))) { + _Found.min = *_UFirst; + } else { + _Found.max = *_UFirst; + } + while (++_UFirst != _ULast) { // process one or two elements _Vty _Prev(*_UFirst); if (++_UFirst == _ULast) { // process last element diff --git a/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp b/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp index ed0a8ed0b5f..6dfcca3fb2a 100644 --- a/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp @@ -339,13 +339,72 @@ constexpr void mm_constexpr_tests() { ProxyRef::no>>(); } +template +struct InputRange { + T* ptr; + size_t size; + + struct iterator { + using value_type = T; + using difference_type = ptrdiff_t; + + T* ptr; + + constexpr T operator*() const { + return *ptr; + } + + constexpr iterator& operator++() { + ++ptr; + return *this; + } + + constexpr void operator++(int) { + ++*this; + } + + constexpr friend bool operator==(iterator left, iterator right) { + return left.ptr == right.ptr; + } + }; + + constexpr iterator begin() const { + return {ptr}; + } + + constexpr iterator end() const { + return {ptr + size}; + } +}; + constexpr void test_cmp_count(std::initializer_list v) { - size_t count = 0; - auto _ = ranges::minmax_element(v.begin(), v.end(), [&count](int left, int right) { - ++count; - return left < right; - }); - ASSERT(count <= 3 * (v.size() - 1) / 2); + { + size_t count = 0; + auto _ = ranges::minmax(v, [&count](int left, int right) { + ++count; + return left < right; + }); + ASSERT(count <= 3 * v.size() / 2); + } + + { + InputRange r = {v.data(), v.size()}; + size_t count = 0; + auto _ = ranges::minmax(r, [&count](int left, int right) { + ++count; + return left < right; + }); + ASSERT(count <= 3 * v.size() / 2); + } + + { + size_t count = 0; + auto _ = ranges::minmax_element(v, [&count](int left, int right) { + ++count; + return left < right; + }); + ASSERT(count <= 3 * (v.size() - 1) / 2); + } } constexpr void cmp_count_tests() { From c49a136d6a08bafacaeca8375df2a549c3490e31 Mon Sep 17 00:00:00 2001 From: Mirion <51433180+mirion-dev@users.noreply.github.com> Date: Tue, 28 Apr 2026 19:46:45 +0800 Subject: [PATCH 3/9] Remove redundant guard for ranges tests --- tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp b/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp index 6dfcca3fb2a..8cee836087d 100644 --- a/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp @@ -512,10 +512,8 @@ int main() { static_assert((mm_constexpr_tests(), true)); test_in(); -#if _ITERATOR_DEBUG_LEVEL < 2 // _DEBUG_LT_PRED affects comparison count static_assert((cmp_count_tests(), true)); cmp_count_tests(); -#endif test_gh_1893(); test_gh_2900(); From c9ee6cedd17f741dcd312d092ceabe132fe9389b Mon Sep 17 00:00:00 2001 From: Mirion <51433180+mirion-dev@users.noreply.github.com> Date: Wed, 29 Apr 2026 10:10:03 +0800 Subject: [PATCH 4/9] Remove redundant std:: --- tests/std/tests/Dev11_0532622_minmax_element/test.cpp | 2 +- tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/std/tests/Dev11_0532622_minmax_element/test.cpp b/tests/std/tests/Dev11_0532622_minmax_element/test.cpp index 07842d87aaf..1b5978c3726 100644 --- a/tests/std/tests/Dev11_0532622_minmax_element/test.cpp +++ b/tests/std/tests/Dev11_0532622_minmax_element/test.cpp @@ -37,7 +37,7 @@ void test_all_permutations(vector& v) { } while (next_permutation(v.begin(), v.end())); } -void test_cmp_count(std::initializer_list v) { +void test_cmp_count(initializer_list v) { size_t count = 0; auto _ = minmax_element(v.begin(), v.end(), [&count](int left, int right) { ++count; diff --git a/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp b/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp index 8cee836087d..468cdcff6b2 100644 --- a/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp @@ -377,7 +377,7 @@ struct InputRange { } }; -constexpr void test_cmp_count(std::initializer_list v) { +constexpr void test_cmp_count(initializer_list v) { { size_t count = 0; auto _ = ranges::minmax(v, [&count](int left, int right) { From 40bfbbf4c9010da5cbbcf2b292c634d120117c4b Mon Sep 17 00:00:00 2001 From: Mirion <51433180+mirion-dev@users.noreply.github.com> Date: Wed, 29 Apr 2026 10:11:30 +0800 Subject: [PATCH 5/9] Avoid shadowing --- .../Dev11_0532622_minmax_element/test.cpp | 8 +++--- .../tests/P0896R4_ranges_alg_minmax/test.cpp | 26 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/std/tests/Dev11_0532622_minmax_element/test.cpp b/tests/std/tests/Dev11_0532622_minmax_element/test.cpp index 1b5978c3726..6fbfd197ded 100644 --- a/tests/std/tests/Dev11_0532622_minmax_element/test.cpp +++ b/tests/std/tests/Dev11_0532622_minmax_element/test.cpp @@ -38,12 +38,12 @@ void test_all_permutations(vector& v) { } void test_cmp_count(initializer_list v) { - size_t count = 0; - auto _ = minmax_element(v.begin(), v.end(), [&count](int left, int right) { - ++count; + size_t cmp_count = 0; + (void) minmax_element(v.begin(), v.end(), [&cmp_count](int left, int right) { + ++cmp_count; return left < right; }); - assert(count <= 3 * (v.size() - 1) / 2); + assert(cmp_count <= 3 * (v.size() - 1) / 2); } int main() { diff --git a/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp b/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp index 468cdcff6b2..0d681640f8e 100644 --- a/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp @@ -379,31 +379,31 @@ struct InputRange { constexpr void test_cmp_count(initializer_list v) { { - size_t count = 0; - auto _ = ranges::minmax(v, [&count](int left, int right) { - ++count; + size_t cmp_count = 0; + (void) ranges::minmax(v, [&cmp_count](int left, int right) { + ++cmp_count; return left < right; }); - ASSERT(count <= 3 * v.size() / 2); + ASSERT(cmp_count <= 3 * v.size() / 2); } { - InputRange r = {v.data(), v.size()}; - size_t count = 0; - auto _ = ranges::minmax(r, [&count](int left, int right) { - ++count; + InputRange r = {v.data(), v.size()}; + size_t cmp_count = 0; + (void) ranges::minmax(r, [&cmp_count](int left, int right) { + ++cmp_count; return left < right; }); - ASSERT(count <= 3 * v.size() / 2); + ASSERT(cmp_count <= 3 * v.size() / 2); } { - size_t count = 0; - auto _ = ranges::minmax_element(v, [&count](int left, int right) { - ++count; + size_t cmp_count = 0; + (void) ranges::minmax_element(v, [&cmp_count](int left, int right) { + ++cmp_count; return left < right; }); - ASSERT(count <= 3 * (v.size() - 1) / 2); + ASSERT(cmp_count <= 3 * (v.size() - 1) / 2); } } From d9cd1b9b73519ed795c22f2114f4377d4c5f3116 Mon Sep 17 00:00:00 2001 From: Mirion <51433180+mirion-dev@users.noreply.github.com> Date: Wed, 29 Apr 2026 10:11:59 +0800 Subject: [PATCH 6/9] Add comment to #endif --- tests/std/tests/Dev11_0532622_minmax_element/test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/std/tests/Dev11_0532622_minmax_element/test.cpp b/tests/std/tests/Dev11_0532622_minmax_element/test.cpp index 6fbfd197ded..8bdb7d4e5f5 100644 --- a/tests/std/tests/Dev11_0532622_minmax_element/test.cpp +++ b/tests/std/tests/Dev11_0532622_minmax_element/test.cpp @@ -107,5 +107,5 @@ int main() { test_cmp_count({1, 2, 3, 4}); test_cmp_count({3, 2, 1}); test_cmp_count({4, 3, 2, 1}); -#endif +#endif // _ITERATOR_DEBUG_LEVEL < 2 } From 57f566e4b7c282f262cc12de8422ebf274f9f888 Mon Sep 17 00:00:00 2001 From: Mirion <51433180+mirion-dev@users.noreply.github.com> Date: Wed, 29 Apr 2026 10:30:46 +0800 Subject: [PATCH 7/9] Add citations --- tests/std/tests/Dev11_0532622_minmax_element/test.cpp | 3 +++ tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/tests/std/tests/Dev11_0532622_minmax_element/test.cpp b/tests/std/tests/Dev11_0532622_minmax_element/test.cpp index 8bdb7d4e5f5..f1dfe591bfe 100644 --- a/tests/std/tests/Dev11_0532622_minmax_element/test.cpp +++ b/tests/std/tests/Dev11_0532622_minmax_element/test.cpp @@ -37,6 +37,9 @@ void test_all_permutations(vector& v) { } while (next_permutation(v.begin(), v.end())); } +// N5032 [alg.min.max]/32: +// Complexity: Let N be last - first. At most max(floor(3/2 (N − 1)), 0) comparisons +// and twice as many applications of the projection, if any. void test_cmp_count(initializer_list v) { size_t cmp_count = 0; (void) minmax_element(v.begin(), v.end(), [&cmp_count](int left, int right) { diff --git a/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp b/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp index 0d681640f8e..89e9f8e3f4d 100644 --- a/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp @@ -377,6 +377,14 @@ struct InputRange { } }; +// For minmax: +// N5032 [alg.min.max]/23: +// Complexity: At most (3/2) ranges::distance(r) applications of the corresponding predicate +// and twice as many applications of the projection, if any. +// For minmax_element: +// N5032 [alg.min.max]/32: +// Complexity: Let N be last - first. At most max(floor(3/2 (N − 1)), 0) comparisons +// and twice as many applications of the projection, if any. constexpr void test_cmp_count(initializer_list v) { { size_t cmp_count = 0; From 57798ec73ce6ef56b97c49b4a9637bd527f1de82 Mon Sep 17 00:00:00 2001 From: Mirion <51433180+mirion-dev@users.noreply.github.com> Date: Wed, 29 Apr 2026 10:44:18 +0800 Subject: [PATCH 8/9] Replace non-ASCII minuses --- tests/std/tests/Dev11_0532622_minmax_element/test.cpp | 2 +- tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/std/tests/Dev11_0532622_minmax_element/test.cpp b/tests/std/tests/Dev11_0532622_minmax_element/test.cpp index f1dfe591bfe..715d7979eee 100644 --- a/tests/std/tests/Dev11_0532622_minmax_element/test.cpp +++ b/tests/std/tests/Dev11_0532622_minmax_element/test.cpp @@ -38,7 +38,7 @@ void test_all_permutations(vector& v) { } // N5032 [alg.min.max]/32: -// Complexity: Let N be last - first. At most max(floor(3/2 (N − 1)), 0) comparisons +// Complexity: Let N be last - first. At most max(floor(3/2 (N - 1)), 0) comparisons // and twice as many applications of the projection, if any. void test_cmp_count(initializer_list v) { size_t cmp_count = 0; diff --git a/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp b/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp index 89e9f8e3f4d..a888f1a9edc 100644 --- a/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp @@ -383,7 +383,7 @@ struct InputRange { // and twice as many applications of the projection, if any. // For minmax_element: // N5032 [alg.min.max]/32: -// Complexity: Let N be last - first. At most max(floor(3/2 (N − 1)), 0) comparisons +// Complexity: Let N be last - first. At most max(floor(3/2 (N - 1)), 0) comparisons // and twice as many applications of the projection, if any. constexpr void test_cmp_count(initializer_list v) { { From 3cf6762e39ce75507c2c86ded3f23b014d385151 Mon Sep 17 00:00:00 2001 From: Mirion <51433180+mirion-dev@users.noreply.github.com> Date: Wed, 29 Apr 2026 11:09:27 +0800 Subject: [PATCH 9/9] Mention GH-1006 in the comment --- tests/std/tests/Dev11_0532622_minmax_element/test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/std/tests/Dev11_0532622_minmax_element/test.cpp b/tests/std/tests/Dev11_0532622_minmax_element/test.cpp index 715d7979eee..123f5de9dc8 100644 --- a/tests/std/tests/Dev11_0532622_minmax_element/test.cpp +++ b/tests/std/tests/Dev11_0532622_minmax_element/test.cpp @@ -105,7 +105,7 @@ int main() { assert(minmax_element(begin(data), end(data)) == make_pair(begin(data) + 9, begin(data) + 16)); } -#if _ITERATOR_DEBUG_LEVEL < 2 // _DEBUG_LT_PRED affects comparison count +#if _ITERATOR_DEBUG_LEVEL < 2 // TRANSITION, GH-1006 test_cmp_count({1, 2, 3}); test_cmp_count({1, 2, 3, 4}); test_cmp_count({3, 2, 1});