[libcxx-commits] [libcxx] 8d23b74 - [libc++][ranges] Implement `uninitialized_copy{, _n}` and `uninitialized_move{, _n}`.

Konstantin Varlamov via libcxx-commits libcxx-commits at lists.llvm.org
Mon Jan 10 22:50:28 PST 2022


Author: Konstantin Varlamov
Date: 2022-01-10T22:49:50-08:00
New Revision: 8d23b7420c92ddf8c3e5da39a90a1982fc72c231

URL: https://github.com/llvm/llvm-project/commit/8d23b7420c92ddf8c3e5da39a90a1982fc72c231
DIFF: https://github.com/llvm/llvm-project/commit/8d23b7420c92ddf8c3e5da39a90a1982fc72c231.diff

LOG: [libc++][ranges] Implement `uninitialized_copy{,_n}` and `uninitialized_move{,_n}`.

Also implement `in_out_result` which is a prerequisite.

Differential Revision: https://reviews.llvm.org/D116023

Added: 
    libcxx/include/__algorithm/in_out_result.h
    libcxx/test/libcxx/diagnostics/detail.headers/algorithm/in_out_result.module.verify.cpp
    libcxx/test/std/algorithms/algorithms.results/in_out_result.compile.pass.cpp
    libcxx/test/std/algorithms/algorithms.results/in_out_result.pass.cpp
    libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.copy/ranges_uninitialized_copy.pass.cpp
    libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.copy/ranges_uninitialized_copy_n.pass.cpp
    libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/ranges_uninitialized_move.pass.cpp
    libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/ranges_uninitialized_move_n.pass.cpp

Modified: 
    libcxx/docs/Status/RangesAlgorithms.csv
    libcxx/docs/Status/RangesPaper.csv
    libcxx/include/CMakeLists.txt
    libcxx/include/__memory/ranges_uninitialized_algorithms.h
    libcxx/include/__memory/uninitialized_algorithms.h
    libcxx/include/algorithm
    libcxx/include/memory
    libcxx/include/module.modulemap
    libcxx/test/std/utilities/memory/specialized.algorithms/counted.h
    libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.construct.default/ranges_uninitialized_default_construct.pass.cpp
    libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.construct.default/ranges_uninitialized_default_construct_n.pass.cpp
    libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.construct.default/uninitialized_default_construct_n.pass.cpp
    libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.construct.value/ranges_uninitialized_value_construct.pass.cpp
    libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.construct.value/ranges_uninitialized_value_construct_n.pass.cpp
    libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.fill.n/ranges_uninitialized_fill_n.pass.cpp
    libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.fill/ranges_uninitialized_fill.pass.cpp

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/Status/RangesAlgorithms.csv b/libcxx/docs/Status/RangesAlgorithms.csv
index 0d8beb37984c1..2636fca2d838d 100644
--- a/libcxx/docs/Status/RangesAlgorithms.csv
+++ b/libcxx/docs/Status/RangesAlgorithms.csv
@@ -84,12 +84,12 @@ Permutation,pop_heap,Not assigned,n/a,Not started
 Permutation,sort_heap,Not assigned,n/a,Not started
 Permutation,prev_permutation,Not assigned,n/a,Not started
 Permutation,next_permutation,Not assigned,n/a,Not started
-Uninitialised memory,uninitialized_copy,Konstantin Varlamov,n/a,Not started
-Uninitialised memory,uninitialized_copy_n,Konstantin Varlamov,n/a,Not started
+Uninitialised memory,uninitialized_copy,Konstantin Varlamov,`D116023 <https://llvm.org/D116023>`_,✅
+Uninitialised memory,uninitialized_copy_n,Konstantin Varlamov,`D116023 <https://llvm.org/D116023>`_,✅
 Uninitialised memory,uninitialized_fill,Konstantin Varlamov,`D115626 <https://llvm.org/D115626>`_,✅
 Uninitialised memory,uninitialized_fill_n,Konstantin Varlamov,`D115626 <https://llvm.org/D115626>`_,✅
-Uninitialised memory,uninitialized_move,Konstantin Varlamov,n/a,Not started
-Uninitialised memory,uninitialized_move_n,Konstantin Varlamov,n/a,Not started
+Uninitialised memory,uninitialized_move,Konstantin Varlamov,`D116023 <https://llvm.org/D116023>`_,✅
+Uninitialised memory,uninitialized_move_n,Konstantin Varlamov,`D116023 <https://llvm.org/D116023>`_,✅
 Uninitialised memory,uninitialized_default_construct,Konstantin Varlamov,`D115315 <https://llvm.org/D115315>`_,✅
 Uninitialised memory,uninitialized_default_construct_n,Konstantin Varlamov,`D115315 <https://llvm.org/D115315>`_,✅
 Uninitialised memory,uninitialized_value_construct,Konstantin Varlamov,`D115626 <https://llvm.org/D115626>`_,✅

diff  --git a/libcxx/docs/Status/RangesPaper.csv b/libcxx/docs/Status/RangesPaper.csv
index 2c972f27e0047..ef6aacc0ac523 100644
--- a/libcxx/docs/Status/RangesPaper.csv
+++ b/libcxx/docs/Status/RangesPaper.csv
@@ -22,10 +22,10 @@ Section,Description,Dependencies,Assignee,Complete
 | `ranges::uninitialized_default_construct_n <https://llvm.org/D115315>`_
 | `ranges::uninitialized_value_construct <https://llvm.org/D115626>`_
 | `ranges::uninitialized_value_construct_n <https://llvm.org/D115626>`_
-| ranges::uninitialized_copy
-| ranges::uninitialized_copy_n
-| ranges::uninitialized_move
-| ranges::uninitialized_move_n
+| `ranges::uninitialized_copy <https://llvm.org/D116023>`_
+| `ranges::uninitialized_copy_n <https://llvm.org/D116023>`_
+| `ranges::uninitialized_move <https://llvm.org/D116023>`_
+| `ranges::uninitialized_move_n <https://llvm.org/D116023>`_
 | `ranges::uninitialized_fill <https://llvm.org/D115626>`_
 | `ranges::uninitialized_fill_n <https://llvm.org/D115626>`_
 | ranges::construct_at

diff  --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index de7b124e3f76a..7efe0383d80b6 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -26,6 +26,7 @@ set(files
   __algorithm/generate.h
   __algorithm/generate_n.h
   __algorithm/half_positive.h
+  __algorithm/in_out_result.h
   __algorithm/includes.h
   __algorithm/inplace_merge.h
   __algorithm/is_heap.h

diff  --git a/libcxx/include/__algorithm/in_out_result.h b/libcxx/include/__algorithm/in_out_result.h
new file mode 100644
index 0000000000000..9d971157200fa
--- /dev/null
+++ b/libcxx/include/__algorithm/in_out_result.h
@@ -0,0 +1,52 @@
+// -*- 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___ALGORITHM_IN_OUT_RESULT_H
+#define _LIBCPP___ALGORITHM_IN_OUT_RESULT_H
+
+#include <__concepts/convertible_to.h>
+#include <__config>
+#include <__utility/move.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if !defined(_LIBCPP_HAS_NO_RANGES)
+namespace ranges {
+
+template<class _InputIterator, class _OutputIterator>
+struct in_out_result {
+  [[no_unique_address]] _InputIterator in;
+  [[no_unique_address]] _OutputIterator out;
+
+  template <class _InputIterator2, class _OutputIterator2>
+    requires convertible_to<const _InputIterator&, _InputIterator2> && convertible_to<const _OutputIterator&,
+                           _OutputIterator2>
+  _LIBCPP_HIDE_FROM_ABI
+  constexpr operator in_out_result<_InputIterator2, _OutputIterator2>() const & {
+    return {in, out};
+  }
+
+  template <class _InputIterator2, class _OutputIterator2>
+    requires convertible_to<_InputIterator, _InputIterator2> && convertible_to<_OutputIterator, _OutputIterator2>
+  _LIBCPP_HIDE_FROM_ABI
+  constexpr operator in_out_result<_InputIterator2, _OutputIterator2>() && {
+    return {_VSTD::move(in), _VSTD::move(out)};
+  }
+};
+
+} // namespace ranges
+#endif // !defined(_LIBCPP_HAS_NO_RANGES)
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___ALGORITHM_IN_OUT_RESULT_H

diff  --git a/libcxx/include/__memory/ranges_uninitialized_algorithms.h b/libcxx/include/__memory/ranges_uninitialized_algorithms.h
index 6ec803806c05a..09786ffc69731 100644
--- a/libcxx/include/__memory/ranges_uninitialized_algorithms.h
+++ b/libcxx/include/__memory/ranges_uninitialized_algorithms.h
@@ -10,10 +10,13 @@
 #ifndef _LIBCPP___MEMORY_RANGES_UNINITIALIZED_ALGORITHMS_H
 #define _LIBCPP___MEMORY_RANGES_UNINITIALIZED_ALGORITHMS_H
 
+#include <__algorithm/in_out_result.h>
 #include <__concepts/constructible.h>
 #include <__config>
 #include <__function_like.h>
+#include <__iterator/concepts.h>
 #include <__iterator/incrementable_traits.h>
+#include <__iterator/iter_move.h>
 #include <__iterator/iterator_traits.h>
 #include <__iterator/readable_traits.h>
 #include <__memory/concepts.h>
@@ -61,8 +64,8 @@ struct __fn final : private __function_like {
 } // namespace __uninitialized_default_construct
 
 inline namespace __cpo {
-inline constexpr auto uninitialized_default_construct =
-    __uninitialized_default_construct::__fn(__function_like::__tag());
+  inline constexpr auto uninitialized_default_construct =
+      __uninitialized_default_construct::__fn(__function_like::__tag());
 } // namespace __cpo
 
 // uninitialized_default_construct_n
@@ -87,8 +90,8 @@ struct __fn final : private __function_like {
 } // namespace __uninitialized_default_construct_n
 
 inline namespace __cpo {
-inline constexpr auto uninitialized_default_construct_n =
-    __uninitialized_default_construct_n::__fn(__function_like::__tag());
+  inline constexpr auto uninitialized_default_construct_n =
+      __uninitialized_default_construct_n::__fn(__function_like::__tag());
 } // namespace __cpo
 
 // uninitialized_value_construct
@@ -119,8 +122,8 @@ struct __fn final : private __function_like {
 } // namespace __uninitialized_value_construct
 
 inline namespace __cpo {
-inline constexpr auto uninitialized_value_construct =
-    __uninitialized_value_construct::__fn(__function_like::__tag());
+  inline constexpr auto uninitialized_value_construct =
+      __uninitialized_value_construct::__fn(__function_like::__tag());
 } // namespace __cpo
 
 // uninitialized_value_construct_n
@@ -144,8 +147,8 @@ struct __fn final : private __function_like {
 } // namespace __uninitialized_value_construct_n
 
 inline namespace __cpo {
-inline constexpr auto uninitialized_value_construct_n =
-    __uninitialized_value_construct_n::__fn(__function_like::__tag());
+  inline constexpr auto uninitialized_value_construct_n =
+      __uninitialized_value_construct_n::__fn(__function_like::__tag());
 } // namespace __cpo
 
 // uninitialized_fill
@@ -173,10 +176,10 @@ struct __fn final : private __function_like {
 
 };
 
-} // namespace __uninitialized_fil
+} // namespace __uninitialized_fill
 
 inline namespace __cpo {
-inline constexpr auto uninitialized_fill = __uninitialized_fill::__fn(__function_like::__tag());
+  inline constexpr auto uninitialized_fill = __uninitialized_fill::__fn(__function_like::__tag());
 } // namespace __cpo
 
 // uninitialized_fill_n
