[libcxx-commits] [libcxx] [libc++] Fix uninitialized algorithms when using unconstrained comparison operators (PR #69373)

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Tue Oct 17 14:12:06 PDT 2023


https://github.com/ldionne updated https://github.com/llvm/llvm-project/pull/69373

>From 43a8d8f9d7e4e16b16b290a68b2e3d56d87db3bc Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Tue, 17 Oct 2023 12:21:00 -0700
Subject: [PATCH 1/2] [libc++] Fix uninitialized algorithms when using
 unconstrained comparison 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 #69334
---
 .../ranges_uninitialized_algorithms.h         | 19 +++--
 .../__memory/uninitialized_algorithms.h       | 79 +++++++++++--------
 .../overload_compare_iterator.h               | 71 +++++++++++++++++
 .../ranges_uninitialized_copy.pass.cpp        | 32 ++++++++
 .../ranges_uninitialized_copy_n.pass.cpp      | 32 ++++++++
 .../uninitialized_copy.pass.cpp               | 29 +++++++
 .../uninitialized_copy_n.pass.cpp             | 30 +++++++
 .../ranges_uninitialized_move.pass.cpp        | 32 ++++++++
 .../ranges_uninitialized_move_n.pass.cpp      | 32 ++++++++
 .../uninitialized_move.pass.cpp               | 30 +++++++
 .../uninitialized_move_n.pass.cpp             | 30 +++++++
 11 files changed, 374 insertions(+), 42 deletions(-)
 create mode 100644 libcxx/test/std/utilities/memory/specialized.algorithms/overload_compare_iterator.h

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/memory/specialized.algorithms/uninitialized.copy/uninitialized_copy_n.pass.cpp
index 9d0fae3a8e0335d..ddaf02c184bd4d3 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.copy/uninitialized_copy_n.pass.cpp
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.copy/uninitialized_copy_n.pass.cpp
@@ -17,6 +17,7 @@
 #include <cassert>
 
 #include "test_macros.h"
+#include "../overload_compare_iterator.h"
 
 struct B
 {
@@ -85,5 +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_n(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_n(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.move/ranges_uninitialized_move.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/ranges_uninitialized_move.pass.cpp
index 57be8c49ca3b007..56dd25c66e19944 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/ranges_uninitialized_move.pass.cpp
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/ranges_uninitialized_move.pass.cpp
@@ -27,6 +27,7 @@
 
 #include "../buffer.h"
 #include "../counted.h"
+#include "../overload_compare_iterator.h"
 #include "MoveOnly.h"
 #include "test_macros.h"
 #include "test_iterators.h"
@@ -435,5 +436,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_move(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_move(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.move/ranges_uninitialized_move_n.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/ranges_uninitialized_move_n.pass.cpp
index df79987920f4da8..162b4a48537ff34 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/ranges_uninitialized_move_n.pass.cpp
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/ranges_uninitialized_move_n.pass.cpp
@@ -24,6 +24,7 @@
 
 #include "../buffer.h"
 #include "../counted.h"
+#include "../overload_compare_iterator.h"
 #include "MoveOnly.h"
 #include "test_iterators.h"
 #include "test_macros.h"
@@ -191,5 +192,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_move_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_move_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.move/uninitialized_move.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/uninitialized_move.pass.cpp
index 625d30f64bd7132..f77cbea19bd4d68 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/uninitialized_move.pass.cpp
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/uninitialized_move.pass.cpp
@@ -19,6 +19,7 @@
 
 #include "test_macros.h"
 #include "test_iterators.h"
+#include "../overload_compare_iterator.h"
 
 struct Counted {
   static int count;
@@ -111,5 +112,34 @@ int main(int, char**) {
     test_counted();
     test_ctor_throws();
 
+    // 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_move(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_move(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.move/uninitialized_move_n.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/uninitialized_move_n.pass.cpp
index 7dcdcf0c6324b84..7cdfb4da08e4da5 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/uninitialized_move_n.pass.cpp
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/uninitialized_move_n.pass.cpp
@@ -19,6 +19,7 @@
 
 #include "test_macros.h"
 #include "test_iterators.h"
+#include "../overload_compare_iterator.h"
 
 struct Counted {
   static int count;
@@ -114,5 +115,34 @@ int main(int, char**)
     test_counted();
     test_ctor_throws();
 
+    // 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_move_n(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_move_n(array, N, Iterator(p));
+            for (int i = 0; i != N; ++i) {
+                assert(array[i] == p[i]);
+            }
+        }
+    }
+
   return 0;
 }

>From 71b526360442212c725de7d0e5568335e0079e59 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Tue, 17 Oct 2023 14:11:51 -0700
Subject: [PATCH 2/2] Apply review comments and fix C++03/C++11 builds

---
 .../ranges_uninitialized_algorithms.h         | 23 +++++++--------
 .../__memory/uninitialized_algorithms.h       | 28 ++++++++-----------
 .../overload_compare_iterator.h               | 20 +++++++------
 3 files changed, 35 insertions(+), 36 deletions(-)

diff --git a/libcxx/include/__memory/ranges_uninitialized_algorithms.h b/libcxx/include/__memory/ranges_uninitialized_algorithms.h
index 4d08c1888c1eccf..108f6537538413b 100644
--- a/libcxx/include/__memory/ranges_uninitialized_algorithms.h
+++ b/libcxx/include/__memory/ranges_uninitialized_algorithms.h
@@ -196,9 +196,9 @@ struct __fn {
   operator()(_InputIterator __ifirst, _Sentinel1 __ilast, _OutputIterator __ofirst, _Sentinel2 __olast) const {
     using _ValueType = remove_reference_t<iter_reference_t<_OutputIterator>>;
 
-    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);
+    auto __stop_copying = [&__olast](auto&& __out_iter) { return __out_iter == __olast; };
+    auto __result       = std::__uninitialized_copy<_ValueType>(
+        std::move(__ifirst), std::move(__ilast), std::move(__ofirst), __stop_copying);
     return {_VSTD::move(__result.first), _VSTD::move(__result.second)};
   }
 
@@ -233,8 +233,9 @@ 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 __stop      = [&__olast](auto&& __out_iter) { return __out_iter == __olast; };
-    auto __result    = std::__uninitialized_copy_n<_ValueType>(std::move(__ifirst), __n, std::move(__ofirst), __stop);
+    auto __stop_copying = [&__olast](auto&& __out_iter) { return __out_iter == __olast; };
+    auto __result =
+        std::__uninitialized_copy_n<_ValueType>(std::move(__ifirst), __n, std::move(__ofirst), __stop_copying);
     return {_VSTD::move(__result.first), _VSTD::move(__result.second)};
   }
 };
@@ -262,9 +263,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 __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);
+    auto __stop_moving = [&__olast](auto&& __out_iter) { return __out_iter == __olast; };
+    auto __result      = std::__uninitialized_move<_ValueType>(
+        std::move(__ifirst), std::move(__ilast), std::move(__ofirst), __stop_moving, __iter_move);
     return {_VSTD::move(__result.first), _VSTD::move(__result.second)};
   }
 
@@ -300,9 +301,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 __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);
+    auto __stop_moving = [&__olast](auto&& __out_iter) { return __out_iter == __olast; };
+    auto __result      = std::__uninitialized_move_n<_ValueType>(
+        std::move(__ifirst), __n, std::move(__ofirst), __stop_moving, __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 0e660daeefe23da..af0d83c97cf58db 100644
--- a/libcxx/include/__memory/uninitialized_algorithms.h
+++ b/libcxx/include/__memory/uninitialized_algorithms.h
@@ -53,14 +53,14 @@ struct __always_false {
 
 // uninitialized_copy
 
-template <class _ValueType, class _InputIterator, class _Sentinel1, class _ForwardIterator, class _OutputIterCompare>
+template <class _ValueType, class _InputIterator, class _Sentinel1, class _ForwardIterator, class _EndPredicate>
 inline _LIBCPP_HIDE_FROM_ABI pair<_InputIterator, _ForwardIterator> __uninitialized_copy(
-    _InputIterator __ifirst, _Sentinel1 __ilast, _ForwardIterator __ofirst, _OutputIterCompare __out_iter_at_end) {
+    _InputIterator __ifirst, _Sentinel1 __ilast, _ForwardIterator __ofirst, _EndPredicate __stop_copying) {
   _ForwardIterator __idx = __ofirst;
 #ifndef _LIBCPP_HAS_NO_EXCEPTIONS
   try {
 #endif
-    for (; __ifirst != __ilast && !__out_iter_at_end(__idx); ++__ifirst, (void)++__idx)
+    for (; __ifirst != __ilast && !__stop_copying(__idx); ++__ifirst, (void)++__idx)
       ::new (_VSTD::__voidify(*__idx)) _ValueType(*__ifirst);
 #ifndef _LIBCPP_HAS_NO_EXCEPTIONS
   } catch (...) {
@@ -84,14 +84,14 @@ _ForwardIterator uninitialized_copy(_InputIterator __ifirst, _InputIterator __il
 
 // uninitialized_copy_n
 
-template <class _ValueType, class _InputIterator, class _Size, class _ForwardIterator, class _OutputIterCompare>
+template <class _ValueType, class _InputIterator, class _Size, class _ForwardIterator, class _EndPredicate>
 inline _LIBCPP_HIDE_FROM_ABI pair<_InputIterator, _ForwardIterator> __uninitialized_copy_n(
-    _InputIterator __ifirst, _Size __n, _ForwardIterator __ofirst, _OutputIterCompare __out_iter_at_end) {
+    _InputIterator __ifirst, _Size __n, _ForwardIterator __ofirst, _EndPredicate __stop_copying) {
   _ForwardIterator __idx = __ofirst;
 #ifndef _LIBCPP_HAS_NO_EXCEPTIONS
   try {
 #endif
-    for (; __n > 0 && !__out_iter_at_end(__idx); ++__ifirst, (void)++__idx, (void)--__n)
+    for (; __n > 0 && !__stop_copying(__idx); ++__ifirst, (void)++__idx, (void)--__n)
       ::new (_VSTD::__voidify(*__idx)) _ValueType(*__ifirst);
 #ifndef _LIBCPP_HAS_NO_EXCEPTIONS
   } catch (...) {
@@ -300,19 +300,19 @@ template <class _ValueType,
           class _InputIterator,
           class _Sentinel1,
           class _ForwardIterator,
-          class _OutputIterCompare,
+          class _EndPredicate,
           class _IterMove>
 inline _LIBCPP_HIDE_FROM_ABI pair<_InputIterator, _ForwardIterator> __uninitialized_move(
     _InputIterator __ifirst,
     _Sentinel1 __ilast,
     _ForwardIterator __ofirst,
-    _OutputIterCompare __out_iter_at_end,
+    _EndPredicate __stop_moving,
     _IterMove __iter_move) {
   auto __idx = __ofirst;
 #ifndef _LIBCPP_HAS_NO_EXCEPTIONS
   try {
 #endif
-    for (; __ifirst != __ilast && !__out_iter_at_end(__idx); ++__idx, (void)++__ifirst) {
+    for (; __ifirst != __ilast && !__stop_moving(__idx); ++__idx, (void)++__ifirst) {
       ::new (_VSTD::__voidify(*__idx)) _ValueType(__iter_move(__ifirst));
     }
 #ifndef _LIBCPP_HAS_NO_EXCEPTIONS
@@ -342,19 +342,15 @@ template <class _ValueType,
           class _InputIterator,
           class _Size,
           class _ForwardIterator,
-          class _OutputIterCompare,
+          class _EndPredicate,
           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) {
+    _InputIterator __ifirst, _Size __n, _ForwardIterator __ofirst, _EndPredicate __stop_moving, _IterMove __iter_move) {
   auto __idx = __ofirst;
 #ifndef _LIBCPP_HAS_NO_EXCEPTIONS
   try {
 #endif
-    for (; __n > 0 && !__out_iter_at_end(__idx); ++__idx, (void)++__ifirst, --__n)
+    for (; __n > 0 && !__stop_moving(__idx); ++__idx, (void)++__ifirst, --__n)
       ::new (_VSTD::__voidify(*__idx)) _ValueType(__iter_move(__ifirst));
 #ifndef _LIBCPP_HAS_NO_EXCEPTIONS
   } catch (...) {
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
index d251f806c3fc70d..d5dcb08c37ed6f5 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/overload_compare_iterator.h
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/overload_compare_iterator.h
@@ -13,6 +13,8 @@
 #include <iterator>
 #include <memory>
 
+#include "test_macros.h"
+
 // 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.
 //
@@ -34,33 +36,33 @@ struct overload_compare_iterator {
   overload_compare_iterator& operator=(overload_compare_iterator const&) = default;
   overload_compare_iterator& operator=(overload_compare_iterator&&)      = default;
 
-  constexpr reference operator*() const noexcept { return *it_; }
+  reference operator*() const TEST_NOEXCEPT { return *it_; }
 
-  constexpr pointer operator->() const noexcept { return std::addressof(*it_); }
+  pointer operator->() const TEST_NOEXCEPT { return std::addressof(*it_); }
 
-  constexpr overload_compare_iterator& operator++() noexcept {
+  overload_compare_iterator& operator++() TEST_NOEXCEPT {
     ++it_;
     return *this;
   }
 
-  constexpr overload_compare_iterator operator++(int) const noexcept {
-    overload_compare_iterator old{*this};
+  overload_compare_iterator operator++(int) const TEST_NOEXCEPT {
+    overload_compare_iterator old(*this);
     ++(*this);
     return old;
   }
 
-  constexpr bool operator==(overload_compare_iterator const& other) const noexcept { return this->it_ == other.it_; }
+  bool operator==(overload_compare_iterator const& other) const TEST_NOEXCEPT { return this->it_ == other.it_; }
 
-  constexpr bool operator!=(overload_compare_iterator const& other) const noexcept { return !this->operator==(other); }
+  bool operator!=(overload_compare_iterator const& other) const TEST_NOEXCEPT { return !this->operator==(other); }
 
   // Hostile overloads
   template <class Sentinel>
-  friend bool operator==(overload_compare_iterator const& lhs, Sentinel const& rhs) noexcept {
+  friend bool operator==(overload_compare_iterator const& lhs, Sentinel const& rhs) TEST_NOEXCEPT {
     return static_cast<Iterator const&>(lhs) == rhs;
   }
 
   template <class Sentinel>
-  friend bool operator!=(overload_compare_iterator const& lhs, Sentinel const& rhs) noexcept {
+  friend bool operator!=(overload_compare_iterator const& lhs, Sentinel const& rhs) TEST_NOEXCEPT {
     return static_cast<Iterator const&>(lhs) != rhs;
   }
 



More information about the libcxx-commits mailing list