Index: libcxx/include/__algorithm/ranges_minmax.h =================================================================== --- libcxx/include/__algorithm/ranges_minmax.h +++ libcxx/include/__algorithm/ranges_minmax.h @@ -13,6 +13,7 @@ #include <__algorithm/minmax_element.h> #include <__assert> #include <__concepts/copyable.h> +#include <__concepts/same_as.h> #include <__config> #include <__functional/identity.h> #include <__functional/invoke.h> @@ -76,8 +77,20 @@ _LIBCPP_ASSERT(__first != __last, "range has to contain at least one element"); if constexpr (forward_range<_Range>) { + // Special-case the one element case. Avoid repeatedly initializing objects from the result of an iterator + // dereference when doing so might not be idempotent. The `if constexpr` avoids the extra branch in cases where + // it's not needed. + if constexpr (!same_as>, _ValueT> || + is_rvalue_reference_v>) { + auto __test_for_one_element = __first; + if (++__test_for_one_element == __last) { + // This initialization is correct, similar to the N4928 [dcl.init.aggr]/6 example + minmax_result<_ValueT> __result = {static_cast<_ValueT>(*__result.min), __result.min}; + return __result; + } + } auto __result = std::__minmax_element_impl(__first, __last, __comp, __proj); - return {*__result.first, *__result.second}; + return {static_cast<_ValueT>(*__result.first), static_cast<_ValueT>(*__result.second)}; } else { // input_iterators can't be copied, so the implementation for input_iterators has to store // the values instead of a pointer to the correct values @@ -86,7 +99,8 @@ std::invoke(__proj, std::forward(__b))); }; - ranges::minmax_result<_ValueT> __result = {*__first, __result.min}; + // This initialization is correct, similar to the N4928 [dcl.init.aggr]/6 example + ranges::minmax_result<_ValueT> __result = {static_cast<_ValueT>(*__first), __result.min}; if (__first == __last || ++__first == __last) return __result; 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 @@ -339,7 +339,7 @@ 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 +347,16 @@ 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( + cpp20_input_iterator(std::move_iterator(a)), sentinel_wrapper(cpp20_input_iterator(std::move_iterator(a + 1)))); + auto [min, max] = std::ranges::minmax(range); + assert(a[0] == nullptr); + assert(min != nullptr); + assert(max == min); + } return 0; }