@@ -201,7 +204,168 @@ struct __fn final : private __function_like {
 } // namespace __uninitialized_fill_n
 
 inline namespace __cpo {
-inline constexpr auto uninitialized_fill_n = __uninitialized_fill_n::__fn(__function_like::__tag());
+  inline constexpr auto uninitialized_fill_n = __uninitialized_fill_n::__fn(__function_like::__tag());
+} // namespace __cpo
+
+// uninitialized_copy
+
+template <class _InputIterator, class _OutputIterator>
+using uninitialized_copy_result = in_out_result<_InputIterator, _OutputIterator>;
+
+namespace __uninitialized_copy {
+
+struct __fn final : private __function_like {
+
+  constexpr explicit __fn(__tag __x) noexcept : __function_like(__x) {}
+
+  // clang-format off
+  template <input_iterator _InputIterator,
+            sentinel_for<_InputIterator> _Sentinel1,
+            __nothrow_forward_iterator _OutputIterator,
+            __nothrow_sentinel_for<_OutputIterator> _Sentinel2>
+    requires constructible_from<iter_value_t<_OutputIterator>, iter_reference_t<_InputIterator>>
+  uninitialized_copy_result<_InputIterator, _OutputIterator>
+  operator()(_InputIterator __ifirst, _Sentinel1 __ilast, _OutputIterator __ofirst, _Sentinel2 __olast) const {
+    // clang-format on
+    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));
+    return {_VSTD::move(__result.first), _VSTD::move(__result.second)};
+  }
+
+  // clang-format off
+  template <input_range _InputRange, __nothrow_forward_range _OutputRange>
+    requires constructible_from<range_value_t<_OutputRange>, range_reference_t<_InputRange>>
+  uninitialized_copy_result<borrowed_iterator_t<_InputRange>, borrowed_iterator_t<_OutputRange>>
+  operator()( _InputRange&& __in_range, _OutputRange&& __out_range) const {
+    // clang-format on
+    return (*this)(ranges::begin(__in_range), ranges::end(__in_range), ranges::begin(__out_range),
+                   ranges::end(__out_range));
+  }
+};
+
+} // namespace __uninitialized_copy
+
+inline namespace __cpo {
+  inline constexpr auto uninitialized_copy = __uninitialized_copy::__fn(__function_like::__tag());
+} // namespace __cpo
+
+// uninitialized_copy_n
+
+template <class _InputIterator, class _OutputIterator>
+using uninitialized_copy_n_result = in_out_result<_InputIterator, _OutputIterator>;
+
+namespace __uninitialized_copy_n {
+
+struct __fn final : private __function_like {
+
+  constexpr explicit __fn(__tag __x) noexcept : __function_like(__x) {}
+
+  // clang-format off
+  template <input_iterator _InputIterator,
+           __nothrow_forward_iterator _OutputIterator,
+           __nothrow_sentinel_for<_OutputIterator> _Sentinel>
+    requires constructible_from<iter_value_t<_OutputIterator>, iter_reference_t<_InputIterator>>
+  uninitialized_copy_n_result<_InputIterator, _OutputIterator>
+  operator()(_InputIterator __ifirst, iter_
diff erence_t<_InputIterator> __n,
+             _OutputIterator __ofirst, _Sentinel __olast) const {
+    // clang-format on
+    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));
+    return {_VSTD::move(__result.first), _VSTD::move(__result.second)};
+  }
+
+};
+
+} // namespace __uninitialized_copy_n
+
+inline namespace __cpo {
+  inline constexpr auto uninitialized_copy_n = __uninitialized_copy_n::__fn(__function_like::__tag());
+} // namespace __cpo
+
+// uninitialized_move
+
+template <class _InputIterator, class _OutputIterator>
+using uninitialized_move_result = in_out_result<_InputIterator, _OutputIterator>;
+
+namespace __uninitialized_move {
+
+struct __fn final : private __function_like {
+
+  constexpr explicit __fn(__tag __x) noexcept : __function_like(__x) {}
+
+  // clang-format off
+  template <input_iterator _InputIterator,
+            sentinel_for<_InputIterator> _Sentinel1,
+            __nothrow_forward_iterator _OutputIterator,
+            __nothrow_sentinel_for<_OutputIterator> _Sentinel2>
+    requires constructible_from<iter_value_t<_OutputIterator>, iter_reference_t<_InputIterator>>
+  uninitialized_move_result<_InputIterator, _OutputIterator>
+  operator()(_InputIterator __ifirst, _Sentinel1 __ilast, _OutputIterator __ofirst, _Sentinel2 __olast) const {
+    // clang-format on
+    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);
+    return {_VSTD::move(__result.first), _VSTD::move(__result.second)};
+  }
+
+  // clang-format off
+  template <input_range _InputRange, __nothrow_forward_range _OutputRange>
+    requires constructible_from<range_value_t<_OutputRange>, range_reference_t<_InputRange>>
+  uninitialized_move_result<borrowed_iterator_t<_InputRange>, borrowed_iterator_t<_OutputRange>>
+  operator()(_InputRange&& __in_range, _OutputRange&& __out_range) const {
+    // clang-format on
+    return (*this)(ranges::begin(__in_range), ranges::end(__in_range), ranges::begin(__out_range),
+                   ranges::end(__out_range));
+  }
+
+};
+
+} // namespace __uninitialized_move
+
+inline namespace __cpo {
+  inline constexpr auto uninitialized_move = __uninitialized_move::__fn(__function_like::__tag());
+} // namespace __cpo
+
+// uninitialized_move_n
+
+template <class _InputIterator, class _OutputIterator>
+using uninitialized_move_n_result = in_out_result<_InputIterator, _OutputIterator>;
+
+namespace __uninitialized_move_n {
+
+struct __fn final : private __function_like {
+
+  constexpr explicit __fn(__tag __x) noexcept : __function_like(__x) {}
+
+  // clang-format off
+  template <input_iterator _InputIterator,
+           __nothrow_forward_iterator _OutputIterator,
+           __nothrow_sentinel_for<_OutputIterator> _Sentinel>
+    requires constructible_from<iter_value_t<_OutputIterator>, iter_reference_t<_InputIterator>>
+  uninitialized_move_n_result<_InputIterator, _OutputIterator>
+  operator()(_InputIterator __ifirst, iter_
diff erence_t<_InputIterator> __n, _OutputIterator __ofirst,
+             _Sentinel __olast) const {
+    // clang-format on
+    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);
+    return {_VSTD::move(__result.first), _VSTD::move(__result.second)};
+  }
+
+};
+
+} // namespace __uninitialized_move_n
+
+inline namespace __cpo {
+  inline constexpr auto uninitialized_move_n = __uninitialized_move_n::__fn(__function_like::__tag());
 } // namespace __cpo
 
 } // namespace ranges

diff  --git a/libcxx/include/__memory/uninitialized_algorithms.h b/libcxx/include/__memory/uninitialized_algorithms.h
index 69132633a48e3..40e7c79a51e0f 100644
--- a/libcxx/include/__memory/uninitialized_algorithms.h
+++ b/libcxx/include/__memory/uninitialized_algorithms.h
@@ -23,52 +23,75 @@
 
 _LIBCPP_BEGIN_NAMESPACE_STD
 
-template <class _InputIterator, class _ForwardIterator>
-_ForwardIterator
-uninitialized_copy(_InputIterator __f, _InputIterator __l, _ForwardIterator __r)
-{
-    typedef typename iterator_traits<_ForwardIterator>::value_type value_type;
+// 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;
+  }
+};
+
+// 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) {
+  _ForwardIterator __idx = __ofirst;
 #ifndef _LIBCPP_NO_EXCEPTIONS
-    _ForwardIterator __s = __r;
-    try
-    {
+  try {
 #endif
-        for (; __f != __l; ++__f, (void) ++__r)
-            ::new ((void*)_VSTD::addressof(*__r)) value_type(*__f);
+    for (; __ifirst != __ilast && __idx != __olast; ++__ifirst, (void)++__idx)
+      ::new (_VSTD::__voidify(*__idx)) _ValueType(*__ifirst);
 #ifndef _LIBCPP_NO_EXCEPTIONS
-    }
-    catch (...)
-    {
-        for (; __s != __r; ++__s)
-            __s->~value_type();
-        throw;
-    }
+  } catch (...) {
+    _VSTD::__destroy(__ofirst, __idx);
+    throw;
+  }
 #endif
-    return __r;
+
+  return pair<_InputIterator, _ForwardIterator>(_VSTD::move(__ifirst), _VSTD::move(__idx));
 }
 
-template <class _InputIterator, class _Size, class _ForwardIterator>
-_ForwardIterator
-uninitialized_copy_n(_InputIterator __f, _Size __n, _ForwardIterator __r)
-{
-    typedef typename iterator_traits<_ForwardIterator>::value_type value_type;
+template <class _InputIterator, class _ForwardIterator>
+_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());
+  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) {
+  _ForwardIterator __idx = __ofirst;
 #ifndef _LIBCPP_NO_EXCEPTIONS
-    _ForwardIterator __s = __r;
-    try
-    {
+  try {
 #endif
-        for (; __n > 0; ++__f, (void) ++__r, (void) --__n)
-            ::new ((void*)_VSTD::addressof(*__r)) value_type(*__f);
+    for (; __n > 0 && __idx != __olast; ++__ifirst, (void)++__idx, (void)--__n)
+      ::new (_VSTD::__voidify(*__idx)) _ValueType(*__ifirst);
 #ifndef _LIBCPP_NO_EXCEPTIONS
-    }
-    catch (...)
-    {
-        for (; __s != __r; ++__s)
-            __s->~value_type();
-        throw;
-    }
+  } catch (...) {
+    _VSTD::__destroy(__ofirst, __idx);
+    throw;
+  }
 #endif
-    return __r;
+
+  return pair<_InputIterator, _ForwardIterator>(_VSTD::move(__ifirst), _VSTD::move(__idx));
+}
+
+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());
+  return _VSTD::move(__result.second);
 }
 
 // uninitialized_fill
@@ -253,43 +276,71 @@ _ForwardIterator uninitialized_value_construct_n(_ForwardIterator __first, _Size
     return __uninitialized_value_construct_n<_ValueType>(_VSTD::move(__first), __n);
 }
 
