Skip to content
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

<algorithm>: ranges::minmax should dereference iterators only once #3366

Merged
merged 17 commits into from
Feb 3, 2023
Merged
12 changes: 11 additions & 1 deletion stl/inc/algorithm
Original file line number Diff line number Diff line change
Expand Up @@ -10102,9 +10102,19 @@ namespace ranges {
if constexpr (forward_range<_Rng> && _Prefer_iterator_copies<iterator_t<_Rng>>) {
const auto _Found = _RANGES _Minmax_element_unchecked(
_STD move(_UFirst), _STD move(_ULast), _Pass_fn(_Pred), _Pass_fn(_Proj));
// We can dereference normal pointers many times but some iterators could move their value after
// dereferencing so we should dereference them only once. The `if constexpr` is a pure optimization;
// we don't want an additional branch for normal pointers.
if constexpr (!is_pointer_v<iterator_t<_Rng>>) {
fsb4000 marked this conversation as resolved.
Show resolved Hide resolved
if (_Found.min == _Found.max) {
fsb4000 marked this conversation as resolved.
Show resolved Hide resolved
auto _Temp = static_cast<_Vty>(*_Found.min);
return {_Temp, _STD move(_Temp)};
}
}
return {static_cast<_Vty>(*_Found.min), static_cast<_Vty>(*_Found.max)};
} else {
minmax_result<_Vty> _Found = {static_cast<_Vty>(*_UFirst), static_cast<_Vty>(*_UFirst)};
auto _Temp = static_cast<_Vty>(*_UFirst);
minmax_result<_Vty> _Found = {_Temp, _STD move(_Temp)};
if (_UFirst == _ULast) {
return _Found;
}
Expand Down
1 change: 0 additions & 1 deletion tests/libcxx/expected_results.txt
Original file line number Diff line number Diff line change
Expand Up @@ -918,7 +918,6 @@ std/algorithms/alg.sorting/alg.heap.operations/push.heap/ranges_push_heap.pass.c
std/algorithms/alg.sorting/alg.heap.operations/sort.heap/ranges_sort_heap.pass.cpp FAIL
std/algorithms/alg.sorting/alg.merge/ranges_inplace_merge.pass.cpp FAIL
std/algorithms/alg.sorting/alg.merge/ranges_merge.pass.cpp FAIL
std/algorithms/alg.sorting/alg.min.max/ranges.minmax.pass.cpp FAIL
std/algorithms/alg.sorting/alg.min.max/requires_forward_iterator.fail.cpp FAIL
std/algorithms/alg.sorting/alg.nth.element/ranges_nth_element.pass.cpp FAIL
std/algorithms/alg.sorting/alg.partitions/ranges.is_partitioned.pass.cpp:0 FAIL
Expand Down
1 change: 0 additions & 1 deletion tests/libcxx/skipped_tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -918,7 +918,6 @@ algorithms\alg.sorting\alg.heap.operations\push.heap\ranges_push_heap.pass.cpp
algorithms\alg.sorting\alg.heap.operations\sort.heap\ranges_sort_heap.pass.cpp
algorithms\alg.sorting\alg.merge\ranges_inplace_merge.pass.cpp
algorithms\alg.sorting\alg.merge\ranges_merge.pass.cpp
algorithms\alg.sorting\alg.min.max\ranges.minmax.pass.cpp
algorithms\alg.sorting\alg.min.max\requires_forward_iterator.fail.cpp
algorithms\alg.sorting\alg.nth.element\ranges_nth_element.pass.cpp
algorithms\alg.sorting\alg.partitions\ranges.is_partitioned.pass.cpp
Expand Down
63 changes: 63 additions & 0 deletions tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
#include <cassert>
#include <concepts>
#include <functional>
#include <memory>
#include <ranges>
#include <span>
#include <string>
#include <utility>
#include <vector>

#include <range_algorithm_support.hpp>

Expand Down Expand Up @@ -361,6 +363,66 @@ void test_gh_1893() {
ASSERT(val == "meow");
}

class input_move_iterator {
public:
using iterator_category = input_iterator_tag;
using iterator_concept = input_iterator_tag;
using difference_type = ptrdiff_t;
using value_type = shared_ptr<int>;
using pointer = shared_ptr<int>*;
using reference = shared_ptr<int>&&;

input_move_iterator() = default;
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
explicit input_move_iterator(shared_ptr<int>* ptr) : m_ptr(ptr) {}

reference operator*() const {
return ranges::iter_move(m_ptr);
}
pointer operator->() const {
return m_ptr;
}

input_move_iterator& operator++() {
++m_ptr;
return *this;
}
input_move_iterator operator++(int) {
input_move_iterator tmp = *this;
++*this;
return tmp;
}

friend bool operator==(const input_move_iterator& a, const input_move_iterator& b) {
fsb4000 marked this conversation as resolved.
Show resolved Hide resolved
return a.m_ptr == b.m_ptr;
}

private:
shared_ptr<int>* m_ptr{nullptr};
};

void test_gh_2900() {
// GH-2900: <algorithm>: ranges::minmax initializes minmax_result with the moved value
{
// check that the random access iterator isn't moved from multiple times
const string str{"this long string will be dynamically allocated"};
vector<string> v{str};
ranges::subrange rng{move_iterator{v.begin()}, move_iterator{v.end()}};
auto result = ranges::minmax(rng);
assert(result.min == str);
assert(result.max == str);
}
{
// check that the input iterator isn't moved from multiple times
shared_ptr<int> a[] = {make_shared<int>(42)};
ranges::subrange rng{input_move_iterator{a}, input_move_iterator{a + 1}};
auto result = ranges::minmax(rng);
assert(a[0] == nullptr);
assert(result.min != nullptr);
assert(result.max == result.min);
assert(*result.max == 42);
}
}

int main() {
STATIC_ASSERT((nonrange_tests(), true));
nonrange_tests();
Expand All @@ -378,4 +440,5 @@ int main() {
test_in<mm, const P>();

test_gh_1893();
test_gh_2900();
}