[libcxx-commits] [libcxx] [libc++] Treat negative counts in copy_n & friends as no-ops (PR #207086)

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Thu Jul 2 09:33:27 PDT 2026


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

>From 64b22d4e38f896e39762ccfd1f011f0dd16dfddb Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Mon, 29 Jun 2026 14:57:40 -0400
Subject: [PATCH 1/5] [libc++] Treat negative counts in copy_n & friends as
 no-ops

The standard specifies copy_n, fill_n and generate_n (both std and
ranges) to be no-ops when passed a negative count. This patch fixes
libc++ to abide by that requirement, with tests.

It's worth noting that std::for_each_n makes n > 0 a precondition
instead: we add the check and tests for it.

Fixes #193613
---
 libcxx/include/__algorithm/copy_n.h           | 17 +++++----
 libcxx/include/__algorithm/for_each_n.h       |  2 +
 libcxx/include/__algorithm/ranges_copy_n.h    |  2 +
 .../include/__algorithm/ranges_for_each_n.h   |  2 +
 libcxx/include/__bit_reference                | 17 +++++----
 .../alg.foreach/assert.for_each_n.pass.cpp    | 35 ++++++++++++++++++
 .../assert.ranges.for_each_n.pass.cpp         | 37 +++++++++++++++++++
 .../alg.copy/copy_n.pass.cpp                  | 16 ++++++++
 .../alg.copy/ranges.copy_n.pass.cpp           | 17 +++++++++
 .../alg.fill/fill_n.pass.cpp                  | 15 ++++++++
 .../alg.fill/ranges.fill_n.pass.cpp           | 15 ++++++++
 .../alg.generate/generate_n.pass.cpp          |  7 ++++
 .../alg.generate/ranges_generate_n.pass.cpp   | 15 +++++++-
 13 files changed, 181 insertions(+), 16 deletions(-)
 create mode 100644 libcxx/test/libcxx/algorithms/alg.nonmodifying/alg.foreach/assert.for_each_n.pass.cpp
 create mode 100644 libcxx/test/libcxx/algorithms/alg.nonmodifying/alg.foreach/assert.ranges.for_each_n.pass.cpp

diff --git a/libcxx/include/__algorithm/copy_n.h b/libcxx/include/__algorithm/copy_n.h
index ac52b2b686c28..a0ea02121b70b 100644
--- a/libcxx/include/__algorithm/copy_n.h
+++ b/libcxx/include/__algorithm/copy_n.h
@@ -42,7 +42,7 @@ template <class _AlgPolicy,
           __enable_if_t<!__has_random_access_iterator_category<_InIter>::value, int> = 0>
 _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 __in_out_result<_InIter, _OutIter>
 __copy_n(_InIter __first, typename _IterOps<_AlgPolicy>::template __difference_type<_InIter> __n, _OutIter __result) {
-  while (__n != 0) {
+  while (__n > 0) {
     *__result = *__first;
     ++__first;
     ++__result;
@@ -63,14 +63,15 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _OutputIterator
 copy_n(_InputIterator __first, _Size __n, _OutputIterator __result) {
   using _IntegralSize       = decltype(std::__convert_to_integral(__n));
   _IntegralSize __converted = __n;
-  if (__converted > 0) {
+  if (__converted < 0) [[__unlikely__]]
+    return __result;
+
+  *__result = *__first;
+  ++__result;
+  for (--__converted; __converted > 0; --__converted) {
+    ++__first;
     *__result = *__first;
     ++__result;
-    for (--__converted; __converted > 0; --__converted) {
-      ++__first;
-      *__result = *__first;
-      ++__result;
-    }
   }
   return __result;
 }
@@ -83,6 +84,8 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _OutputIterator
 copy_n(_InputIterator __first, _Size __n, _OutputIterator __result) {
   using _IntegralSize       = decltype(std::__convert_to_integral(__n));
   _IntegralSize __converted = __n;
+  if (__converted < 0) [[__unlikely__]]
+    return __result;
   return std::__copy_n<_ClassicAlgPolicy>(__first, __iterator_difference_type<_InputIterator>(__converted), __result)
       .__out_;
 }
diff --git a/libcxx/include/__algorithm/for_each_n.h b/libcxx/include/__algorithm/for_each_n.h
index 7ff3f9b157c02..9b393b307f323 100644
--- a/libcxx/include/__algorithm/for_each_n.h
+++ b/libcxx/include/__algorithm/for_each_n.h
@@ -12,6 +12,7 @@
 
 #include <__algorithm/for_each.h>
 #include <__algorithm/for_each_n_segment.h>
+#include <__assert>
 #include <__config>
 #include <__functional/identity.h>
 #include <__iterator/iterator_traits.h>
@@ -63,6 +64,7 @@ __for_each_n(_InputIterator __first, _Size __orig_n, _Func&& __f, _Proj& __proj)
 template <class _InputIterator, class _Size, class _Func>
 inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _InputIterator
 for_each_n(_InputIterator __first, _Size __orig_n, _Func __f) {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__orig_n >= 0, "for_each_n requires a non-negative count");
   __identity __proj;
   return std::__for_each_n(__first, __orig_n, __f, __proj);
 }
diff --git a/libcxx/include/__algorithm/ranges_copy_n.h b/libcxx/include/__algorithm/ranges_copy_n.h
index 1720ac358fa4d..c153e74009801 100644
--- a/libcxx/include/__algorithm/ranges_copy_n.h
+++ b/libcxx/include/__algorithm/ranges_copy_n.h
@@ -38,6 +38,8 @@ struct __copy_n {
     requires indirectly_copyable<_Ip, _Op>
   _LIBCPP_HIDE_FROM_ABI constexpr copy_n_result<_Ip, _Op>
   operator()(_Ip __first, iter_difference_t<_Ip> __n, _Op __result) const {
+    if (__n < 0) [[__unlikely__]]
+      return {std::move(__first), std::move(__result)};
     return std::__copy_n<_RangeAlgPolicy>(std::move(__first), __n, std::move(__result));
   }
 };
diff --git a/libcxx/include/__algorithm/ranges_for_each_n.h b/libcxx/include/__algorithm/ranges_for_each_n.h
index 3aab1b79c10a1..126dc481ec723 100644
--- a/libcxx/include/__algorithm/ranges_for_each_n.h
+++ b/libcxx/include/__algorithm/ranges_for_each_n.h
@@ -11,6 +11,7 @@
 
 #include <__algorithm/for_each_n.h>
 #include <__algorithm/in_fun_result.h>
+#include <__assert>
 #include <__config>
 #include <__functional/identity.h>
 #include <__iterator/concepts.h>
@@ -40,6 +41,7 @@ struct __for_each_n {
   template <input_iterator _Iter, class _Proj = identity, indirectly_unary_invocable<projected<_Iter, _Proj>> _Func>
   _LIBCPP_HIDE_FROM_ABI constexpr for_each_n_result<_Iter, _Func>
   operator()(_Iter __first, iter_difference_t<_Iter> __count, _Func __func, _Proj __proj = {}) const {
+    _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__count >= 0, "for_each_n requires a non-negative count");
     auto __last = std::__for_each_n(std::move(__first), __count, __func, __proj);
     return {std::move(__last), std::move(__func)};
   }
diff --git a/libcxx/include/__bit_reference b/libcxx/include/__bit_reference
index 692686f1584d0..0083714b2cafe 100644
--- a/libcxx/include/__bit_reference
+++ b/libcxx/include/__bit_reference
@@ -424,8 +424,8 @@ public:
   }
 
 #if _LIBCPP_STD_VER <= 17
-  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 friend bool
-  operator!=(const __bit_iterator& __x, const __bit_iterator& __y) {
+  _LIBCPP_HIDE_FROM_ABI
+  _LIBCPP_CONSTEXPR_SINCE_CXX20 friend bool operator!=(const __bit_iterator& __x, const __bit_iterator& __y) {
     return !(__x == __y);
   }
 
@@ -573,12 +573,13 @@ struct __specialized_algorithm<_Algorithm::__fill_n, __single_iterator<__bit_ite
   template <class _Size, class _Tp>
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 static __bit_iterator<_Cp, false>
   operator()(__bit_iterator<_Cp, false> __first, _Size __n, const _Tp& __value) {
-    if (__n > 0) {
-      if (__value)
-        __impl<true>(__first, __n);
-      else
-        __impl<false>(__first, __n);
-    }
+    if (__n < 0) [[__unlikely__]]
+      return __first;
+
+    if (__value)
+      __impl<true>(__first, __n);
+    else
+      __impl<false>(__first, __n);
     return __first + __n;
   }
 };
diff --git a/libcxx/test/libcxx/algorithms/alg.nonmodifying/alg.foreach/assert.for_each_n.pass.cpp b/libcxx/test/libcxx/algorithms/alg.nonmodifying/alg.foreach/assert.for_each_n.pass.cpp
new file mode 100644
index 0000000000000..53e59401430fa
--- /dev/null
+++ b/libcxx/test/libcxx/algorithms/alg.nonmodifying/alg.foreach/assert.for_each_n.pass.cpp
@@ -0,0 +1,35 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// <algorithm>
+
+// template<class InputIterator, class Size, class Function>
+//    constexpr InputIterator
+//    for_each_n(InputIterator first, Size n, Function f);
+//
+// [alg.foreach] requires `n >= 0`; passing a negative count is a precondition violation.
+
+// REQUIRES: has-unix-headers
+// UNSUPPORTED: c++03, c++11, c++14
+// UNSUPPORTED: libcpp-hardening-mode=none
+// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
+
+#include <algorithm>
+
+#include "check_assertion.h"
+
+int main(int, char**) {
+  int a[] = {1, 2, 3};
+
+  TEST_LIBCPP_ASSERT_FAILURE(
+      std::for_each_n(a, -1, [](int) {}), "for_each_n requires a non-negative count");
+  TEST_LIBCPP_ASSERT_FAILURE(
+      std::for_each_n(a, -10000000, [](int) {}), "for_each_n requires a non-negative count");
+
+  return 0;
+}
diff --git a/libcxx/test/libcxx/algorithms/alg.nonmodifying/alg.foreach/assert.ranges.for_each_n.pass.cpp b/libcxx/test/libcxx/algorithms/alg.nonmodifying/alg.foreach/assert.ranges.for_each_n.pass.cpp
new file mode 100644
index 0000000000000..e57f342e760a6
--- /dev/null
+++ b/libcxx/test/libcxx/algorithms/alg.nonmodifying/alg.foreach/assert.ranges.for_each_n.pass.cpp
@@ -0,0 +1,37 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// <algorithm>
+
+// template<input_iterator I, class Proj = identity,
+//          indirectly_unary_invocable<projected<I, Proj>> Fun>
+//   constexpr ranges::for_each_n_result<I, Fun>
+//     ranges::for_each_n(I first, iter_difference_t<I> n, Fun f, Proj proj = {});
+//
+// [alg.foreach] requires `n >= 0`; passing a negative count is a precondition violation.
+
+// REQUIRES: has-unix-headers
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-hardening-mode=none
+// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
+
+#include <algorithm>
+#include <array>
+
+#include "check_assertion.h"
+
+int main(int, char**) {
+  std::array a = {1, 2, 3};
+
+  TEST_LIBCPP_ASSERT_FAILURE(
+      std::ranges::for_each_n(a.begin(), -1, [](int) {}), "for_each_n requires a non-negative count");
+  TEST_LIBCPP_ASSERT_FAILURE(
+      std::ranges::for_each_n(a.begin(), -10000000, [](int) {}), "for_each_n requires a non-negative count");
+
+  return 0;
+}
diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/copy_n.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/copy_n.pass.cpp
index 183f38c1ce117..52d99f018110b 100644
--- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/copy_n.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/copy_n.pass.cpp
@@ -61,6 +61,15 @@ struct TestIterators {
       assert(base(r) == ib + N / 2);
       for (unsigned i = 0; i < N / 2; ++i)
         assert(ia[i] == ib[i]);
+
+      { // A negative count is a no-op that returns the unchanged output iterator.
+        // Regression test for https://llvm.org/PR193613.
+        int source[]  = {1, 2, 3};
+        int dest[]    = {-1, -2, -3};
+        OutIter ret   = std::copy_n(InIter(source), -5, OutIter(dest));
+        assert(base(ret) == dest);
+        assert(dest[0] == -1 && dest[1] == -2 && dest[2] == -3);
+      }
     }
   };
 };
@@ -81,6 +90,13 @@ TEST_CONSTEXPR_CXX20 bool test_vector_bool(std::size_t N) {
     for (std::size_t i = 0; i < N; ++i)
       assert(out[i + 4] == in[i]);
   }
+  { // Negative count test
+    std::vector<bool> source(N, true);
+    std::vector<bool> dest(N, false);
+    std::vector<bool>::iterator r = std::copy_n(source.begin(), -5, dest.begin());
+    assert(r == dest.begin());
+    assert(dest == std::vector<bool>(N, false));
+  }
 
   return true;
 }
diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_n.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_n.pass.cpp
index 132993dba76b9..5e073026ed8db 100644
--- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_n.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_n.pass.cpp
@@ -60,6 +60,16 @@ constexpr void test_iterators() {
     assert(base(ret.in) == in.data());
     assert(base(ret.out) == out.data());
   }
+
+  { // A negative count is a no-op that returns the unchanged iterators.
+    // Regression test for https://llvm.org/PR193613.
+    std::array in{1, 2, 3};
+    std::array out{-1, -2, -3};
+    std::same_as<std::ranges::in_out_result<In, Out>> auto ret = std::ranges::copy_n(In(in.data()), -5, Out(out.data()));
+    assert(base(ret.in) == in.data());
+    assert(base(ret.out) == out.data());
+    assert((out == std::array{-1, -2, -3}));
+  }
 }
 
 #if TEST_STD_VER >= 23
@@ -79,6 +89,13 @@ constexpr bool test_vector_bool(std::size_t N) {
     for (std::size_t i = 0; i < N; ++i)
       assert(out[i + 4] == in[i]);
   }
+  { // Negative count test
+    std::vector<bool> source(N, true);
+    std::vector<bool> dest(N, false);
+    auto ret = std::ranges::copy_n(source.begin(), -5, dest.begin());
+    assert(ret.out == dest.begin());
+    assert(dest == std::vector<bool>(N, false));
+  }
 
   return true;
 };
diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.fill/fill_n.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.fill/fill_n.pass.cpp
index 6d7ba98a33b65..cc9aaeb04d7e8 100644
--- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.fill/fill_n.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.fill/fill_n.pass.cpp
@@ -52,6 +52,14 @@ struct Test {
       std::array<T, 4> expected = {1, 5, 5, 4};
       test<Iter>(in, 1, 2, 5, expected);
     }
+    { // A negative count is a no-op that returns the unchanged iterator.
+      // Regression test for https://llvm.org/PR193613.
+      std::array<T, 4> in       = {1, 2, 3, 4};
+      std::array<T, 4> expected = {1, 2, 3, 4};
+      Iter it                   = std::fill_n(Iter(in.data()), -5, T(9));
+      assert(base(it) == in.data());
+      assert(in == expected);
+    }
   }
 };
 
@@ -126,6 +134,13 @@ TEST_CONSTEXPR_CXX20 bool test_vector_bool(std::size_t N) {
     }
   }
 
+  { // Negative count test
+    std::vector<bool> v(N, false);
+    std::vector<bool>::iterator r = std::fill_n(v.begin(), -5, true);
+    assert(r == v.begin());
+    assert(v == std::vector<bool>(N, false));
+  }
+
   return true;
 }
 
diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.fill/ranges.fill_n.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.fill/ranges.fill_n.pass.cpp
index 0de6f4a625136..a8eb6c4eb9737 100644
--- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.fill/ranges.fill_n.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.fill/ranges.fill_n.pass.cpp
@@ -51,6 +51,14 @@ constexpr void test_iterators() {
     auto ret = std::ranges::fill_n(It(a.data()), 0, 1);
     assert(base(ret) == a.data());
   }