-template <class _InputIt, class _ForwardIt>
-inline _LIBCPP_INLINE_VISIBILITY
-_ForwardIt uninitialized_move(_InputIt __first, _InputIt __last, _ForwardIt __first_res) {
-    using _Vt = typename iterator_traits<_ForwardIt>::value_type;
-    auto __idx = __first_res;
+// uninitialized_move
+
+template <class _ValueType, class _InputIterator, class _Sentinel1, class _ForwardIterator, class _Sentinel2,
+          class _IterMove>
+inline _LIBCPP_HIDE_FROM_ABI pair<_InputIterator, _ForwardIterator>
+__uninitialized_move(_InputIterator __ifirst, _Sentinel1 __ilast,
+                     _ForwardIterator __ofirst, _Sentinel2 __olast, _IterMove __iter_move) {
+  auto __idx = __ofirst;
 #ifndef _LIBCPP_NO_EXCEPTIONS
-    try {
+  try {
 #endif
-    for (; __first != __last; ++__idx, (void) ++__first)
-        ::new ((void*)_VSTD::addressof(*__idx)) _Vt(_VSTD::move(*__first));
-    return __idx;
-#ifndef _LIBCPP_NO_EXCEPTIONS
-    } catch (...) {
-        _VSTD::destroy(__first_res, __idx);
-        throw;
+    for (; __ifirst != __ilast && __idx != __olast; ++__idx, (void)++__ifirst) {
+      ::new (_VSTD::__voidify(*__idx)) _ValueType(__iter_move(__ifirst));
     }
+#ifndef _LIBCPP_NO_EXCEPTIONS
+  } catch (...) {
+    _VSTD::__destroy(__ofirst, __idx);
+    throw;
+  }
 #endif
+
+  return {_VSTD::move(__ifirst), _VSTD::move(__idx)};
+}
+
+template <class _InputIterator, class _ForwardIterator>
+inline _LIBCPP_HIDE_FROM_ABI _ForwardIterator uninitialized_move(_InputIterator __ifirst, _InputIterator __ilast,
+                                                                 _ForwardIterator __ofirst) {
+  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);
+  return _VSTD::move(__result.second);
 }
 
-template <class _InputIt, class _Size, class _ForwardIt>
-inline _LIBCPP_INLINE_VISIBILITY
-pair<_InputIt, _ForwardIt>
-uninitialized_move_n(_InputIt __first, _Size __n, _ForwardIt __first_res) {
-    using _Vt = typename iterator_traits<_ForwardIt>::value_type;
-    auto __idx = __first_res;
+// 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) {
+  auto __idx = __ofirst;
 #ifndef _LIBCPP_NO_EXCEPTIONS
-    try {
+  try {
 #endif
-    for (; __n > 0; ++__idx, (void) ++__first, --__n)
-        ::new ((void*)_VSTD::addressof(*__idx)) _Vt(_VSTD::move(*__first));
-    return {__first, __idx};
+    for (; __n > 0 && __idx != __olast; ++__idx, (void)++__ifirst, --__n)
+      ::new (_VSTD::__voidify(*__idx)) _ValueType(__iter_move(__ifirst));
 #ifndef _LIBCPP_NO_EXCEPTIONS
-    } catch (...) {
-        _VSTD::destroy(__first_res, __idx);
-        throw;
-    }
+  } catch (...) {
+    _VSTD::__destroy(__ofirst, __idx);
+    throw;
+  }
 #endif
+
+  return {_VSTD::move(__ifirst), _VSTD::move(__idx)};
+}
+
+template <class _InputIterator, class _Size, class _ForwardIterator>
+inline _LIBCPP_HIDE_FROM_ABI pair<_InputIterator, _ForwardIterator>
+uninitialized_move_n(_InputIterator __ifirst, _Size __n, _ForwardIterator __ofirst) {
+  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);
 }
 
 #endif // _LIBCPP_STD_VER > 14

diff  --git a/libcxx/include/algorithm b/libcxx/include/algorithm
index 55b0bc5249c34..932d17d66b176 100644
--- a/libcxx/include/algorithm
+++ b/libcxx/include/algorithm
@@ -641,6 +641,12 @@ template <class BidirectionalIterator, class Compare>
     constexpr bool     // constexpr in C++20
     prev_permutation(BidirectionalIterator first, BidirectionalIterator last, Compare comp);
 
+namespace ranges {
+// [algorithms.results], algorithm result types
+template<class InputIterator, class OutputIterator>
+    struct in_out_result;
+}
+
 }  // std
 
 */
@@ -685,6 +691,7 @@ template <class BidirectionalIterator, class Compare>
 #include <__algorithm/generate.h>
 #include <__algorithm/generate_n.h>
 #include <__algorithm/half_positive.h>
+#include <__algorithm/in_out_result.h>
 #include <__algorithm/includes.h>
 #include <__algorithm/inplace_merge.h>
 #include <__algorithm/is_heap.h>

diff  --git a/libcxx/include/memory b/libcxx/include/memory
index 3ed1530f2a756..244b40fe7720d 100644
--- a/libcxx/include/memory
+++ b/libcxx/include/memory
@@ -181,28 +181,65 @@ template <class InputIterator, class ForwardIterator>
 ForwardIterator
 uninitialized_copy(InputIterator first, InputIterator last, ForwardIterator result);
 
+namespace ranges {
+
+template<class InputIterator, class OutputIterator>
+using uninitialized_copy_result = in_out_result<InputIterator, OutputIterator>; // since C++20
+
+template<input_iterator InputIterator, sentinel-for<InputIterator> Sentinel1, nothrow-forward-iterator OutputIterator, nothrow-sentinel-for<OutputIterator> Sentinel2>
+  requires constructible_from<iter_value_t<OutputIterator>, iter_reference_t<InputIterator>>
+uninitialized_copy_result<InputIterator, OutputIterator>
+uninitialized_copy(InputIterator ifirst, Sentinel1 ilast, OutputIterator ofirst, Sentinel2 olast); // since C++20
+
+template<input_range InputRange, nothrow-forward-range OutputRange>
+  requires constructible_from<range_value_t<OutputRange>, range_reference_t<InputRange>>
+uninitialized_copy_result<borrowed_iterator_t<InputRange>, borrowed_iterator_t<OutputRange>>
+uninitialized_copy(InputRange&& in_range, OutputRange&& out_range); // since C++20
+
+}
+
 template <class InputIterator, class Size, class ForwardIterator>
 ForwardIterator
 uninitialized_copy_n(InputIterator first, Size n, ForwardIterator result);
 
+namespace ranges {
+
+template<class InputIterator, class OutputIterator>
+using uninitialized_copy_n_result = in_out_result<InputIterator, OutputIterator>; // since C++20
+
+template<input_iterator InputIterator, nothrow-forward-iterator OutputIterator, nothrow-sentinel-for<OutputIterator> Sentinel>
+  requires constructible_from<iter_value_t<OutputIterator>, iter_reference_t<InputIterator>>
+uninitialized_copy_n_result<InputIterator, OutputIterator>
+uninitialized_copy_n(InputIterator ifirst, iter_
diff erence_t<InputIterator> n, OutputIterator ofirst, Sentinel olast); // since C++20
+
+}
+
 template <class ForwardIterator, class T>
 void uninitialized_fill(ForwardIterator first, ForwardIterator last, const T& x);
 
+namespace ranges {
+
 template <nothrow-forward-iterator ForwardIterator, nothrow-sentinel-for<ForwardIterator> Sentinel, class T>
   requires constructible_from<iter_value_t<ForwardIterator>, const T&>
-ForwardIterator ranges::uninitialized_fill(ForwardIterator first, Sentinel last, const T& x); // since C++20
+ForwardIterator uninitialized_fill(ForwardIterator first, Sentinel last, const T& x); // since C++20
 
 template <nothrow-forward-range ForwardRange, class T>
   requires constructible_from<range_value_t<ForwardRange>, const T&>
-borrowed_iterator_t<ForwardRange> ranges::uninitialized_fill(ForwardRange&& range, const T& x); // since C++20
+borrowed_iterator_t<ForwardRange> uninitialized_fill(ForwardRange&& range, const T& x); // since C++20
+
+}
 
 template <class ForwardIterator, class Size, class T>
 ForwardIterator
 uninitialized_fill_n(ForwardIterator first, Size n, const T& x);
 
+namespace ranges {
+
 template <nothrow-forward-iterator ForwardIterator, class T>
   requires constructible_from<iter_value_t<ForwardIterator>, const T&>
-ForwardIterator ranges::uninitialized_fill_n(ForwardIterator first, iter_
diff erence_t<ForwardIterator> n); // since C++20
+ForwardIterator uninitialized_fill_n(ForwardIterator first, iter_
diff erence_t<ForwardIterator> n); // since C++20
+
+}
 
 template <class T, class ...Args>
 constexpr T* construct_at(T* location, Args&& ...args); // since C++20
@@ -219,44 +256,89 @@ ForwardIterator destroy_n(ForwardIterator first, Size n); // constexpr in C++20
 template <class InputIterator, class ForwardIterator>
  ForwardIterator uninitialized_move(InputIterator first, InputIterator last, ForwardIterator result);
 
+namespace ranges {
+
+template<class InputIterator, class OutputIterator>
+using uninitialized_move_result = in_out_result<InputIterator, OutputIterator>; // since C++20
+
+template <input_iterator InputIterator, sentinel_for<InputIterator> Sentinel1, nothrow-forward-iterator OutputIterator, nothrow-sentinel-for<O> Sentinel2>
+  requires constructible_from<iter_value_t<OutputIterator>, iter_rvalue_reference_t<InputIterator>>
+uninitialized_move_result<InputIterator, OutputIterator>
+uninitialized_move(InputIterator ifirst, Sentinel1 ilast, OutputIterator ofirst, Sentinel2 olast); // since C++20
+
+template<input_range InputRange, nothrow-forward-range OutputRange>
+  requires constructible_from<range_value_t<OutputRange>, range_rvalue_reference_t<InputRange>>
+uninitialized_move_result<borrowed_iterator_t<InputRange>, borrowed_iterator_t<OutputRange>>
+uninitialized_move(InputRange&& in_range, OutputRange&& out_range); // since C++20
+
+}
+
 template <class InputIterator, class Size, class ForwardIterator>
  pair<InputIterator,ForwardIterator> uninitialized_move_n(InputIterator first, Size n, ForwardIterator result);
 
+namespace ranges {
+
+template<class InputIterator, class OutputIterator>
+using uninitialized_move_n_result = in_out_result<InputIterator, OutputIterator>; // since C++20
+
+template<input_iterator InputIterator, nothrow-forward-iterator OutputIterator, nothrow-sentinel-for<OutputIterator> Sentinel>
+  requires constructible_from<iter_value_t<OutputIterator>, iter_rvalue_reference_t<InputIterator>>
+uninitialized_move_n_result<InputIterator, OutputIterator>
+uninitialized_move_n(InputIterator ifirst, iter_
diff erence_t<InputIterator> n, OutputIterator ofirst, Sentinel olast); // since C++20
+
+}
+
 template <class ForwardIterator>
  void uninitialized_value_construct(ForwardIterator first, ForwardIterator last);
 
+namespace ranges {
+
 template <nothrow-forward-iterator ForwardIterator, nothrow-sentinel-for<ForwardIterator> Sentinel>
   requires default_initializable<iter_value_t<ForwardIterator>>
- ForwardIterator ranges::uninitialized_value_construct(ForwardIterator first, Sentinel last); // since C++20
+ ForwardIterator uninitialized_value_construct(ForwardIterator first, Sentinel last); // since C++20
 
 template <nothrow-forward-range ForwardRange>
   requires default_initializable<range_value_t<ForwardRange>>
- borrowed_iterator_t<ForwardRange> ranges::uninitialized_value_construct(ForwardRange&& r); // since C++20
+ borrowed_iterator_t<ForwardRange> uninitialized_value_construct(ForwardRange&& r); // since C++20
+
+}
 
 template <class ForwardIterator, class Size>
  ForwardIterator uninitialized_value_construct_n(ForwardIterator first, Size n);
 
