Index: libcxx/include/__algorithm/ranges_minmax.h =================================================================== --- libcxx/include/__algorithm/ranges_minmax.h +++ libcxx/include/__algorithm/ranges_minmax.h @@ -77,6 +77,14 @@ if constexpr (forward_range<_Range>) { auto __result = std::__minmax_element_impl(__first, __last, __comp, __proj); + // We need to detect move_iterators (and similar types) to avoid dereferencing them more than once. + // The `if constexpr` is a pure optimization; we don't want an additional branch for normal iterators. + if constexpr (!is_lvalue_reference_v>) { + if (__result.first == __result.second) { + ranges::minmax_result<_ValueT> __found = {*__result.first, __found.min}; + return __found; + } + } return {*__result.first, *__result.second}; } else { // input_iterators can't be copied, so the implementation for input_iterators has to store Index: libcxx/test/std/algorithms/alg.sorting/alg.min.max/ranges.minmax.pass.cpp =================================================================== --- libcxx/test/std/algorithms/alg.sorting/alg.min.max/ranges.minmax.pass.cpp +++ libcxx/test/std/algorithms/alg.sorting/alg.min.max/ranges.minmax.pass.cpp @@ -334,12 +334,43 @@ return true; } +class input_move_iterator { +public: + using iterator_category = std::input_iterator_tag; + using iterator_concept = std::input_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = std::shared_ptr; + using pointer = std::shared_ptr*; + using reference = std::shared_ptr&&; + + input_move_iterator() = default; + explicit input_move_iterator(std::shared_ptr* ptr) : m_ptr(ptr) {} + + reference operator*() const { return std::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) { return a.m_ptr == b.m_ptr; } + +private: + std::shared_ptr* m_ptr{nullptr}; +}; + int main(int, char**) { test(); static_assert(test()); { - // check that the iterator isn't moved from multiple times + // check that the random access iterator isn't moved from multiple times std::shared_ptr a[] = { std::make_shared(42) }; auto range = std::ranges::subrange(std::move_iterator(a), std::move_iterator(a + 1)); auto [min, max] = std::ranges::minmax(range); @@ -347,6 +378,15 @@ assert(min != nullptr); assert(max == min); } + { + // check that the input iterator isn't moved from multiple times + std::shared_ptr a[] = {std::make_shared(42)}; + auto range = std::ranges::subrange(input_move_iterator(a), input_move_iterator(a + 1)); + auto [min, max] = std::ranges::minmax(range); + assert(a[0] == nullptr); + assert(min != nullptr); + assert(max == min); + } return 0; }