+
+  { // A negative count is a no-op that returns the unchanged iterator.
+    // Regression test for https://llvm.org/PR193613.
+    int a[3]                            = {-1, -2, -3};
+    std::same_as<It> decltype(auto) ret = std::ranges::fill_n(It(a), -5, 1);
+    assert(base(ret) == a);
+    assert(a[0] == -1 && a[1] == -2 && a[2] == -3);
+  }
 }
 
 // The `ranges::{fill, fill_n}` algorithms require `vector<bool, Alloc>::iterator` to satisfy
@@ -99,6 +107,13 @@ constexpr bool test_vector_bool(std::size_t N) {
     }
   }
 
+  { // Negative count test
+    std::vector<bool> v(N, false);
+    auto r = std::ranges::fill_n(v.begin(), -5, true);
+    assert(r == v.begin());
+    assert(v == std::vector<bool>(N, false));
+  }
+
   return true;
 }
 #endif
diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.generate/generate_n.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.generate/generate_n.pass.cpp
index 525737ceacfbf..0e38a89613aa8 100644
--- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.generate/generate_n.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.generate/generate_n.pass.cpp
@@ -70,6 +70,13 @@ test()
     test2<Iter, float>();
     test2<Iter, double>();  // this is PR#35498
     test2<Iter, long double>();
