diff --git a/libcxx/include/__algorithm/ranges_minmax.h b/libcxx/include/__algorithm/ranges_minmax.h --- a/libcxx/include/__algorithm/ranges_minmax.h +++ b/libcxx/include/__algorithm/ranges_minmax.h @@ -13,11 +13,13 @@ #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> #include <__functional/ranges_operations.h> #include <__iterator/concepts.h> +#include <__iterator/next.h> #include <__iterator/projected.h> #include <__ranges/access.h> #include <__ranges/concepts.h> @@ -76,6 +78,18 @@ _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>) { + if (ranges::next(__first) == __last) { + // During initialization, members are allowed to refer to already initialized members + // (see http://eel.is/c++draft/dcl.init.aggr#6) + minmax_result<_ValueT> __result = {*__first, __result.min}; + return __result; + } + } auto __result = std::__minmax_element_impl(__first, __last, __comp, __proj); return {*__result.first, *__result.second}; } else { @@ -86,6 +100,8 @@ std::invoke(__proj, std::forward(__b))); }; + // During initialization, members are allowed to refer to already initialized members + // (see http://eel.is/c++draft/dcl.init.aggr#6) ranges::minmax_result<_ValueT> __result = {*__first, __result.min}; if (__first == __last || ++__first == __last) return __result; diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.min.max/ranges.minmax.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.min.max/ranges.minmax.pass.cpp --- a/libcxx/test/std/algorithms/alg.sorting/alg.min.max/ranges.minmax.pass.cpp +++ b/libcxx/test/std/algorithms/alg.sorting/alg.min.max/ranges.minmax.pass.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include "test_iterators.h" @@ -324,6 +325,26 @@ assert(ret.min == 1); assert(ret.max == 4); } + { + // check that the input iterator isn't moved from multiple times + const std::string str{"this long string will be dynamically allocated"}; + std::string a[] = {str}; + auto range = std::ranges::subrange( + cpp20_input_iterator(std::move_iterator(a)), sentinel_wrapper(cpp20_input_iterator(std::move_iterator(a + 1)))); + auto ret = std::ranges::minmax(range); + assert(ret.min == str); + assert(ret.max == str); + } + { + // check that the forward iterator isn't moved from multiple times + const std::string str{"this long string will be dynamically allocated"}; + std::string a[] = {str}; + auto range = + std::ranges::subrange(forward_iterator(std::move_iterator(a)), forward_iterator(std::move_iterator(a + 1))); + auto ret = std::ranges::minmax(range); + assert(ret.min == str); + assert(ret.max == str); + } } constexpr bool test() { @@ -338,15 +359,5 @@ test(); static_assert(test()); - { - // check that the 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); - assert(a[0] == nullptr); - assert(min != nullptr); - assert(max == min); - } - return 0; }