-
Notifications
You must be signed in to change notification settings - Fork 12.4k
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
[libc++] Fix uninitialized algorithms when using unconstrained comparison operators #69373
[libc++] Fix uninitialized algorithms when using unconstrained comparison operators #69373
Conversation
…ison operators If an iterator passed to std::uninitialized_copy & friends provided an unconstrained comparison operator, we would trigger an ambiguous overload resolution because we used to compare against __unreachable_sentinel in our implementation. This patch fixes that by only comparing the output iterator when it is actually required, i.e. in the <ranges> versions of the algorithms. Fixes llvm#69334
@llvm/pr-subscribers-libcxx Author: Louis Dionne (ldionne) ChangesIf an iterator passed to std::uninitialized_copy & friends provided an unconstrained comparison operator, we would trigger an ambiguous overload resolution because we used to compare against __unreachable_sentinel in our implementation. This patch fixes that by only comparing the output iterator when it is actually required, i.e. in the <ranges> versions of the algorithms. Fixes #69334 Patch is 28.28 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/69373.diff 11 Files Affected:
diff --git a/libcxx/include/__memory/ranges_uninitialized_algorithms.h b/libcxx/include/__memory/ranges_uninitialized_algorithms.h
index 01c3e01003d410e..4d08c1888c1eccf 100644
--- a/libcxx/include/__memory/ranges_uninitialized_algorithms.h
+++ b/libcxx/include/__memory/ranges_uninitialized_algorithms.h
@@ -196,8 +196,9 @@ struct __fn {
operator()(_InputIterator __ifirst, _Sentinel1 __ilast, _OutputIterator __ofirst, _Sentinel2 __olast) const {
using _ValueType = remove_reference_t<iter_reference_t<_OutputIterator>>;
- auto __result = _VSTD::__uninitialized_copy<_ValueType>(_VSTD::move(__ifirst), _VSTD::move(__ilast),
- _VSTD::move(__ofirst), _VSTD::move(__olast));
+ auto __stop = [&__olast](auto&& __out_iter) { return __out_iter == __olast; };
+ auto __result =
+ std::__uninitialized_copy<_ValueType>(std::move(__ifirst), std::move(__ilast), std::move(__ofirst), __stop);
return {_VSTD::move(__result.first), _VSTD::move(__result.second)};
}
@@ -232,8 +233,8 @@ struct __fn {
operator()(_InputIterator __ifirst, iter_difference_t<_InputIterator> __n,
_OutputIterator __ofirst, _Sentinel __olast) const {
using _ValueType = remove_reference_t<iter_reference_t<_OutputIterator>>;
- auto __result = _VSTD::__uninitialized_copy_n<_ValueType>(_VSTD::move(__ifirst), __n,
- _VSTD::move(__ofirst), _VSTD::move(__olast));
+ auto __stop = [&__olast](auto&& __out_iter) { return __out_iter == __olast; };
+ auto __result = std::__uninitialized_copy_n<_ValueType>(std::move(__ifirst), __n, std::move(__ofirst), __stop);
return {_VSTD::move(__result.first), _VSTD::move(__result.second)};
}
};
@@ -261,8 +262,9 @@ struct __fn {
operator()(_InputIterator __ifirst, _Sentinel1 __ilast, _OutputIterator __ofirst, _Sentinel2 __olast) const {
using _ValueType = remove_reference_t<iter_reference_t<_OutputIterator>>;
auto __iter_move = [](auto&& __iter) -> decltype(auto) { return ranges::iter_move(__iter); };
- auto __result = _VSTD::__uninitialized_move<_ValueType>(_VSTD::move(__ifirst), _VSTD::move(__ilast),
- _VSTD::move(__ofirst), _VSTD::move(__olast), __iter_move);
+ auto __stop = [&__olast](auto&& __out_iter) { return __out_iter == __olast; };
+ auto __result = std::__uninitialized_move<_ValueType>(
+ std::move(__ifirst), std::move(__ilast), std::move(__ofirst), __stop, __iter_move);
return {_VSTD::move(__result.first), _VSTD::move(__result.second)};
}
@@ -298,8 +300,9 @@ struct __fn {
_OutputIterator __ofirst, _Sentinel __olast) const {
using _ValueType = remove_reference_t<iter_reference_t<_OutputIterator>>;
auto __iter_move = [](auto&& __iter) -> decltype(auto) { return ranges::iter_move(__iter); };
- auto __result = _VSTD::__uninitialized_move_n<_ValueType>(_VSTD::move(__ifirst), __n,
- _VSTD::move(__ofirst), _VSTD::move(__olast), __iter_move);
+ auto __stop = [&__olast](auto&& __out_iter) { return __out_iter == __olast; };
+ auto __result =
+ std::__uninitialized_move_n<_ValueType>(std::move(__ifirst), __n, std::move(__ofirst), __stop, __iter_move);
return {_VSTD::move(__result.first), _VSTD::move(__result.second)};
}
};
diff --git a/libcxx/include/__memory/uninitialized_algorithms.h b/libcxx/include/__memory/uninitialized_algorithms.h
index 2b68df8e6d634f3..0e660daeefe23da 100644
--- a/libcxx/include/__memory/uninitialized_algorithms.h
+++ b/libcxx/include/__memory/uninitialized_algorithms.h
@@ -44,26 +44,23 @@
_LIBCPP_BEGIN_NAMESPACE_STD
-// This is a simplified version of C++20 `unreachable_sentinel` that doesn't use concepts and thus can be used in any
-// language mode.
-struct __unreachable_sentinel {
- template <class _Iter>
- _LIBCPP_HIDE_FROM_ABI friend _LIBCPP_CONSTEXPR bool operator!=(const _Iter&, __unreachable_sentinel) _NOEXCEPT {
- return true;
+struct __always_false {
+ template <class... _Args>
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR bool operator()(_Args&&...) const _NOEXCEPT {
+ return false;
}
};
// uninitialized_copy
-template <class _ValueType, class _InputIterator, class _Sentinel1, class _ForwardIterator, class _Sentinel2>
-inline _LIBCPP_HIDE_FROM_ABI pair<_InputIterator, _ForwardIterator>
-__uninitialized_copy(_InputIterator __ifirst, _Sentinel1 __ilast,
- _ForwardIterator __ofirst, _Sentinel2 __olast) {
+template <class _ValueType, class _InputIterator, class _Sentinel1, class _ForwardIterator, class _OutputIterCompare>
+inline _LIBCPP_HIDE_FROM_ABI pair<_InputIterator, _ForwardIterator> __uninitialized_copy(
+ _InputIterator __ifirst, _Sentinel1 __ilast, _ForwardIterator __ofirst, _OutputIterCompare __out_iter_at_end) {
_ForwardIterator __idx = __ofirst;
#ifndef _LIBCPP_HAS_NO_EXCEPTIONS
try {
#endif
- for (; __ifirst != __ilast && __idx != __olast; ++__ifirst, (void)++__idx)
+ for (; __ifirst != __ilast && !__out_iter_at_end(__idx); ++__ifirst, (void)++__idx)
::new (_VSTD::__voidify(*__idx)) _ValueType(*__ifirst);
#ifndef _LIBCPP_HAS_NO_EXCEPTIONS
} catch (...) {
@@ -80,22 +77,21 @@ _LIBCPP_HIDE_FROM_ABI
_ForwardIterator uninitialized_copy(_InputIterator __ifirst, _InputIterator __ilast,
_ForwardIterator __ofirst) {
typedef typename iterator_traits<_ForwardIterator>::value_type _ValueType;
- auto __result = _VSTD::__uninitialized_copy<_ValueType>(_VSTD::move(__ifirst), _VSTD::move(__ilast),
- _VSTD::move(__ofirst), __unreachable_sentinel());
+ auto __result = std::__uninitialized_copy<_ValueType>(
+ std::move(__ifirst), std::move(__ilast), std::move(__ofirst), __always_false());
return _VSTD::move(__result.second);
}
// uninitialized_copy_n
-template <class _ValueType, class _InputIterator, class _Size, class _ForwardIterator, class _Sentinel>
-inline _LIBCPP_HIDE_FROM_ABI pair<_InputIterator, _ForwardIterator>
-__uninitialized_copy_n(_InputIterator __ifirst, _Size __n,
- _ForwardIterator __ofirst, _Sentinel __olast) {
+template <class _ValueType, class _InputIterator, class _Size, class _ForwardIterator, class _OutputIterCompare>
+inline _LIBCPP_HIDE_FROM_ABI pair<_InputIterator, _ForwardIterator> __uninitialized_copy_n(
+ _InputIterator __ifirst, _Size __n, _ForwardIterator __ofirst, _OutputIterCompare __out_iter_at_end) {
_ForwardIterator __idx = __ofirst;
#ifndef _LIBCPP_HAS_NO_EXCEPTIONS
try {
#endif
- for (; __n > 0 && __idx != __olast; ++__ifirst, (void)++__idx, (void)--__n)
+ for (; __n > 0 && !__out_iter_at_end(__idx); ++__ifirst, (void)++__idx, (void)--__n)
::new (_VSTD::__voidify(*__idx)) _ValueType(*__ifirst);
#ifndef _LIBCPP_HAS_NO_EXCEPTIONS
} catch (...) {
@@ -111,8 +107,8 @@ template <class _InputIterator, class _Size, class _ForwardIterator>
inline _LIBCPP_HIDE_FROM_ABI _ForwardIterator uninitialized_copy_n(_InputIterator __ifirst, _Size __n,
_ForwardIterator __ofirst) {
typedef typename iterator_traits<_ForwardIterator>::value_type _ValueType;
- auto __result = _VSTD::__uninitialized_copy_n<_ValueType>(_VSTD::move(__ifirst), __n, _VSTD::move(__ofirst),
- __unreachable_sentinel());
+ auto __result =
+ std::__uninitialized_copy_n<_ValueType>(std::move(__ifirst), __n, std::move(__ofirst), __always_false());
return _VSTD::move(__result.second);
}
@@ -300,16 +296,23 @@ _ForwardIterator uninitialized_value_construct_n(_ForwardIterator __first, _Size
// uninitialized_move
-template <class _ValueType, class _InputIterator, class _Sentinel1, class _ForwardIterator, class _Sentinel2,
+template <class _ValueType,
+ class _InputIterator,
+ class _Sentinel1,
+ class _ForwardIterator,
+ class _OutputIterCompare,
class _IterMove>
-inline _LIBCPP_HIDE_FROM_ABI pair<_InputIterator, _ForwardIterator>
-__uninitialized_move(_InputIterator __ifirst, _Sentinel1 __ilast,
- _ForwardIterator __ofirst, _Sentinel2 __olast, _IterMove __iter_move) {
+inline _LIBCPP_HIDE_FROM_ABI pair<_InputIterator, _ForwardIterator> __uninitialized_move(
+ _InputIterator __ifirst,
+ _Sentinel1 __ilast,
+ _ForwardIterator __ofirst,
+ _OutputIterCompare __out_iter_at_end,
+ _IterMove __iter_move) {
auto __idx = __ofirst;
#ifndef _LIBCPP_HAS_NO_EXCEPTIONS
try {
#endif
- for (; __ifirst != __ilast && __idx != __olast; ++__idx, (void)++__ifirst) {
+ for (; __ifirst != __ilast && !__out_iter_at_end(__idx); ++__idx, (void)++__ifirst) {
::new (_VSTD::__voidify(*__idx)) _ValueType(__iter_move(__ifirst));
}
#ifndef _LIBCPP_HAS_NO_EXCEPTIONS
@@ -328,22 +331,30 @@ inline _LIBCPP_HIDE_FROM_ABI _ForwardIterator uninitialized_move(_InputIterator
using _ValueType = typename iterator_traits<_ForwardIterator>::value_type;
auto __iter_move = [](auto&& __iter) -> decltype(auto) { return _VSTD::move(*__iter); };
- auto __result = _VSTD::__uninitialized_move<_ValueType>(_VSTD::move(__ifirst), _VSTD::move(__ilast),
- _VSTD::move(__ofirst), __unreachable_sentinel(), __iter_move);
+ auto __result = std::__uninitialized_move<_ValueType>(
+ std::move(__ifirst), std::move(__ilast), std::move(__ofirst), __always_false(), __iter_move);
return _VSTD::move(__result.second);
}
// uninitialized_move_n
-template <class _ValueType, class _InputIterator, class _Size, class _ForwardIterator, class _Sentinel, class _IterMove>
-inline _LIBCPP_HIDE_FROM_ABI pair<_InputIterator, _ForwardIterator>
-__uninitialized_move_n(_InputIterator __ifirst, _Size __n,
- _ForwardIterator __ofirst, _Sentinel __olast, _IterMove __iter_move) {
+template <class _ValueType,
+ class _InputIterator,
+ class _Size,
+ class _ForwardIterator,
+ class _OutputIterCompare,
+ class _IterMove>
+inline _LIBCPP_HIDE_FROM_ABI pair<_InputIterator, _ForwardIterator> __uninitialized_move_n(
+ _InputIterator __ifirst,
+ _Size __n,
+ _ForwardIterator __ofirst,
+ _OutputIterCompare __out_iter_at_end,
+ _IterMove __iter_move) {
auto __idx = __ofirst;
#ifndef _LIBCPP_HAS_NO_EXCEPTIONS
try {
#endif
- for (; __n > 0 && __idx != __olast; ++__idx, (void)++__ifirst, --__n)
+ for (; __n > 0 && !__out_iter_at_end(__idx); ++__idx, (void)++__ifirst, --__n)
::new (_VSTD::__voidify(*__idx)) _ValueType(__iter_move(__ifirst));
#ifndef _LIBCPP_HAS_NO_EXCEPTIONS
} catch (...) {
@@ -361,8 +372,8 @@ uninitialized_move_n(_InputIterator __ifirst, _Size __n, _ForwardIterator __ofir
using _ValueType = typename iterator_traits<_ForwardIterator>::value_type;
auto __iter_move = [](auto&& __iter) -> decltype(auto) { return _VSTD::move(*__iter); };
- return _VSTD::__uninitialized_move_n<_ValueType>(_VSTD::move(__ifirst), __n, _VSTD::move(__ofirst),
- __unreachable_sentinel(), __iter_move);
+ return std::__uninitialized_move_n<_ValueType>(
+ std::move(__ifirst), __n, std::move(__ofirst), __always_false(), __iter_move);
}
// TODO: Rewrite this to iterate left to right and use reverse_iterators when calling
diff --git a/libcxx/test/std/utilities/memory/specialized.algorithms/overload_compare_iterator.h b/libcxx/test/std/utilities/memory/specialized.algorithms/overload_compare_iterator.h
new file mode 100644
index 000000000000000..d251f806c3fc70d
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/overload_compare_iterator.h
@@ -0,0 +1,71 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LIBCPP_TEST_STD_UTILITIES_MEMORY_SPECIALIZED_ALGORITHMS_OVERLOAD_COMPARE_ITERATOR_H
+#define LIBCPP_TEST_STD_UTILITIES_MEMORY_SPECIALIZED_ALGORITHMS_OVERLOAD_COMPARE_ITERATOR_H
+
+#include <iterator>
+#include <memory>
+
+// An iterator type that overloads operator== and operator!= without any constraints, which
+// can trip up some algorithms if we compare iterators against types that we're not allowed to.
+//
+// See https://github.com/llvm/llvm-project/issues/69334 for details.
+template <class Iterator>
+struct overload_compare_iterator {
+ using value_type = typename std::iterator_traits<Iterator>::value_type;
+ using difference_type = typename std::iterator_traits<Iterator>::difference_type;
+ using reference = typename std::iterator_traits<Iterator>::reference;
+ using pointer = typename std::iterator_traits<Iterator>::pointer;
+ using iterator_category = typename std::iterator_traits<Iterator>::iterator_category;
+
+ overload_compare_iterator() = default;
+
+ explicit overload_compare_iterator(Iterator it) : it_(it) {}
+
+ overload_compare_iterator(overload_compare_iterator const&) = default;
+ overload_compare_iterator(overload_compare_iterator&&) = default;
+ overload_compare_iterator& operator=(overload_compare_iterator const&) = default;
+ overload_compare_iterator& operator=(overload_compare_iterator&&) = default;
+
+ constexpr reference operator*() const noexcept { return *it_; }
+
+ constexpr pointer operator->() const noexcept { return std::addressof(*it_); }
+
+ constexpr overload_compare_iterator& operator++() noexcept {
+ ++it_;
+ return *this;
+ }
+
+ constexpr overload_compare_iterator operator++(int) const noexcept {
+ overload_compare_iterator old{*this};
+ ++(*this);
+ return old;
+ }
+
+ constexpr bool operator==(overload_compare_iterator const& other) const noexcept { return this->it_ == other.it_; }
+
+ constexpr bool operator!=(overload_compare_iterator const& other) const noexcept { return !this->operator==(other); }
+
+ // Hostile overloads
+ template <class Sentinel>
+ friend bool operator==(overload_compare_iterator const& lhs, Sentinel const& rhs) noexcept {
+ return static_cast<Iterator const&>(lhs) == rhs;
+ }
+
+ template <class Sentinel>
+ friend bool operator!=(overload_compare_iterator const& lhs, Sentinel const& rhs) noexcept {
+ return static_cast<Iterator const&>(lhs) != rhs;
+ }
+
+private:
+ Iterator it_;
+};
+
+#endif // LIBCPP_TEST_STD_UTILITIES_MEMORY_SPECIALIZED_ALGORITHMS_OVERLOAD_COMPARE_ITERATOR_H
diff --git a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.copy/ranges_uninitialized_copy.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.copy/ranges_uninitialized_copy.pass.cpp
index fb3ae6f4ae96b80..92dc380728e2425 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.copy/ranges_uninitialized_copy.pass.cpp
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.copy/ranges_uninitialized_copy.pass.cpp
@@ -27,6 +27,7 @@
#include "../buffer.h"
#include "../counted.h"
+#include "../overload_compare_iterator.h"
#include "test_macros.h"
#include "test_iterators.h"
@@ -396,5 +397,36 @@ int main(int, char**) {
}
}
+ // Test with an iterator that overloads operator== and operator!= as the input and output iterators
+ {
+ using T = int;
+ using Iterator = overload_compare_iterator<T*>;
+ const int N = 5;
+
+ // input
+ {
+ char pool[sizeof(T) * N] = {0};
+ T* p = reinterpret_cast<T*>(pool);
+ T* p_end = reinterpret_cast<T*>(pool) + N;
+ T array[N] = {1, 2, 3, 4, 5};
+ std::ranges::uninitialized_copy(Iterator(array), Iterator(array + N), p, p_end);
+ for (int i = 0; i != N; ++i) {
+ assert(array[i] == p[i]);
+ }
+ }
+
+ // output
+ {
+ char pool[sizeof(T) * N] = {0};
+ T* p = reinterpret_cast<T*>(pool);
+ T* p_end = reinterpret_cast<T*>(pool) + N;
+ T array[N] = {1, 2, 3, 4, 5};
+ std::ranges::uninitialized_copy(array, array + N, Iterator(p), Iterator(p_end));
+ for (int i = 0; i != N; ++i) {
+ assert(array[i] == p[i]);
+ }
+ }
+ }
+
return 0;
}
diff --git a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.copy/ranges_uninitialized_copy_n.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.copy/ranges_uninitialized_copy_n.pass.cpp
index 097f88dec0022c9..80082eb3b98e6f9 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.copy/ranges_uninitialized_copy_n.pass.cpp
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.copy/ranges_uninitialized_copy_n.pass.cpp
@@ -24,6 +24,7 @@
#include "../buffer.h"
#include "../counted.h"
+#include "../overload_compare_iterator.h"
#include "test_macros.h"
#include "test_iterators.h"
@@ -161,5 +162,36 @@ int main(int, char**) {
std::ranges::uninitialized_copy_n(std::move(in), N, out.begin(), out.end());
}
+ // Test with an iterator that overloads operator== and operator!= as the input and output iterators
+ {
+ using T = int;
+ using Iterator = overload_compare_iterator<T*>;
+ const int N = 5;
+
+ // input
+ {
+ char pool[sizeof(T) * N] = {0};
+ T* p = reinterpret_cast<T*>(pool);
+ T* p_end = reinterpret_cast<T*>(pool) + N;
+ T array[N] = {1, 2, 3, 4, 5};
+ std::ranges::uninitialized_copy_n(Iterator(array), N, p, p_end);
+ for (int i = 0; i != N; ++i) {
+ assert(array[i] == p[i]);
+ }
+ }
+
+ // output
+ {
+ char pool[sizeof(T) * N] = {0};
+ T* p = reinterpret_cast<T*>(pool);
+ T* p_end = reinterpret_cast<T*>(pool) + N;
+ T array[N] = {1, 2, 3, 4, 5};
+ std::ranges::uninitialized_copy_n(array, N, Iterator(p), Iterator(p_end));
+ for (int i = 0; i != N; ++i) {
+ assert(array[i] == p[i]);
+ }
+ }
+ }
+
return 0;
}
diff --git a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.copy/uninitialized_copy.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.copy/uninitialized_copy.pass.cpp
index c3b6e1809007efb..dddc550e0ef120b 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.copy/uninitialized_copy.pass.cpp
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.copy/uninitialized_copy.pass.cpp
@@ -17,6 +17,7 @@
#include <cassert>
#include "test_macros.h"
+#include "../overload_compare_iterator.h"
struct B
{
@@ -85,6 +86,34 @@ int main(int, char**)
}
}
+ // Test with an iterator that overloads operator== and operator!= as the input and output iterators
+ {
+ using T = int;
+ using Iterator = overload_compare_iterator<T*>;
+ const int N = 5;
+
+ // input
+ {
+ char pool[sizeof(T) * N] = {0};
+ T* p = reinterpret_cast<T*>(pool);
+ T array[N] = {1, 2, 3, 4, 5};
+ std::uninitialized_copy(Iterator(array), Iterator(array + N), p);
+ for (int i = 0; i != N; ++i) {
+ assert(array[i] == p[i]);
+ }
+ }
+
+ // output
+ {
+ char pool[sizeof(T) * N] = {0};
+ T* p = reinterpret_cast<T*>(pool);
+ T array[N] = {1, 2, 3, 4, 5};
+ std::uninitialized_copy(array, array + N, Iterator(p));
+ for (int i = 0; i != N; ++i) {
+ assert(array[i] == p[i]);
+ }
+ }
+ }
return 0;
}
diff --git a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.copy/uninitialized_copy_n.pass.cpp b/libcxx/test/std/utilities/me...
[truncated]
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the quick patch!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for fixing, It looks fine now!
If an iterator passed to std::uninitialized_copy & friends provided an unconstrained comparison operator, we would trigger an ambiguous overload resolution because we used to compare against __unreachable_sentinel in our implementation.
This patch fixes that by only comparing the output iterator when it is actually required, i.e. in the versions of the algorithms.
Fixes #69334