+
+    { // A negative count is a no-op that returns the unchanged iterator.
+      // Regression test for https://llvm.org/PR193613.
+      int ia[] = {1, 2, 3};
+      assert(std::generate_n(Iter(ia), -5, gen_test()) == Iter(ia));
+      assert(ia[0] == 1 && ia[1] == 2 && ia[2] == 3);
+    }
 }
 
 void deque_test() {
diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.generate/ranges_generate_n.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.generate/ranges_generate_n.pass.cpp
index 33d40a39cf385..6154ab240c3d6 100644
--- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.generate/ranges_generate_n.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.generate/ranges_generate_n.pass.cpp
@@ -92,6 +92,20 @@ constexpr void test_iter() {
   test_one<Iter>(std::array{-10, -20, -30, -40, -50}, 3, gen, {1, 2, 3, -40, -50});
   // Longer sequence, n = 5.
   test_one<Iter>(std::array<int, 5>{}, 5, gen, {1, 2, 3, 4, 5});
+
+  { // A negative count is a no-op that returns the unchanged iterator.
+    // Regression test for https://llvm.org/PR193613.
+    std::array in{-10, -20, -30};
+    int called        = 0;
+    auto counting_gen = [&called] {
+      ++called;
+      return 42;
+    };
+    std::same_as<Iter> decltype(auto) result = std::ranges::generate_n(Iter(in.data()), -5, counting_gen);
+    assert(base(result) == in.data());
+    assert(called == 0);
+    assert((in == std::array{-10, -20, -30}));
+  }
 }
 
 constexpr void test_iterators() {
@@ -131,7 +145,6 @@ constexpr bool test() {
     assert(gen_invocations == N2);
   }
 
-
   return true;
 }
 

>From 892279c90e066c4af5e0ef600fe47df0ba9dbf9e Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 1 Jul 2026 17:31:39 -0400
Subject: [PATCH 2/5] Fix check

---
 libcxx/include/__bit_reference | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libcxx/include/__bit_reference b/libcxx/include/__bit_reference
index 0083714b2cafe..b07c7ddccd5a0 100644
--- a/libcxx/include/__bit_reference
+++ b/libcxx/include/__bit_reference
@@ -573,7 +573,7 @@ struct __specialized_algorithm<_Algorithm::__fill_n, __single_iterator<__bit_ite
   template <class _Size, class _Tp>
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 static __bit_iterator<_Cp, false>
   operator()(__bit_iterator<_Cp, false> __first, _Size __n, const _Tp& __value) {
-    if (__n < 0) [[__unlikely__]]
+    if (__n <= 0) [[__unlikely__]]
       return __first;
 
     if (__value)

>From 87de9ba731d496c0900154847a077591dde9c906 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 1 Jul 2026 17:36:19 -0400
Subject: [PATCH 3/5] Revert to != 0

---
 libcxx/include/__algorithm/copy_n.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libcxx/include/__algorithm/copy_n.h b/libcxx/include/__algorithm/copy_n.h
index a0ea02121b70b..7ce0ec53b0c53 100644
--- a/libcxx/include/__algorithm/copy_n.h
+++ b/libcxx/include/__algorithm/copy_n.h
@@ -42,7 +42,7 @@ template <class _AlgPolicy,
           __enable_if_t<!__has_random_access_iterator_category<_InIter>::value, int> = 0>
 _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 __in_out_result<_InIter, _OutIter>
 __copy_n(_InIter __first, typename _IterOps<_AlgPolicy>::template __difference_type<_InIter> __n, _OutIter __result) {
-  while (__n > 0) {
+  while (__n != 0) {
     *__result = *__first;
     ++__first;
     ++__result;

>From 86c6915b6d53b0ffde7b3a2ccb197b05f28c85ee Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Thu, 2 Jul 2026 12:28:02 -0400
Subject: [PATCH 4/5] Drop [[__unlikely__]]

---
 libcxx/include/__algorithm/copy_n.h        | 4 ++--
 libcxx/include/__algorithm/ranges_copy_n.h | 2 +-
 libcxx/include/__bit_reference             | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/libcxx/include/__algorithm/copy_n.h b/libcxx/include/__algorithm/copy_n.h
index 7ce0ec53b0c53..7f9a109a763ce 100644
--- a/libcxx/include/__algorithm/copy_n.h
+++ b/libcxx/include/__algorithm/copy_n.h
@@ -63,7 +63,7 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _OutputIterator
 copy_n(_InputIterator __first, _Size __n, _OutputIterator __result) {
   using _IntegralSize       = decltype(std::__convert_to_integral(__n));
   _IntegralSize __converted = __n;
-  if (__converted < 0) [[__unlikely__]]
+  if (__converted < 0)
     return __result;
 
   *__result = *__first;
@@ -84,7 +84,7 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _OutputIterator
 copy_n(_InputIterator __first, _Size __n, _OutputIterator __result) {
   using _IntegralSize       = decltype(std::__convert_to_integral(__n));
   _IntegralSize __converted = __n;
-  if (__converted < 0) [[__unlikely__]]
+  if (__converted < 0)
     return __result;
   return std::__copy_n<_ClassicAlgPolicy>(__first, __iterator_difference_type<_InputIterator>(__converted), __result)
       .__out_;
diff --git a/libcxx/include/__algorithm/ranges_copy_n.h b/libcxx/include/__algorithm/ranges_copy_n.h
index c153e74009801..7baf1932bb3d8 100644
--- a/libcxx/include/__algorithm/ranges_copy_n.h
+++ b/libcxx/include/__algorithm/ranges_copy_n.h
@@ -38,7 +38,7 @@ struct __copy_n {
     requires indirectly_copyable<_Ip, _Op>
   _LIBCPP_HIDE_FROM_ABI constexpr copy_n_result<_Ip, _Op>
   operator()(_Ip __first, iter_difference_t<_Ip> __n, _Op __result) const {
-    if (__n < 0) [[__unlikely__]]
+    if (__n < 0)
       return {std::move(__first), std::move(__result)};
     return std::__copy_n<_RangeAlgPolicy>(std::move(__first), __n, std::move(__result));
   }
diff --git a/libcxx/include/__bit_reference b/libcxx/include/__bit_reference
index b07c7ddccd5a0..23ff6eaf56684 100644
--- a/libcxx/include/__bit_reference
+++ b/libcxx/include/__bit_reference
@@ -573,7 +573,7 @@ struct __specialized_algorithm<_Algorithm::__fill_n, __single_iterator<__bit_ite
   template <class _Size, class _Tp>
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 static __bit_iterator<_Cp, false>
   operator()(__bit_iterator<_Cp, false> __first, _Size __n, const _Tp& __value) {
-    if (__n <= 0) [[__unlikely__]]
+    if (__n <= 0)
       return __first;
 
     if (__value)

>From 6088d6dfa8ef53749309e627c85783bedb3ab248 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Thu, 2 Jul 2026 12:33:12 -0400
Subject: [PATCH 5/5] Format

---
 .../alg.nonmodifying/alg.foreach/assert.for_each_n.pass.cpp | 6 ++----
 .../alg.modifying.operations/alg.copy/copy_n.pass.cpp       | 6 +++---
 .../alg.copy/ranges.copy_n.pass.cpp                         | 3 ++-
 3 files changed, 7 insertions(+), 8 deletions(-)

diff --git a/libcxx/test/libcxx/algorithms/alg.nonmodifying/alg.foreach/assert.for_each_n.pass.cpp b/libcxx/test/libcxx/algorithms/alg.nonmodifying/alg.foreach/assert.for_each_n.pass.cpp
index 53e59401430fa..5ae0dbc865b46 100644
--- a/libcxx/test/libcxx/algorithms/alg.nonmodifying/alg.foreach/assert.for_each_n.pass.cpp
+++ b/libcxx/test/libcxx/algorithms/alg.nonmodifying/alg.foreach/assert.for_each_n.pass.cpp
@@ -26,10 +26,8 @@
 int main(int, char**) {
   int a[] = {1, 2, 3};
 
-  TEST_LIBCPP_ASSERT_FAILURE(
-      std::for_each_n(a, -1, [](int) {}), "for_each_n requires a non-negative count");
-  TEST_LIBCPP_ASSERT_FAILURE(
-      std::for_each_n(a, -10000000, [](int) {}), "for_each_n requires a non-negative count");
+  TEST_LIBCPP_ASSERT_FAILURE(std::for_each_n(a, -1, [](int) {}), "for_each_n requires a non-negative count");
+  TEST_LIBCPP_ASSERT_FAILURE(std::for_each_n(a, -10000000, [](int) {}), "for_each_n requires a non-negative count");
 
   return 0;
 }
diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/copy_n.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/copy_n.pass.cpp
index 52d99f018110b..3348fcf358580 100644
--- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/copy_n.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/copy_n.pass.cpp
@@ -64,9 +64,9 @@ struct TestIterators {
 
       { // A negative count is a no-op that returns the unchanged output iterator.
         // Regression test for https://llvm.org/PR193613.
-        int source[]  = {1, 2, 3};
-        int dest[]    = {-1, -2, -3};
-        OutIter ret   = std::copy_n(InIter(source), -5, OutIter(dest));
+        int source[] = {1, 2, 3};
+        int dest[]   = {-1, -2, -3};
+        OutIter ret  = std::copy_n(InIter(source), -5, OutIter(dest));
         assert(base(ret) == dest);
         assert(dest[0] == -1 && dest[1] == -2 && dest[2] == -3);
       }
diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_n.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_n.pass.cpp
index 5e073026ed8db..c354076b63363 100644
--- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_n.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_n.pass.cpp
@@ -65,7 +65,8 @@ constexpr void test_iterators() {
     // Regression test for https://llvm.org/PR193613.
     std::array in{1, 2, 3};
     std::array out{-1, -2, -3};
-    std::same_as<std::ranges::in_out_result<In, Out>> auto ret = std::ranges::copy_n(In(in.data()), -5, Out(out.data()));
+    std::same_as<std::ranges::in_out_result<In, Out>> auto ret =
+        std::ranges::copy_n(In(in.data()), -5, Out(out.data()));
     assert(base(ret.in) == in.data());
     assert(base(ret.out) == out.data());
     assert((out == std::array{-1, -2, -3}));



More information about the libcxx-commits mailing list