+namespace ranges {
+
 template <nothrow-forward-iterator ForwardIterator>
   requires default_initializable<iter_value_t<ForwardIterator>>
- ForwardIterator ranges::uninitialized_value_construct_n(ForwardIterator first, iter_
diff erence_t<ForwardIterator> n); // since C++20
+ ForwardIterator uninitialized_value_construct_n(ForwardIterator first, iter_
diff erence_t<ForwardIterator> n); // since C++20
+
+}
 
 template <class ForwardIterator>
  void uninitialized_default_construct(ForwardIterator first, ForwardIterator last);
 
+namespace ranges {
+
 template <nothrow-forward-iterator ForwardIterator, nothrow-sentinel-for<ForwardIterator> Sentinel>
   requires default_initializable<iter_value_t<ForwardIterator>>
- ForwardIterator ranges::uninitialized_default_construct(ForwardIterator first, Sentinel last); // since C++20
+ ForwardIterator uninitialized_default_construct(ForwardIterator first, Sentinel last); // since C++20
 
 template <nothrow-forward-range ForwardRange>
   requires default_initializable<range_value_t<ForwardRange>>
- borrowed_iterator_t<ForwardRange> ranges::uninitialized_default_construct(ForwardRange&& r); // since C++20
+ borrowed_iterator_t<ForwardRange> uninitialized_default_construct(ForwardRange&& r); // since C++20
+
+}
 
 template <class ForwardIterator, class Size>
  ForwardIterator uninitialized_default_construct_n(ForwardIterator first, Size n);
 
+namespace ranges {
+
 template <nothrow-forward-iterator ForwardIterator>
   requires default_initializable<iter_value_t<ForwardIterator>>
- ForwardIterator ranges::uninitialized_default_construct_n(ForwardIterator first, iter_
diff erence_t<ForwardIterator> n); // since C++20
+ ForwardIterator uninitialized_default_construct_n(ForwardIterator first, iter_
diff erence_t<ForwardIterator> n); // since C++20
+
+}
 
 template <class Y> struct auto_ptr_ref {};      // deprecated in C++11, removed in C++17
 

