[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 10:19:50 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/6] [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/6] 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/6] 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/6] 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/6] 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}));
>From d29c394231476873ae920ceaa26d498a3a96eca5 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Thu, 2 Jul 2026 12:57:25 -0400
Subject: [PATCH 6/6] [libc++] Fix bug in std::copy_n for n == 0
---
libcxx/include/__algorithm/copy_n.h | 2 +-
.../alg.copy/copy_n.pass.cpp | 50 +++++++++++++++++++
.../alg.fill/fill_n.pass.cpp | 2 +
3 files changed, 53 insertions(+), 1 deletion(-)
diff --git a/libcxx/include/__algorithm/copy_n.h b/libcxx/include/__algorithm/copy_n.h
index 7f9a109a763ce..efd5624d94f67 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)
+ if (__converted <= 0)
return __result;
*__result = *__first;
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 3348fcf358580..3e673cee21f69 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
@@ -12,9 +12,12 @@
// constexpr OutIter // constexpr after C++17
// copy_n(InIter first, InIter::difference_type n, OutIter result);
+// XFAIL: FROZEN-CXX03-HEADERS-FIXME
+
#include <algorithm>
#include <cassert>
#include <cstdint>
+#include <iterator>
#include <vector>
#include "test_macros.h"
@@ -24,6 +27,31 @@
typedef UserDefinedIntegral<unsigned> UDI;
+// A minimal single-pass input iterator that counts how many times it is advanced. Used to verify that
+// copy_n reads exactly n elements (advancing the iterator only n - 1 times).
+struct CountingInputIterator {
+ using iterator_category = std::input_iterator_tag;
+ using value_type = int;
+ using difference_type = long;
+ using pointer = const int*;
+ using reference = const int&;
+
+ const int* base_;
+ int* increments_;
+
+ TEST_CONSTEXPR_CXX14 reference operator*() const { return *base_; }
+ TEST_CONSTEXPR_CXX14 CountingInputIterator& operator++() {
+ ++base_;
+ ++*increments_;
+ return *this;
+ }
+ TEST_CONSTEXPR_CXX14 CountingInputIterator operator++(int) {
+ CountingInputIterator __tmp = *this;
+ ++*this;
+ return __tmp;
+ }
+};
+
class PaddedBase {
public:
TEST_CONSTEXPR PaddedBase(std::int16_t a, std::int8_t b) : a_(a), b_(b) {}
@@ -130,6 +158,28 @@ TEST_CONSTEXPR_CXX20 bool test() {
assert(test_vector_bool(256));
}
+ { // For a single-pass input iterator, copy_n reads exactly n elements -- advancing the iterator only
+ // n - 1 times. For n == 0, it shouldn't advance the iterator. See 99847d2bf132.
+ int in[] = {1, 2, 3, 4, 5};
+
+ { // n == 0 is a no-op
+ int out[3] = {-1, -2, -3};
+ int increments = 0;
+ int* r = std::copy_n(CountingInputIterator{in, &increments}, 0, out);
+ assert(r == out);
+ assert(increments == 0);
+ assert(out[0] == -1 && out[1] == -2 && out[2] == -3);
+ }
+ { // n > 0 advances the iterator exactly n - 1 times
+ int out[3] = {0, 0, 0};
+ int increments = 0;
+ int* r = std::copy_n(CountingInputIterator{in, &increments}, 3, out);
+ assert(r == out + 3);
+ assert(increments == 2);
+ assert(out[0] == 1 && out[1] == 2 && out[2] == 3);
+ }
+ }
+
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 cc9aaeb04d7e8..5a9214923577a 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
@@ -13,6 +13,8 @@
// constexpr OutputIterator // constexpr after C++17
// fill_n(Iter first, Size n, const T& value);
+// XFAIL: FROZEN-CXX03-HEADERS-FIXME
+
#include <algorithm>
#include <array>
#include <cassert>
More information about the libcxx-commits
mailing list