diff  --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap
index 3ee19ab1b8f88..305fe1bc9d35f 100644
--- a/libcxx/include/module.modulemap
+++ b/libcxx/include/module.modulemap
@@ -246,6 +246,7 @@ module std [system] {
       module generate                 { private header "__algorithm/generate.h" }
       module generate_n               { private header "__algorithm/generate_n.h" }
       module half_positive            { private header "__algorithm/half_positive.h" }
+      module in_out_result            { private header "__algorithm/in_out_result.h" }
       module includes                 { private header "__algorithm/includes.h" }
       module inplace_merge            { private header "__algorithm/inplace_merge.h" }
       module is_heap                  { private header "__algorithm/is_heap.h" }

diff  --git a/libcxx/test/libcxx/diagnostics/detail.headers/algorithm/in_out_result.module.verify.cpp b/libcxx/test/libcxx/diagnostics/detail.headers/algorithm/in_out_result.module.verify.cpp
new file mode 100644
index 0000000000000..254aca51128f4
--- /dev/null
+++ b/libcxx/test/libcxx/diagnostics/detail.headers/algorithm/in_out_result.module.verify.cpp
@@ -0,0 +1,15 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: modules-build
+
+// WARNING: This test was generated by 'generate_private_header_tests.py'
+// and should not be edited manually.
+
+// expected-error@*:* {{use of private header from outside its module: '__algorithm/in_out_result.h'}}
+#include <__algorithm/in_out_result.h>

diff  --git a/libcxx/test/std/algorithms/algorithms.results/in_out_result.compile.pass.cpp b/libcxx/test/std/algorithms/algorithms.results/in_out_result.compile.pass.cpp
new file mode 100644
index 0000000000000..081f52c811dd0
--- /dev/null
+++ b/libcxx/test/std/algorithms/algorithms.results/in_out_result.compile.pass.cpp
@@ -0,0 +1,28 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts, libcpp-has-no-incomplete-ranges
+//
+// clang-cl and cl currently don't support [[no_unique_address]]
+// XFAIL: msvc
+
+// namespace ranges {
+//   template<class InputIterator, class OutputIterator>
+//     struct in_out_result;
+// }
+
+#include <algorithm>
+
+// Size optimization.
+struct Empty {};
+struct Empty2 {};
+
+static_assert(sizeof(std::ranges::in_out_result<Empty, int>) == sizeof(int));
+static_assert(sizeof(std::ranges::in_out_result<int, Empty>) == sizeof(int));
+static_assert(sizeof(std::ranges::in_out_result<Empty, Empty2>) == sizeof(char));

diff  --git a/libcxx/test/std/algorithms/algorithms.results/in_out_result.pass.cpp b/libcxx/test/std/algorithms/algorithms.results/in_out_result.pass.cpp
new file mode 100644
index 0000000000000..2fb02fba6f10c
--- /dev/null
+++ b/libcxx/test/std/algorithms/algorithms.results/in_out_result.pass.cpp
@@ -0,0 +1,132 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts, libcpp-has-no-incomplete-ranges
+
+// <algorithm>
+//
+// namespace ranges {
+//   template<class InputIterator, class OutputIterator>
+//     struct in_out_result;
+// }
+
+#include <algorithm>
+#include <cassert>
+#include <type_traits>
+
+struct A {
+  A(int&);
+};
+static_assert(!std::is_constructible_v<std::ranges::in_out_result<A, A>, std::ranges::in_out_result<int, int>&>);
+
+static_assert(std::is_convertible_v<std::ranges::in_out_result<int, int>&,
+    std::ranges::in_out_result<long, long>>);
+static_assert(!std::is_nothrow_convertible_v<std::ranges::in_out_result<int, int>&,
+    std::ranges::in_out_result<long, long>>);
+static_assert(std::is_convertible_v<const std::ranges::in_out_result<int, int>&,
+    std::ranges::in_out_result<long, long>>);
+static_assert(!std::is_nothrow_convertible_v<const std::ranges::in_out_result<int, int>&,
+    std::ranges::in_out_result<long, long>>);
+static_assert(std::is_convertible_v<std::ranges::in_out_result<int, int>&&,
+    std::ranges::in_out_result<long, long>>);
+static_assert(!std::is_nothrow_convertible_v<std::ranges::in_out_result<int, int>&&,
+    std::ranges::in_out_result<long, long>>);
+static_assert(std::is_convertible_v<const std::ranges::in_out_result<int, int>&&,
+    std::ranges::in_out_result<long, long>>);
+static_assert(!std::is_nothrow_convertible_v<const std::ranges::in_out_result<int, int>&&,
+    std::ranges::in_out_result<long, long>>);
+
+int main(int, char**) {
+  // Conversion, fundamental types.
+  {
+    std::ranges::in_out_result<int, bool> x = {2, false};
+    std::ranges::in_out_result<double, char> y = x;
+    assert(y.in == 2.0);
+    assert(y.out == '\0');
+  }
+
+  // Conversion, user-defined types.
+  {
+    struct From1 {
+      int value = 0;
+      From1(int v) : value(v) {}
+    };
+
+    struct To1 {
+      int value = 0;
+      To1(int v) : value(v) {}
+
+      To1(const From1& f) : value(f.value) {};
+    };
+
+    struct To2 {
+      int value = 0;
+      To2(int v) : value(v) {}
+    };
+    struct From2 {
+      int value = 0;
+      From2(int v) : value(v) {}
+
+      operator To2() const { return To2(value); }
+    };
+
+    std::ranges::in_out_result<From1, From2> x{42, 99};
+    std::ranges::in_out_result<To1, To2> y = x;
+    assert(y.in.value == 42);
+    assert(y.out.value == 99);
+  }
+
+  // Copy-only type.
+  {
+    struct CopyOnly {
+      int value = 0;
+      CopyOnly() = default;
+      CopyOnly(int v) : value(v) {}
+
+      CopyOnly(const CopyOnly&) = default;
+      CopyOnly(CopyOnly&&) = delete;
+    };
+
+    std::ranges::in_out_result<CopyOnly, CopyOnly> x;
+    x.in.value = 42;
+    x.out.value = 99;
+
+    auto y = x;
+    assert(y.in.value == 42);
+    assert(y.out.value == 99);
+  }
+
+  // Move-only type.
+  {
+    struct MoveOnly {
+      int value = 0;
+      MoveOnly(int v) : value(v) {}
+
+      MoveOnly(MoveOnly&&) = default;
+      MoveOnly(const MoveOnly&) = delete;
+    };
+
+    std::ranges::in_out_result<MoveOnly, MoveOnly> x{42, 99};
+    auto y = std::move(x);
+    assert(y.in.value == 42);
+    assert(y.out.value == 99);
+  }
+
+  // Unsuccessful conversion.
+  {
+    struct Foo1 {};
+    struct Foo2 {};
+    struct Bar1 {};
+    struct Bar2 {};
+    static_assert(
+        !std::is_convertible_v<std::ranges::in_out_result<Foo1, Foo2>, std::ranges::in_out_result<Bar1, Bar2>>);
+  }
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/memory/specialized.algorithms/counted.h b/libcxx/test/std/utilities/memory/specialized.algorithms/counted.h
index 384fa7d78f4b9..1d4cc5229a59b 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/counted.h
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/counted.h
@@ -15,9 +15,12 @@
 struct Counted {
   static int current_objects;
   static int total_objects;
+  static int total_copies;
+  static int total_moves;
   static int throw_on;
 
   int value;
+  bool moved_from = false;
 
   explicit Counted() {
     check_throw();
@@ -33,12 +36,30 @@ struct Counted {
 
   static void reset() {
     current_objects = total_objects = 0;
+    total_copies = total_moves = 0;
     throw_on = -1;
   }
 
   Counted(const Counted& rhs) : value(rhs.value) {
     check_throw();
     increase_counters();
+    ++total_copies;
+  }
+
+  Counted(Counted&& rhs) : value(rhs.value) {
+    check_throw();
+    increase_counters();
+
+    rhs.moved_from = true;
+    ++total_moves;
+  }
+
+  friend bool operator==(const Counted& l, const Counted& r) {
+    return l.value == r.value;
+  }
+
+  friend bool operator!=(const Counted& l, const Counted& r) {
+    return !(l == r);
   }
 
   friend void operator&(Counted) = delete;
@@ -57,6 +78,8 @@ struct Counted {
 };
 int Counted::current_objects = 0;
 int Counted::total_objects = 0;
+int Counted::total_copies = 0;
+int Counted::total_moves = 0;
 int Counted::throw_on = -1;
 
 #endif // LIBCPP_TEST_STD_UTILITIES_MEMORY_SPECIALIZED_ALGORITHMS_COUNTED_H

diff  --git a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.construct.default/ranges_uninitialized_default_construct.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.construct.default/ranges_uninitialized_default_construct.pass.cpp
index 04ee5b8e4d6eb..59c149c21e11a 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.construct.default/ranges_uninitialized_default_construct.pass.cpp
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.construct.default/ranges_uninitialized_default_construct.pass.cpp
@@ -30,6 +30,7 @@
 #include "test_macros.h"
 #include "test_iterators.h"
 
+// TODO(varconst): consolidate the ADL checks into a single file.
 // Because this is a variable and not a function, it's guaranteed that ADL won't be used. However,
 // implementations are allowed to use a 
diff erent mechanism to achieve this effect, so this check is
 // libc++-specific.
@@ -154,8 +155,7 @@ int main(int, char**) {
 
     Counted::throw_on = 3; // When constructing the fourth object.
     try {
-      auto range = std::ranges::subrange(buf.begin(), buf.end());
-      std::ranges::uninitialized_default_construct(range);
+      std::ranges::uninitialized_default_construct(buf);
     } catch(...) {}
     assert(Counted::current_objects == 0);
     assert(Counted::total_objects == 3);

diff  --git a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.construct.default/ranges_uninitialized_default_construct_n.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.construct.default/ranges_uninitialized_default_construct_n.pass.cpp
index 904366fe315c4..9836a3e74f19e 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.construct.default/ranges_uninitialized_default_construct_n.pass.cpp
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.construct.default/ranges_uninitialized_default_construct_n.pass.cpp
@@ -25,6 +25,7 @@
 #include "test_macros.h"
 #include "test_iterators.h"
 
+// TODO(varconst): consolidate the ADL checks into a single file.
 // Because this is a variable and not a function, it's guaranteed that ADL won't be used. However,
 // implementations are allowed to use a 
diff erent mechanism to achieve this effect, so this check is
 // libc++-specific.

diff  --git a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.construct.default/uninitialized_default_construct_n.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.construct.default/uninitialized_default_construct_n.pass.cpp
index 95fb90d6f2a8f..58ee06aaa819d 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.construct.default/uninitialized_default_construct_n.pass.cpp
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.construct.default/uninitialized_default_construct_n.pass.cpp
@@ -66,7 +66,7 @@ void test_ctor_throws()
         assert(false);
     } catch (...) {}
     assert(ThrowsCounted::count == 0);
-    assert(ThrowsCounted::constructed == 4); // forth construction throws
+    assert(ThrowsCounted::constructed == 4); // Fourth construction throws
 #endif
 }
 

diff  --git a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.construct.value/ranges_uninitialized_value_construct.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.construct.value/ranges_uninitialized_value_construct.pass.cpp
index cc89c69514b93..7c46558f2d471 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.construct.value/ranges_uninitialized_value_construct.pass.cpp
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.construct.value/ranges_uninitialized_value_construct.pass.cpp
@@ -30,6 +30,7 @@
 #include "test_macros.h"
 #include "test_iterators.h"
 
+// TODO(varconst): consolidate the ADL checks into a single file.
 // Because this is a variable and not a function, it's guaranteed that ADL won't be used. However,
 // implementations are allowed to use a 
diff erent mechanism to achieve this effect, so this check is
 // libc++-specific.
@@ -173,8 +174,7 @@ int main(int, char**) {
 
     Counted::throw_on = 3; // When constructing the fourth object.
     try {
-      auto range = std::ranges::subrange(buf.begin(), buf.end());
-      std::ranges::uninitialized_value_construct(range);
+      std::ranges::uninitialized_value_construct(buf);
     } catch (...) {
     }
     assert(Counted::current_objects == 0);

diff  --git a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.construct.value/ranges_uninitialized_value_construct_n.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.construct.value/ranges_uninitialized_value_construct_n.pass.cpp
index 3207f286d14b7..ea8ae75cbd704 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.construct.value/ranges_uninitialized_value_construct_n.pass.cpp
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.construct.value/ranges_uninitialized_value_construct_n.pass.cpp
@@ -25,6 +25,7 @@
 #include "test_macros.h"
 #include "test_iterators.h"
 
+// TODO(varconst): consolidate the ADL checks into a single file.
 // Because this is a variable and not a function, it's guaranteed that ADL won't be used. However,
 // implementations are allowed to use a 
diff erent mechanism to achieve this effect, so this check is
 // libc++-specific.

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
new file mode 100644
index 0000000000000..f6b40fd145635
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.copy/ranges_uninitialized_copy.pass.cpp
@@ -0,0 +1,374 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts, libcpp-has-no-incomplete-ranges
+
+// <memory>
+//
+// template<input_iterator I, sentinel-for<I> S1, nothrow-forward-iterator O, nothrow-sentinel-for<O> S2>
+//   requires constructible_from<iter_value_t<O>, iter_reference_t<I>>
+// uninitialized_copy_result<I, O> ranges::uninitialized_copy(I ifirst, S1 ilast, O ofirst, S2 olast); // since C++20
+//
+// template<input_range IR, nothrow-forward-range OR>
+//   requires constructible_from<range_value_t<OR>, range_reference_t<IR>>
+// uninitialized_copy_result<borrowed_iterator_t<IR>, borrowed_iterator_t<OR>> ranges::uninitialized_copy(IR&& in_range, OR&& out_range); // since C++20
+
+#include <algorithm>
+#include <cassert>
+#include <iterator>
+#include <memory>
+#include <ranges>
+#include <type_traits>
+
+#include "../buffer.h"
+#include "../counted.h"
+#include "test_macros.h"
+#include "test_iterators.h"
+
+// TODO(varconst): consolidate the ADL checks into a single file.
+// Because this is a variable and not a function, it's guaranteed that ADL won't be used. However,
+// implementations are allowed to use a 
diff erent mechanism to achieve this effect, so this check is
+// libc++-specific.
+LIBCPP_STATIC_ASSERT(std::is_class_v<decltype(std::ranges::uninitialized_copy)>);
+
+static_assert(std::is_invocable_v<decltype(std::ranges::uninitialized_copy), int*, int*, long*, long*>);
+struct NotConvertibleFromInt {};
+static_assert(!std::is_invocable_v<decltype(std::ranges::uninitialized_copy), int*, int*, NotConvertibleFromInt*,
+                                   NotConvertibleFromInt*>);
+
+int main(int, char**) {
+  // An empty range -- no default constructors should be invoked.
+  {
+    Counted in[] = {Counted()};
+    Buffer<Counted, 1> out;
+    Counted::reset();
+
+    {
+      auto result = std::ranges::uninitialized_copy(in, in, out.begin(), out.end());
+      assert(Counted::current_objects == 0);
+      assert(Counted::total_objects == 0);
+      assert(Counted::total_copies == 0);
+      assert(result.in == in);
+      assert(result.out == out.begin());
+    }
+
+    {
+      std::ranges::empty_view<Counted> view;
+      auto result = std::ranges::uninitialized_copy(view, out);
+      assert(Counted::current_objects == 0);
+      assert(Counted::total_objects == 0);
+      assert(Counted::total_copies == 0);
+      assert(result.in == view.begin());
+      assert(result.out == out.begin());
+    }
+
+    {
+      forward_iterator<Counted*> it(in);
+      std::ranges::subrange range(it, sentinel_wrapper<forward_iterator<Counted*>>(it));
+
+      auto result = std::ranges::uninitialized_copy(range.begin(), range.end(), out.begin(), out.end());
+      assert(Counted::current_objects == 0);
+      assert(Counted::total_objects == 0);
+      assert(Counted::total_copies == 0);
+      assert(result.in == it);
+      assert(result.out == out.begin());
+    }
+
+    {
+      forward_iterator<Counted*> it(in);
+      std::ranges::subrange range(it, sentinel_wrapper<forward_iterator<Counted*>>(it));
+
+      auto result = std::ranges::uninitialized_copy(range, out);
+      assert(Counted::current_objects == 0);
+      assert(Counted::total_objects == 0);
+      assert(Counted::total_copies == 0);
+      assert(result.in == it);
+      assert(result.out == out.begin());
+    }
+    Counted::reset();
+  }
+
+  // A range containing several objects, (iter, sentinel) overload.
+  {
+    constexpr int N = 5;
+    Counted in[N] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, N> out;
+    Counted::reset();
+
+    auto result = std::ranges::uninitialized_copy(in, in + N, out.begin(), out.end());
+    ASSERT_SAME_TYPE(decltype(result), std::ranges::uninitialized_copy_result<Counted*, Counted*>);
+
+    assert(Counted::current_objects == N);
+    assert(Counted::total_objects == N);
+    assert(Counted::total_copies == N);
+    assert(Counted::total_moves == 0);
+
+    assert(std::equal(in, in + N, out.begin(), out.end()));
+    assert(result.in == in + N);
+    assert(result.out == out.end());
+
+    std::destroy(out.begin(), out.end());
+  }
+  Counted::reset();
+
+  // A range containing several objects, (range) overload.
+  {
+    constexpr int N = 5;
+    Counted in[N] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, N> out;
+    Counted::reset();
+
+    std::ranges::subrange range(in, in + N);
+    auto result = std::ranges::uninitialized_copy(range, out);
+    ASSERT_SAME_TYPE(decltype(result), std::ranges::uninitialized_copy_result<Counted*, Counted*>);
+
+    assert(Counted::current_objects == N);
+    assert(Counted::total_objects == N);
+    assert(Counted::total_copies == N);
+    assert(Counted::total_moves == 0);
+
+    assert(std::equal(in, in + N, out.begin(), out.end()));
+    assert(result.in == in + N);
+    assert(result.out == out.end());
+
+    std::destroy(out.begin(), out.end());
+  }
+  Counted::reset();
+
+  // Using `counted_iterator`.
+  {
+    constexpr int N = 3;
+    Counted in[] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, 5> out;
+    Counted::reset();
+
+    std::counted_iterator iter(in, N);
+    auto result = std::ranges::uninitialized_copy(iter, std::default_sentinel, out.begin(), out.end());
+
+    assert(Counted::current_objects == N);
+    assert(Counted::total_objects == N);
+    assert(Counted::total_copies == N);
+    assert(Counted::total_moves == 0);
+    assert(std::equal(in, in + N, out.begin(), out.begin() + N));
+
+    assert(result.in == iter + N);
+    assert(result.out == out.begin() + N);
+
+    std::destroy(out.begin(), out.begin() + N);
+  }
+  Counted::reset();
+
+  // Using `views::counted`.
+  {
+    constexpr int N = 3;
+    Counted in[] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, 5> out;
+    Counted::reset();
+
+    auto view = std::views::counted(in, N);
+    auto result = std::ranges::uninitialized_copy(view, out);
+
+    assert(Counted::current_objects == N);
+    assert(Counted::total_objects == N);
+    assert(Counted::total_copies == N);
+    assert(Counted::total_moves == 0);
+    assert(std::equal(in, in + N, out.begin(), out.begin() + N));
+
+    assert(result.in == view.begin() + N);
+    assert(result.out == out.begin() + N);
+
+    std::destroy(out.begin(), out.begin() + N);
+  }
+  Counted::reset();
+
+  // Using `reverse_view`.
+  {
+    constexpr int N = 3;
+    Counted in[] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, 5> out;
+    Counted::reset();
+
+    std::ranges::subrange range(in, in + N);
+    auto view = std::ranges::views::reverse(range);
+    auto result = std::ranges::uninitialized_copy(view, out);
+
+    assert(Counted::current_objects == N);
+    assert(Counted::total_objects == N);
+    assert(Counted::total_copies == N);
+    assert(Counted::total_moves == 0);
+
+    Counted expected[N] = {Counted(3), Counted(2), Counted(1)};
+    assert(std::equal(out.begin(), out.begin() + N, expected, expected + N));
+
+    assert(result.in == view.begin() + N);
+    assert(result.out == out.begin() + N);
+
+    std::destroy(out.begin(), out.begin() + N);
+  }
+  Counted::reset();
+
+  // Any existing values should be overwritten by copy constructors.
+  {
+    constexpr int N = 5;
+    int in[N] = {1, 2, 3, 4, 5};
+    int out[N] = {6, 7, 8, 9, 10};
+    assert(!std::equal(in, in + N, in, out + N));
+
+    std::ranges::uninitialized_copy(in, in + 1, out, out + N);
+    assert(out[0] == 1);
+    assert(out[1] == 7);
+
+    std::ranges::uninitialized_copy(in, in + N, out, out + N);
+    assert(std::equal(in, in + N, out, out + N));
+  }
+
+  // An exception is thrown while objects are being created -- objects not yet overwritten should
+  // stay valid. (iterator, sentinel) overload.
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  {
+    constexpr int M = 3;
+    constexpr int N = 5;
+    Counted in[N] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Counted out[N] = {Counted(6), Counted(7), Counted(8), Counted(9), Counted(10)};
+    Counted::reset();
+
+    Counted::throw_on = M; // When constructing out[3].
+    try {
+      std::ranges::uninitialized_copy(in, in + N, out, out + N);
+      assert(false);
+    } catch (...) {
+    }
+    assert(Counted::current_objects == 0);
+    assert(Counted::total_objects == M);
+    assert(Counted::total_copies == M);
+    assert(Counted::total_moves == 0);
+
+    assert(out[4].value == 10);
+  }
+  Counted::reset();
+
+  // An exception is thrown while objects are being created -- objects not yet overwritten should
+  // stay valid. (range) overload.
+  {
+    constexpr int M = 3;
+    constexpr int N = 5;
+    Counted in[N] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Counted out[N] = {Counted(6), Counted(7), Counted(8), Counted(9), Counted(10)};
+    Counted::reset();
+
+    Counted::throw_on = M; // When constructing out[3].
+    try {
+      std::ranges::uninitialized_copy(in, out);
+      assert(false);
+    } catch (...) {
+    }
+    assert(Counted::current_objects == 0);
+    assert(Counted::total_objects == M);
+    assert(Counted::total_copies == M);
+    assert(Counted::total_moves == 0);
+
+    assert(out[4].value == 10);
+  }
+  Counted::reset();
+#endif // TEST_HAS_NO_EXCEPTIONS
+
+  // Works with const iterators, (iter, sentinel) overload.
+  {
+    constexpr int N = 5;
+    Counted in[N] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, N> out;
+    Counted::reset();
+
+    std::ranges::uninitialized_copy(in, in + N, out.cbegin(), out.cend());
+    assert(Counted::current_objects == N);
+    assert(Counted::total_objects == N);
+    assert(std::equal(in, in + N, out.begin(), out.end()));
+
+    std::destroy(out.begin(), out.end());
+  }
+  Counted::reset();
+
+  // Works with const iterators, (range) overload.
+  {
+    constexpr int N = 5;
+    Counted in[N] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, N> out;
+    Counted::reset();
+
+    std::ranges::subrange out_range(out.cbegin(), out.cend());
+    std::ranges::uninitialized_copy(in, out_range);
+    assert(Counted::current_objects == N);
+    assert(Counted::total_objects == N);
+    assert(std::equal(in, in + N, out.begin(), out.end()));
+
+    std::destroy(out.begin(), out.end());
+  }
+  Counted::reset();
+
+  // Conversions, (iter, sentinel) overload.
+  {
+    constexpr int N = 3;
+    double in[N] = {1.0, 2.0, 3.0};
+    Buffer<int, N> out;
+
+    std::ranges::uninitialized_copy(in, in + N, out.begin(), out.end());
+    assert(std::equal(in, in + N, out.begin(), out.end()));
+  }
+
+  // Conversions, (range) overload.
+  {
+    constexpr int N = 3;
+    double in[N] = {1.0, 2.0, 3.0};
+    Buffer<int, N> out;
+
+    std::ranges::uninitialized_copy(in, out);
+    assert(std::equal(in, in + N, out.begin(), out.end()));
+  }
+
+  // Destination range is shorter than the source range, (iter, sentinel) overload.
+  {
+    constexpr int M = 3;
+    constexpr int N = 5;
+    Counted in[N] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, M> out;
+    Counted::reset();
+
+    auto result = std::ranges::uninitialized_copy(in, in + N, out.begin(), out.end());
+    assert(Counted::current_objects == M);
+    assert(Counted::total_objects == M);
+    assert(Counted::total_copies == M);
+    assert(Counted::total_moves == 0);
+
+    assert(std::equal(in, in + M, out.begin(), out.end()));
+    assert(result.in == in + M);
+    assert(result.out == out.end());
+  }
+
+  // Destination range is shorter than the source range, (range) overload.
+  {
+    constexpr int M = 3;
+    constexpr int N = 5;
+    Counted in[N] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, M> out;
+    Counted::reset();
+
+    std::ranges::subrange range(in, in + N);
+    auto result = std::ranges::uninitialized_copy(range, out);
+    assert(Counted::current_objects == M);
+    assert(Counted::total_objects == M);
+    assert(Counted::total_copies == M);
+    assert(Counted::total_moves == 0);
+
+    assert(std::equal(in, in + M, out.begin(), out.end()));
+    assert(result.in == in + M);
+    assert(result.out == out.end());
+  }
+
+  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
new file mode 100644
index 0000000000000..f32af6cf4b38b
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.copy/ranges_uninitialized_copy_n.pass.cpp
@@ -0,0 +1,152 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts, libcpp-has-no-incomplete-ranges
+
+// <memory>
+//
+// template<input_iterator I, nothrow-forward-iterator O, nothrow-sentinel-for<O> S>
+//   requires constructible_from<iter_value_t<O>, iter_reference_t<I>>
+// uninitialized_copy_n_result<I, O> uninitialized_copy_n(I ifirst, iter_
diff erence_t<I> n, O ofirst, S olast); // since C++20
+
+#include <algorithm>
+#include <cassert>
+#include <iterator>
+#include <memory>
+#include <ranges>
+#include <type_traits>
+
+#include "../buffer.h"
+#include "../counted.h"
+#include "test_macros.h"
+#include "test_iterators.h"
+
+// TODO(varconst): consolidate the ADL checks into a single file.
+// Because this is a variable and not a function, it's guaranteed that ADL won't be used. However,
+// implementations are allowed to use a 
diff erent mechanism to achieve this effect, so this check is
+// libc++-specific.
+LIBCPP_STATIC_ASSERT(std::is_class_v<decltype(std::ranges::uninitialized_copy_n)>);
+
+static_assert(std::is_invocable_v<decltype(std::ranges::uninitialized_copy_n), int*, size_t, long*, long*>);
+struct NotConvertibleFromInt {};
+static_assert(!std::is_invocable_v<decltype(std::ranges::uninitialized_copy_n), int*, size_t, NotConvertibleFromInt*,
+                                   NotConvertibleFromInt*>);
+
+int main(int, char**) {
+  // An empty range -- no default constructors should be invoked.
+  {
+    Counted in[] = {Counted()};
+    Buffer<Counted, 1> out;
+    Counted::reset();
+
+    auto result = std::ranges::uninitialized_copy_n(in, 0, out.begin(), out.end());
+    assert(Counted::current_objects == 0);
+    assert(Counted::total_objects == 0);
+    assert(Counted::total_copies == 0);
+    assert(result.in == in);
+    assert(result.out == out.begin());
+  }
+  Counted::reset();
+
+  // A range containing several objects.
+  {
+    constexpr int N = 5;
+    Counted in[N] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, N> out;
+    Counted::reset();
+
+    auto result = std::ranges::uninitialized_copy_n(in, N, out.begin(), out.end());
+    ASSERT_SAME_TYPE(decltype(result), std::ranges::uninitialized_copy_n_result<Counted*, Counted*>);
+
+    assert(Counted::current_objects == N);
+    assert(Counted::total_objects == N);
+    assert(Counted::total_copies == N);
+    assert(Counted::total_moves == 0);
+    assert(std::equal(in, in + N, out.begin(), out.end()));
+    assert(result.in == in + N);
+    assert(result.out == out.end());
+
+    std::destroy(out.begin(), out.end());
+  }
+  Counted::reset();
+
+  // An exception is thrown while objects are being created -- the existing objects should stay
+  // valid. (iterator, sentinel) overload.
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  {
+    constexpr int M = 3;
+    constexpr int N = 5;
+    Counted in[N] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Counted out[N] = {Counted(6), Counted(7), Counted(8), Counted(9), Counted(10)};
+    Counted::reset();
+
+    Counted::throw_on = M; // When constructing out[3].
+    try {
+      std::ranges::uninitialized_copy_n(in, N, out, out + N);
+      assert(false);
+    } catch (...) {
+    }
+    assert(Counted::current_objects == 0);
+    assert(Counted::total_objects == M);
+    assert(Counted::total_copies == M);
+    assert(Counted::total_moves == 0);
+
+    assert(out[4].value == 10);
+  }
+  Counted::reset();
+
+#endif // TEST_HAS_NO_EXCEPTIONS
+
+  // Works with const iterators.
+  {
+    constexpr int N = 5;
+    Counted in[N] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, N> out;
+    Counted::reset();
+
+    std::ranges::uninitialized_copy_n(in, N, out.cbegin(), out.cend());
+    assert(Counted::current_objects == N);
+    assert(Counted::total_objects == N);
+    assert(std::equal(in, in + N, out.begin(), out.end()));
+
+    std::destroy(out.begin(), out.end());
+  }
+  Counted::reset();
+
+  // Conversions.
+  {
+    constexpr int N = 3;
+    double in[N] = {1.0, 2.0, 3.0};
+    Buffer<int, N> out;
+
+    std::ranges::uninitialized_copy_n(in, N, out.begin(), out.end());
+    assert(std::equal(in, in + N, out.begin(), out.end()));
+  }
+
+  // Destination range is shorter than the source range.
+  {
+    constexpr int M = 3;
+    constexpr int N = 5;
+    Counted in[N] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, M> out;
+    Counted::reset();
+
+    auto result = std::ranges::uninitialized_copy_n(in, N, out.begin(), out.end());
+    assert(Counted::current_objects == M);
+    assert(Counted::total_objects == M);
+    assert(Counted::total_copies == M);
+    assert(Counted::total_moves == 0);
+
+    assert(std::equal(in, in + M, out.begin(), out.end()));
+    assert(result.in == in + M);
+    assert(result.out == out.end());
+  }
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.fill.n/ranges_uninitialized_fill_n.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.fill.n/ranges_uninitialized_fill_n.pass.cpp
index a93ab532f2cea..9807f1d3ab498 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.fill.n/ranges_uninitialized_fill_n.pass.cpp
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.fill.n/ranges_uninitialized_fill_n.pass.cpp
@@ -27,14 +27,14 @@
 #include "test_macros.h"
 #include "test_iterators.h"
 
+// TODO(varconst): consolidate the ADL checks into a single file.
 // Because this is a variable and not a function, it's guaranteed that ADL won't be used. However,
 // implementations are allowed to use a 
diff erent mechanism to achieve this effect, so this check is
 // libc++-specific.
 LIBCPP_STATIC_ASSERT(std::is_class_v<decltype(std::ranges::uninitialized_fill_n)>);
 
 struct NotConvertibleFromInt {};
-static_assert(!std::is_invocable_v<decltype(std::ranges::uninitialized_fill_n), NotConvertibleFromInt*,
-                                   NotConvertibleFromInt*, int>);
+static_assert(!std::is_invocable_v<decltype(std::ranges::uninitialized_fill_n), NotConvertibleFromInt*, size_t, int>);
 
 int main(int, char**) {
   constexpr int value = 42;

diff  --git a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.fill/ranges_uninitialized_fill.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.fill/ranges_uninitialized_fill.pass.cpp
index d9135a036eebb..e6318297ad759 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.fill/ranges_uninitialized_fill.pass.cpp
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.fill/ranges_uninitialized_fill.pass.cpp
@@ -31,6 +31,7 @@
 #include "test_macros.h"
 #include "test_iterators.h"
 
+// TODO(varconst): consolidate the ADL checks into a single file.
 // Because this is a variable and not a function, it's guaranteed that ADL won't be used. However,
 // implementations are allowed to use a 
diff erent mechanism to achieve this effect, so this check is
 // libc++-specific.
@@ -187,8 +188,7 @@ int main(int, char**) {
 
     Counted::throw_on = N; // When constructing the fourth object.
     try {
-      auto range = std::ranges::subrange(buf.begin(), buf.end());
-      std::ranges::uninitialized_fill(range, x);
+      std::ranges::uninitialized_fill(buf, x);
     } catch (...) {
     }
     assert(Counted::current_objects == 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
new file mode 100644
index 0000000000000..934ac6a4f23fe
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/ranges_uninitialized_move.pass.cpp
@@ -0,0 +1,428 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts, libcpp-has-no-incomplete-ranges
+
+// <memory>
+//
+// template<input_iterator InputIterator, nothrow-forward-iterator OutputIterator, nothrow-sentinel-for<OutputIterator> Sentinel>
+// requires constructible_from<iter_value_t<OutputIterator>, iter_rvalue_reference_t<InputIterator>>
+// ranges::uninitialized_move_n_result<InputIterator, OutputIterator>
+// ranges::uninitialized_move_n(InputIterator ifirst, iter_
diff erence_t<InputIterator> n, OutputIterator ofirst, Sentinel olast); // since C++20
+
+
+#include <algorithm>
+#include <cassert>
+#include <iterator>
+#include <memory>
+#include <ranges>
+#include <type_traits>
+#include <utility>
+
+#include "../buffer.h"
+#include "../counted.h"
+#include "test_macros.h"
+#include "test_iterators.h"
+
+// TODO(varconst): consolidate the ADL checks into a single file.
+// Because this is a variable and not a function, it's guaranteed that ADL won't be used. However,
+// implementations are allowed to use a 
diff erent mechanism to achieve this effect, so this check is
+// libc++-specific.
+LIBCPP_STATIC_ASSERT(std::is_class_v<decltype(std::ranges::uninitialized_move)>);
+
+static_assert(std::is_invocable_v<decltype(std::ranges::uninitialized_move), int*, int*, long*, long*>);
+struct NotConvertibleFromInt {};
+static_assert(!std::is_invocable_v<decltype(std::ranges::uninitialized_move), int*, int*, NotConvertibleFromInt*,
+                                   NotConvertibleFromInt*>);
+
+namespace adl {
+
+static int iter_move_invocations = 0;
+
+template <class T>
+struct Iterator {
+  using value_type = T;
+  using 
diff erence_type = int;
+  using iterator_concept = std::input_iterator_tag;
+
+  T* ptr = nullptr;
+
+  Iterator() = default;
+  explicit Iterator(int* p) : ptr(p) {}
+
+  T& operator*() const { return *ptr; }
+
+  Iterator& operator++() { ++ptr; return *this; }
+  Iterator operator++(int) {
+    Iterator prev = *this;
+    ++ptr;
+    return prev;
+  }
+
+  friend T&& iter_move(Iterator iter) {
+    ++iter_move_invocations;
+    return std::move(*iter);
+  }
+
+  friend bool operator==(const Iterator& lhs, const Iterator& rhs) { return lhs.ptr == rhs.ptr; }
+};
+
+} // namespace adl
+
+int main(int, char**) {
+  // An empty range -- no default constructors should be invoked.
+  {
+    Counted in[] = {Counted()};
+    Buffer<Counted, 1> out;
+    Counted::reset();
+
+    {
+      auto result = std::ranges::uninitialized_move(in, in, out.begin(), out.end());
+      assert(Counted::current_objects == 0);
+      assert(Counted::total_objects == 0);
+      assert(Counted::total_copies == 0);
+      assert(result.in == in);
+      assert(result.out == out.begin());
+    }
+
+    {
+      std::ranges::empty_view<Counted> view;
+      auto result = std::ranges::uninitialized_move(view, out);
+      assert(Counted::current_objects == 0);
+      assert(Counted::total_objects == 0);
+      assert(Counted::total_copies == 0);
+      assert(result.in == view.begin());
+      assert(result.out == out.begin());
+    }
+
+    {
+      forward_iterator<Counted*> it(in);
+      std::ranges::subrange range(it, sentinel_wrapper<forward_iterator<Counted*>>(it));
+
+      auto result = std::ranges::uninitialized_move(range.begin(), range.end(), out.begin(), out.end());
+      assert(Counted::current_objects == 0);
+      assert(Counted::total_objects == 0);
+      assert(Counted::total_copies == 0);
+      assert(result.in == it);
+      assert(result.out == out.begin());
+    }
+
+    {
+      forward_iterator<Counted*> it(in);
+      std::ranges::subrange range(it, sentinel_wrapper<forward_iterator<Counted*>>(it));
+
+      auto result = std::ranges::uninitialized_move(range, out);
+      assert(Counted::current_objects == 0);
+      assert(Counted::total_objects == 0);
+      assert(Counted::total_copies == 0);
+      assert(result.in == it);
+      assert(result.out == out.begin());
+    }
+    Counted::reset();
+  }
+
+  // A range containing several objects, (iter, sentinel) overload.
+  {
+    constexpr int N = 5;
+    Counted in[N] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, N> out;
+    Counted::reset();
+
+    auto result = std::ranges::uninitialized_move(in, in + N, out.begin(), out.end());
+    ASSERT_SAME_TYPE(decltype(result), std::ranges::uninitialized_move_result<Counted*, Counted*>);
+    assert(Counted::current_objects == N);
+    assert(Counted::total_objects == N);
+    assert(Counted::total_moves == N);
+    assert(Counted::total_copies == 0);
+
+    assert(std::equal(in, in + N, out.begin(), out.end()));
+    assert(result.in == in + N);
+    assert(result.out == out.end());
+
+    std::destroy(out.begin(), out.end());
+  }
+  Counted::reset();
+
+  // A range containing several objects, (range) overload.
+  {
+    constexpr int N = 5;
+    Counted in[N] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, N> out;
+    Counted::reset();
+
+    std::ranges::subrange range(in, in + N);
+    auto result = std::ranges::uninitialized_move(range, out);
+    ASSERT_SAME_TYPE(decltype(result), std::ranges::uninitialized_move_result<Counted*, Counted*>);
+
+    assert(Counted::current_objects == N);
+    assert(Counted::total_objects == N);
+    assert(Counted::total_moves == N);
+    assert(Counted::total_copies == 0);
+    assert(std::equal(in, in + N, out.begin(), out.end()));
+
+    assert(result.in == in + N);
+    assert(result.out == out.end());
+
+    std::destroy(out.begin(), out.end());
+  }
+  Counted::reset();
+
+  // Using `counted_iterator`.
+  {
+    constexpr int N = 3;
+    Counted in[] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, 5> out;
+    Counted::reset();
+
+    std::counted_iterator iter(in, N);
+    auto result = std::ranges::uninitialized_move(iter, std::default_sentinel, out.begin(), out.end());
+
+    assert(Counted::current_objects == N);
+    assert(Counted::total_objects == N);
+    assert(Counted::total_moves == N);
+    assert(Counted::total_copies == 0);
+    assert(std::equal(in, in + N, out.begin(), out.begin() + N));
+
+    assert(result.in == iter + N);
+    assert(result.out == out.begin() + N);
+
+    std::destroy(out.begin(), out.begin() + N);
+  }
+  Counted::reset();
+
+  // Using `views::counted`.
+  {
+    constexpr int N = 3;
+    Counted in[] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, 5> out;
+    Counted::reset();
+
+    auto view = std::views::counted(in, N);
+    auto result = std::ranges::uninitialized_move(view, out);
+
+    assert(Counted::current_objects == N);
+    assert(Counted::total_objects == N);
+    assert(Counted::total_moves == N);
+    assert(Counted::total_copies == 0);
+    assert(std::equal(in, in + N, out.begin(), out.begin() + N));
+
+    assert(result.in == view.begin() + N);
+    assert(result.out == out.begin() + N);
+
+    std::destroy(out.begin(), out.begin() + N);
+  }
+  Counted::reset();
+
+  // Using `reverse_view`.
+  {
+    constexpr int N = 3;
+    Counted in[] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, 5> out;
+    Counted::reset();
+
+    std::ranges::subrange range(in, in + N);
+    auto view = std::ranges::views::reverse(range);
+    auto result = std::ranges::uninitialized_move(view, out);
+
+    assert(Counted::current_objects == N);
+    assert(Counted::total_objects == N);
+    assert(Counted::total_moves == N);
+    assert(Counted::total_copies == 0);
+
+    Counted expected[N] = {Counted(3), Counted(2), Counted(1)};
+    assert(std::equal(out.begin(), out.begin() + N, expected, expected + N));
+
+    assert(result.in == view.begin() + N);
+    assert(result.out == out.begin() + N);
+
+    std::destroy(out.begin(), out.begin() + N);
+  }
+  Counted::reset();
+
+  // Any existing values should be overwritten by move constructors.
+  {
+    constexpr int N = 5;
+    int in[N] = {1, 2, 3, 4, 5};
+    int out[N] = {6, 7, 8, 9, 10};
+    assert(!std::equal(in, in + N, in, out + N));
+
+    std::ranges::uninitialized_move(in, in + 1, out, out + N);
+    assert(out[0] == 1);
+    assert(out[1] == 7);
+
+    std::ranges::uninitialized_move(in, in + N, out, out + N);
+    assert(std::equal(in, in + N, out, out + N));
+  }
+
+  // An exception is thrown while objects are being created -- check that the objects in the source
+  // range have been moved from. (iterator, sentinel) overload.
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  {
+    constexpr int N = 3;
+    Counted in[] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, 5> out;
+    Counted::reset();
+
+    Counted::throw_on = N; // When constructing out[3].
+    try {
+      std::ranges::uninitialized_move(in, in + 5, out.begin(), out.end());
+      assert(false);
+    } catch (...) {
+    }
+    assert(Counted::current_objects == 0);
+    assert(Counted::total_objects == N);
+    assert(Counted::total_moves == N);
+    assert(Counted::total_copies == 0);
+
+    assert(std::all_of(in, in + 1, [](const auto& e) { return e.moved_from; }));
+    assert(std::none_of(in + N, in + 5, [](const auto& e) { return e.moved_from; }));
+
+    std::destroy(out.begin(), out.begin() + N);
+  }
+  Counted::reset();
+
+  // An exception is thrown while objects are being created -- check that the objects in the source
+  // range have been moved from. (range) overload.
+  {
+    constexpr int N = 3;
+    Counted in[] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, 5> out;
+    Counted::reset();
+
+    Counted::throw_on = N; // When constructing out[3].
+    try {
+      std::ranges::uninitialized_move(in, out);
+      assert(false);
+    } catch (...) {
+    }
+    assert(Counted::current_objects == 0);
+    assert(Counted::total_objects == N);
+    assert(Counted::total_moves == N);
+    assert(Counted::total_copies == 0);
+
+    assert(std::all_of(in, in + 1, [](const auto& e) { return e.moved_from; }));
+    assert(std::none_of(in + N, in + 5, [](const auto& e) { return e.moved_from; }));
+
+    std::destroy(out.begin(), out.begin() + N);
+  }
+  Counted::reset();
+#endif // TEST_HAS_NO_EXCEPTIONS
+
+  // Works with const iterators, (iter, sentinel) overload.
+  {
+    constexpr int N = 5;
+    Counted in[N] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, N> out;
+    Counted::reset();
+
+    std::ranges::uninitialized_move(in, in + N, out.cbegin(), out.cend());
+    assert(Counted::current_objects == N);
+    assert(Counted::total_objects == N);
+    assert(std::equal(in, in + N, out.begin(), out.end()));
+
+    std::destroy(out.begin(), out.end());
+  }
+  Counted::reset();
+
+  // Works with const iterators, (range) overload.
+  {
+    constexpr int N = 5;
+    Counted in[N] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, N> out;
+    Counted::reset();
+
+    std::ranges::subrange out_range (out.cbegin(), out.cend());
+    std::ranges::uninitialized_move(in, out_range);
+    assert(Counted::current_objects == N);
+    assert(Counted::total_objects == N);
+    assert(std::equal(in, in + N, out.begin(), out.end()));
+
+    std::destroy(out.begin(), out.end());
+  }
+  Counted::reset();
+
+  // Conversions, (iter, sentinel) overload.
+  {
+    constexpr int N = 3;
+    double in[N] = {1.0, 2.0, 3.0};
+    Buffer<int, N> out;
+
+    std::ranges::uninitialized_move(in, in + N, out.begin(), out.end());
+    assert(std::equal(in, in + N, out.begin(), out.end()));
+  }
+
+  // Conversions, (range) overload.
+  {
+    constexpr int N = 3;
+    double in[N] = {1.0, 2.0, 3.0};
+    Buffer<int, N> out;
+
+    std::ranges::uninitialized_move(in, out);
+    assert(std::equal(in, in + N, out.begin(), out.end()));
+  }
+
+  // Destination range is shorter than the source range, (iter, sentinel) overload.
+  {
+    constexpr int M = 3;
+    constexpr int N = 5;
+    Counted in[N] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, M> out;
+    Counted::reset();
+
+    auto result = std::ranges::uninitialized_move(in, in + N, out.begin(), out.end());
+    assert(Counted::current_objects == M);
+    assert(Counted::total_objects == M);
+    assert(Counted::total_moves == M);
+    assert(Counted::total_copies == 0);
+
+    assert(std::equal(in, in + M, out.begin(), out.end()));
+    assert(result.in == in + M);
+    assert(result.out == out.end());
+  }
+
+  // Destination range is shorter than the source range, (range) overload.
+  {
+    constexpr int M = 3;
+    constexpr int N = 5;
+    Counted in[N] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, M> out;
+    Counted::reset();
+
+    std::ranges::subrange range(in, in + N);
+    auto result = std::ranges::uninitialized_move(range, out);
+    assert(Counted::current_objects == M);
+    assert(Counted::total_objects == M);
+    assert(Counted::total_moves == M);
+    assert(Counted::total_copies == 0);
+
+    assert(std::equal(in, in + M, out.begin(), out.end()));
+    assert(result.in == in + M);
+    assert(result.out == out.end());
+  }
+
+  // Ensure the `iter_move` customization point is being used.
+  {
+    constexpr int N = 3;
+    int in[N] = {1, 2, 3};
+    Buffer<int, N> out;
+    adl::Iterator<int> begin(in);
+    adl::Iterator<int> end(in + N);
+
+    std::ranges::uninitialized_move(begin, end, out.begin(), out.end());
+    assert(adl::iter_move_invocations == 3);
+    adl::iter_move_invocations = 0;
+
+    std::ranges::subrange range(begin, end);
+    std::ranges::uninitialized_move(range, out);
+    assert(adl::iter_move_invocations == 3);
+    adl::iter_move_invocations = 0;
+  }
+
+  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
new file mode 100644
index 0000000000000..9c6691de92297
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/ranges_uninitialized_move_n.pass.cpp
@@ -0,0 +1,204 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts, libcpp-has-no-incomplete-ranges
+
+// <memory>
+//
+// template<input_iterator I, nothrow-forward-iterator O, nothrow-sentinel-for<O> S>
+//   requires constructible_from<iter_value_t<O>, iter_reference_t<I>>
+// uninitialized_copy_n_result<I, O> uninitialized_copy_n(I ifirst, iter_
diff erence_t<I> n, O ofirst, S olast); // since C++20
+
+#include <algorithm>
+#include <cassert>
+#include <iterator>
+#include <memory>
+#include <ranges>
+#include <type_traits>
+
+#include "../buffer.h"
+#include "../counted.h"
+#include "test_macros.h"
+#include "test_iterators.h"
+
+// TODO(varconst): consolidate the ADL checks into a single file.
+// Because this is a variable and not a function, it's guaranteed that ADL won't be used. However,
+// implementations are allowed to use a 
diff erent mechanism to achieve this effect, so this check is
+// libc++-specific.
+LIBCPP_STATIC_ASSERT(std::is_class_v<decltype(std::ranges::uninitialized_move_n)>);
+
+static_assert(std::is_invocable_v<decltype(std::ranges::uninitialized_move_n), int*, size_t, long*, long*>);
+struct NotConvertibleFromInt {};
+static_assert(!std::is_invocable_v<decltype(std::ranges::uninitialized_move_n), int*, size_t, NotConvertibleFromInt*,
+                                   NotConvertibleFromInt*>);
+
+namespace adl {
+
+static int iter_move_invocations = 0;
+
+template <class T>
+struct Iterator {
+  using value_type = T;
+  using 
diff erence_type = int;
+  using iterator_concept = std::input_iterator_tag;
+
+  T* ptr = nullptr;
+
+  Iterator() = default;
+  explicit Iterator(int* p) : ptr(p) {}
+
+  T& operator*() const { return *ptr; }
+
+  Iterator& operator++() { ++ptr; return *this; }
+  Iterator operator++(int) {
+    Iterator prev = *this;
+    ++ptr;
+    return prev;
+  }
+
+  friend T&& iter_move(Iterator iter) {
+    ++iter_move_invocations;
+    return std::move(*iter);
+  }
+
+  friend bool operator==(const Iterator& lhs, const Iterator& rhs) { return lhs.ptr == rhs.ptr; }
+};
+
+} // namespace adl
+
+int main(int, char**) {
+  // An empty range -- no default constructors should be invoked.
+  {
+    Counted in[] = {Counted()};
+    Buffer<Counted, 1> out;
+    Counted::reset();
+
+    auto result = std::ranges::uninitialized_move_n(in, 0, out.begin(), out.end());
+    assert(Counted::current_objects == 0);
+    assert(Counted::total_objects == 0);
+    assert(Counted::total_copies == 0);
+    assert(result.in == in);
+    assert(result.out == out.begin());
+  }
+  Counted::reset();
+
+  // A range containing several objects.
+  {
+    constexpr int N = 5;
+    Counted in[N] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, N> out;
+    Counted::reset();
+
+    auto result = std::ranges::uninitialized_move_n(in, N, out.begin(), out.end());
+    ASSERT_SAME_TYPE(decltype(result), std::ranges::uninitialized_move_n_result<Counted*, Counted*>);
+
+    assert(Counted::current_objects == N);
+    assert(Counted::total_objects == N);
+    assert(Counted::total_moves == N);
+    assert(Counted::total_copies == 0);
+
+    assert(std::equal(in, in + N, out.begin(), out.end()));
+    assert(result.in == in + N);
+    assert(result.out == out.end());
+
+    std::destroy(out.begin(), out.end());
+  }
+  Counted::reset();
+
+  // An exception is thrown while objects are being created -- the existing objects should stay
+  // valid. (iterator, sentinel) overload.
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  {
+    constexpr int N = 3;
+    Counted in[] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, 5> out;
+    Counted::reset();
+
+    Counted::throw_on = N; // When constructing out[3].
+    try {
+      std::ranges::uninitialized_move_n(in, 5, out.begin(), out.end());
+      assert(false);
+    } catch (...) {
+    }
+    assert(Counted::current_objects == 0);
+    assert(Counted::total_objects == N);
+    assert(Counted::total_moves == N);
+    assert(Counted::total_copies == 0);
+
+    std::destroy(out.begin(), out.begin() + N);
+  }
+  Counted::reset();
+
+#endif // TEST_HAS_NO_EXCEPTIONS
+
+  // Works with const iterators.
+  {
+    constexpr int N = 5;
+    Counted in[N] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, N> out;
+    Counted::reset();
+
+    std::ranges::uninitialized_move_n(in, N, out.cbegin(), out.cend());
+    assert(Counted::current_objects == N);
+    assert(Counted::total_objects == N);
+    assert(std::equal(in, in + N, out.begin(), out.end()));
+
+    std::destroy(out.begin(), out.end());
+  }
+  Counted::reset();
+
+  // Conversions.
+  {
+    constexpr int N = 3;
+    double in[N] = {1.0, 2.0, 3.0};
+    Buffer<int, N> out;
+
+    std::ranges::uninitialized_move_n(in, N, out.begin(), out.end());
+    assert(std::equal(in, in + N, out.begin(), out.end()));
+  }
+
+  // Destination range is shorter than the source range.
+  {
+    constexpr int M = 3;
+    constexpr int N = 5;
+    Counted in[N] = {Counted(1), Counted(2), Counted(3), Counted(4), Counted(5)};
+    Buffer<Counted, M> out;
+    Counted::reset();
+
+    auto result = std::ranges::uninitialized_move_n(in, N, out.begin(), out.end());
+    assert(Counted::current_objects == M);
+    assert(Counted::total_objects == M);
+    assert(Counted::total_moves == M);
+    assert(Counted::total_copies == 0);
+
+    assert(std::equal(in, in + M, out.begin(), out.end()));
+    assert(result.in == in + M);
+    assert(result.out == out.end());
+  }
+
+  // Ensure the `iter_move` customization point is being used.
+  {
+    constexpr int N = 3;
+    int in[N] = {1, 2, 3};
+    Buffer<int, N> out;
+    adl::Iterator<int> begin(in);
+    adl::Iterator<int> end(in + N);
+
+    std::ranges::uninitialized_move(begin, end, out.begin(), out.end());
+    assert(adl::iter_move_invocations == 3);
+    adl::iter_move_invocations = 0;
+
+    std::ranges::subrange range(begin, end);
+    std::ranges::uninitialized_move(range, out);
+    assert(adl::iter_move_invocations == 3);
+    adl::iter_move_invocations = 0;
+  }
+
+  return 0;
+}


        


More information about the libcxx-commits mailing list