[libcxx-commits] [libcxx] [libc++][pstl] Implementation of parallel std::search_n() based on __parallel_find() (PR #214069)
Michael G. Kazakov via libcxx-commits
libcxx-commits at lists.llvm.org
Thu Aug 6 04:21:24 PDT 2026
https://github.com/mikekazakov updated https://github.com/llvm/llvm-project/pull/214069
>From 719a409d6b3be08f0d1b2d3c7cc43a508a2332a7 Mon Sep 17 00:00:00 2001
From: Michael Kazakov <mike.kazakov at gmail.com>
Date: Tue, 4 Aug 2026 21:49:59 +0100
Subject: [PATCH 1/6] Implementation of a parallel std::search_n()
---
libcxx/include/CMakeLists.txt | 1 +
libcxx/include/__algorithm/pstl.h | 44 +++++
libcxx/include/__pstl/backend_fwd.h | 7 +
libcxx/include/__pstl/backends/default.h | 4 +
libcxx/include/__pstl/backends/libdispatch.h | 5 +
libcxx/include/__pstl/backends/serial.h | 11 ++
libcxx/include/__pstl/backends/std_thread.h | 5 +
libcxx/include/__pstl/cpu_algos/search_n.h | 106 ++++++++++++
.../pstl.iterator-requirements.verify.cpp | 5 +
.../algorithms/pstl.nodiscard.verify.cpp | 4 +
.../alg.search/pstl.search_n.pass.cpp | 156 +++++++++++++++++
.../alg.search/pstl.search_n_pred.pass.cpp | 160 ++++++++++++++++++
.../pstl.exception_handling.pass.cpp | 12 ++
13 files changed, 520 insertions(+)
create mode 100644 libcxx/include/__pstl/cpu_algos/search_n.h
create mode 100644 libcxx/test/std/algorithms/alg.nonmodifying/alg.search/pstl.search_n.pass.cpp
create mode 100644 libcxx/test/std/algorithms/alg.nonmodifying/alg.search/pstl.search_n_pred.pass.cpp
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 98b09b9dcf8ae..8a55e5dbfe4aa 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -698,6 +698,7 @@ set(files
__pstl/cpu_algos/merge.h
__pstl/cpu_algos/mismatch.h
__pstl/cpu_algos/reverse.h
+ __pstl/cpu_algos/search_n.h
__pstl/cpu_algos/stable_sort.h
__pstl/cpu_algos/transform.h
__pstl/cpu_algos/transform_reduce.h
diff --git a/libcxx/include/__algorithm/pstl.h b/libcxx/include/__algorithm/pstl.h
index d27044ecdc8b9..099302ef2952f 100644
--- a/libcxx/include/__algorithm/pstl.h
+++ b/libcxx/include/__algorithm/pstl.h
@@ -792,6 +792,50 @@ _LIBCPP_HIDE_FROM_ABI _ForwardOutIterator rotate_copy(
std::move(__result));
}
+template <class _ExecutionPolicy,
+ class _ForwardIterator,
+ class _Size,
+ class _T,
+ class _RawPolicy = __remove_cvref_t<_ExecutionPolicy>,
+ enable_if_t<is_execution_policy_v<_RawPolicy>, int> = 0>
+[[nodiscard]] _LIBCPP_HIDE_FROM_ABI _ForwardIterator search_n(
+ _ExecutionPolicy&& __policy, _ForwardIterator __first, _ForwardIterator __last, _Size __count, const _T& __value) {
+ _LIBCPP_REQUIRE_CPP17_FORWARD_ITERATOR(_ForwardIterator, "search_n requires ForwardIterators");
+ using _Implementation = __pstl::__dispatch<__pstl::__search_n, __pstl::__current_configuration, _RawPolicy>;
+ return __pstl::__handle_exception<_Implementation>(
+ std::forward<_ExecutionPolicy>(__policy),
+ std::move(__first),
+ std::move(__last),
+ std::move(__count),
+ __value,
+ equal_to<>{});
+}
+
+template <class _ExecutionPolicy,
+ class _ForwardIterator,
+ class _Size,
+ class _T,
+ class _BinaryPredicate,
+ class _RawPolicy = __remove_cvref_t<_ExecutionPolicy>,
+ enable_if_t<is_execution_policy_v<_RawPolicy>, int> = 0>
+[[nodiscard]] _LIBCPP_HIDE_FROM_ABI _ForwardIterator search_n(
+ _ExecutionPolicy&& __policy,
+ _ForwardIterator __first,
+ _ForwardIterator __last,
+ _Size __count,
+ const _T& __value,
+ _BinaryPredicate __pred) {
+ _LIBCPP_REQUIRE_CPP17_FORWARD_ITERATOR(_ForwardIterator, "search_n requires ForwardIterators");
+ using _Implementation = __pstl::__dispatch<__pstl::__search_n, __pstl::__current_configuration, _RawPolicy>;
+ return __pstl::__handle_exception<_Implementation>(
+ std::forward<_ExecutionPolicy>(__policy),
+ std::move(__first),
+ std::move(__last),
+ std::move(__count),
+ __value,
+ std::move(__pred));
+}
+
template <class _ExecutionPolicy,
class _RandomAccessIterator,
class _Comp,
diff --git a/libcxx/include/__pstl/backend_fwd.h b/libcxx/include/__pstl/backend_fwd.h
index e83375d1c0c74..1a8f33f6beec2 100644
--- a/libcxx/include/__pstl/backend_fwd.h
+++ b/libcxx/include/__pstl/backend_fwd.h
@@ -187,6 +187,13 @@ struct __merge;
// _ForwardIterator2 __first2, _ForwardIterator2 __last2,
// _ForwardOutIterator __result, _Comp __comp) const noexcept;
+template <class _Backend, class _ExecutionPolicy>
+struct __search_n;
+// template <class _Policy, class _ForwardIterator, class _Size, class _T, class _Predicate>
+// optional<_ForwardIterator>
+// operator()(_Policy&&, _ForwardIterator __first, _ForwardIterator __last,
+// _Size __count, const _T& __value, _Predicate __pred) const noexcept;
+
template <class _Backend, class _ExecutionPolicy>
struct __stable_sort;
// template <class _Policy, class _RandomAccessIterator, class _Comp>
diff --git a/libcxx/include/__pstl/backends/default.h b/libcxx/include/__pstl/backends/default.h
index 5562c23bf09f1..1f3784e5ef399 100644
--- a/libcxx/include/__pstl/backends/default.h
+++ b/libcxx/include/__pstl/backends/default.h
@@ -94,6 +94,10 @@ namespace __pstl {
// ------------
// No other algorithms based on reverse
//
+// search_n family
+// ------------------
+// No other algorithms based on search_n
+//
// stable_sort family
// ------------------
// - sort
diff --git a/libcxx/include/__pstl/backends/libdispatch.h b/libcxx/include/__pstl/backends/libdispatch.h
index ee698742152f3..11df83cd9fd7a 100644
--- a/libcxx/include/__pstl/backends/libdispatch.h
+++ b/libcxx/include/__pstl/backends/libdispatch.h
@@ -42,6 +42,7 @@
#include <__pstl/cpu_algos/merge.h>
#include <__pstl/cpu_algos/mismatch.h>
#include <__pstl/cpu_algos/reverse.h>
+#include <__pstl/cpu_algos/search_n.h>
#include <__pstl/cpu_algos/stable_sort.h>
#include <__pstl/cpu_algos/transform.h>
#include <__pstl/cpu_algos/transform_reduce.h>
@@ -380,6 +381,10 @@ template <class _ExecutionPolicy>
struct __reverse<__libdispatch_backend_tag, _ExecutionPolicy>
: __cpu_parallel_reverse<__libdispatch_backend_tag, _ExecutionPolicy> {};
+template <class _ExecutionPolicy>
+struct __search_n<__libdispatch_backend_tag, _ExecutionPolicy>
+ : __cpu_parallel_search_n<__libdispatch_backend_tag, _ExecutionPolicy> {};
+
template <class _ExecutionPolicy>
struct __stable_sort<__libdispatch_backend_tag, _ExecutionPolicy>
: __cpu_parallel_stable_sort<__libdispatch_backend_tag, _ExecutionPolicy> {};
diff --git a/libcxx/include/__pstl/backends/serial.h b/libcxx/include/__pstl/backends/serial.h
index 13c515b356608..073051e84e9f2 100644
--- a/libcxx/include/__pstl/backends/serial.h
+++ b/libcxx/include/__pstl/backends/serial.h
@@ -15,6 +15,7 @@
#include <__algorithm/merge.h>
#include <__algorithm/mismatch.h>
#include <__algorithm/reverse.h>
+#include <__algorithm/search_n.h>
#include <__algorithm/stable_sort.h>
#include <__algorithm/transform.h>
#include <__config>
@@ -117,6 +118,16 @@ struct __reverse<__serial_backend_tag, _ExecutionPolicy> {
}
};
+template <class _ExecutionPolicy>
+struct __search_n<__serial_backend_tag, _ExecutionPolicy> {
+ template <class _Policy, class _ForwardIterator, class _Size, class _T, class _Predicate>
+ _LIBCPP_HIDE_FROM_ABI optional<_ForwardIterator> operator()(
+ _Policy&&, _ForwardIterator __first, _ForwardIterator __last, _Size __count, const _T& __value, _Predicate __pred)
+ const noexcept {
+ return std::search_n(std::move(__first), std::move(__last), __count, __value, std::move(__pred));
+ }
+};
+
template <class _ExecutionPolicy>
struct __stable_sort<__serial_backend_tag, _ExecutionPolicy> {
template <class _Policy, class _RandomAccessIterator, class _Comp>
diff --git a/libcxx/include/__pstl/backends/std_thread.h b/libcxx/include/__pstl/backends/std_thread.h
index d0b15c8ed1f00..850956a34d7c7 100644
--- a/libcxx/include/__pstl/backends/std_thread.h
+++ b/libcxx/include/__pstl/backends/std_thread.h
@@ -21,6 +21,7 @@
#include <__pstl/cpu_algos/merge.h>
#include <__pstl/cpu_algos/mismatch.h>
#include <__pstl/cpu_algos/reverse.h>
+#include <__pstl/cpu_algos/search_n.h>
#include <__pstl/cpu_algos/stable_sort.h>
#include <__pstl/cpu_algos/transform.h>
#include <__pstl/cpu_algos/transform_reduce.h>
@@ -111,6 +112,10 @@ template <class _ExecutionPolicy>
struct __reverse<__std_thread_backend_tag, _ExecutionPolicy>
: __cpu_parallel_reverse<__std_thread_backend_tag, _ExecutionPolicy> {};
+template <class _ExecutionPolicy>
+struct __search_n<__std_thread_backend_tag, _ExecutionPolicy>
+ : __cpu_parallel_search_n<__std_thread_backend_tag, _ExecutionPolicy> {};
+
template <class _ExecutionPolicy>
struct __stable_sort<__std_thread_backend_tag, _ExecutionPolicy>
: __cpu_parallel_stable_sort<__std_thread_backend_tag, _ExecutionPolicy> {};
diff --git a/libcxx/include/__pstl/cpu_algos/search_n.h b/libcxx/include/__pstl/cpu_algos/search_n.h
new file mode 100644
index 0000000000000..2672b1d84a2fa
--- /dev/null
+++ b/libcxx/include/__pstl/cpu_algos/search_n.h
@@ -0,0 +1,106 @@
+//===----------------------------------------------------------------------===//
+//
+// 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___PSTL_CPU_ALGOS_SEARCH_N_H
+#define _LIBCPP___PSTL_CPU_ALGOS_SEARCH_N_H
+
+#include <__algorithm/search_n.h>
+#include <__config>
+#include <__functional/operations.h>
+#include <__iterator/concepts.h>
+#include <__iterator/iterator_traits.h>
+#include <__optional/optional.h>
+#include <__pstl/backend_fwd.h>
+#include <__pstl/cpu_algos/cpu_traits.h>
+#include <__pstl/cpu_algos/find_if.h>
+#include <__type_traits/is_execution_policy.h>
+#include <__utility/move.h>
+#include <__utility/pair.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+# pragma GCC system_header
+#endif
+
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
+#if _LIBCPP_STD_VER >= 17
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+namespace __pstl {
+
+template <class _Backend, class _RawExecutionPolicy>
+struct __cpu_parallel_search_n {
+ template <class _Policy, class _ForwardIterator, class _Size, class _T, class _Predicate>
+ _LIBCPP_HIDE_FROM_ABI optional<_ForwardIterator> operator()(
+ _Policy&&, _ForwardIterator __first, _ForwardIterator __last, _Size __count, const _T& __value, _Predicate __pred)
+ const noexcept {
+ if constexpr (__is_parallel_execution_policy_v<_RawExecutionPolicy> &&
+ __has_random_access_iterator_category_or_concept<_ForwardIterator>::value) {
+ typedef typename std::iterator_traits<_ForwardIterator>::difference_type _DifferenceType;
+ if (static_cast<_DifferenceType>(__count) <= 0) {
+ return __first; // If the count is non-positive, the first iterator is returned.
+ }
+ _DifferenceType __size = __last - __first;
+ if (__size < static_cast<_DifferenceType>(__count)) {
+ return __last; // The range is too small to contain the requested number of consecutive elements.
+ }
+ // We're only interested in the range where a potential match can start.
+ _DifferenceType __n = __size - static_cast<_DifferenceType>(__count) + 1;
+ _ForwardIterator __last2 = __first + __n;
+ // Run a parallel chunked find_if, covering the range where a potential match can start.
+ auto __res = __pstl::__parallel_find<_Backend>(
+ __first,
+ __last2,
+ [__count, &__value, &__pred](_ForwardIterator __brick_first, _ForwardIterator __brick_last) {
+ // [__brick_first, __brick_first + __count) is guaranteed to be a valid range.
+ // __brick_first can walk past __brick_last.
+ for (; __brick_first < __brick_last; ++__brick_first) {
+ if (!__pred(*__brick_first, __value)) {
+ continue;
+ }
+ // We found a start of a potential match, now check the next __count - 1 elements.
+ _ForwardIterator __match_start = __brick_first;
+ _Size __matches = static_cast<_Size>(0);
+ while (true) {
+ if (++__matches == __count) {
+ return __match_start; // Found a full match, return the start of the match.
+ }
+ ++__brick_first;
+ if (!__pred(*__brick_first, __value)) {
+ break; // The match was broken, continue searching for a new potential match.
+ }
+ }
+ }
+ return __brick_last; // No matches starting in this chunk, return the end of the chunk.
+ },
+ less<>{}, // `less` here means the lowest index among the matches
+ true // `true` here means we want the first match, not the last
+ );
+ if (!__res) {
+ return std::nullopt; // Failed to run the algorithm, propagate the error.
+ }
+ if (*__res == __last2) {
+ return __last; // No match was found in the range.
+ }
+ return *__res; // Return the successful match.
+ } else {
+ // Non-random access iterators cannot be processed in parallel, fall back to the sequential implementation.
+ return std::search_n(std::move(__first), std::move(__last), __count, __value, std::move(__pred));
+ }
+ }
+};
+
+} // namespace __pstl
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_STD_VER >= 17
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___PSTL_CPU_ALGOS_SEARCH_N_H
diff --git a/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp b/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
index e61c562025fa0..0f1697c3c306c 100644
--- a/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
+++ b/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
@@ -217,6 +217,11 @@ void f(non_forward_iterator non_fwd,
(void)std::rotate_copy(pol, it, it, it, non_output); // expected-error@*:* {{static assertion failed: rotate_copy}}
}
+ {
+ (void)std::search_n(pol, non_fwd, non_fwd, 1, 1); // expected-error@*:* {{static assertion failed: search_n}}
+ (void)std::search_n(pol, non_fwd, non_fwd, 1, 1, pred); // expected-error@*:* {{static assertion failed: search_n}}
+ }
+
{
(void)std::sort(pol, non_fwd, non_fwd); // expected-error@*:* {{static assertion failed: sort}}
(void)std::sort(pol, non_fwd, non_fwd, pred); // expected-error@*:* {{static assertion failed: sort}}
diff --git a/libcxx/test/libcxx/algorithms/pstl.nodiscard.verify.cpp b/libcxx/test/libcxx/algorithms/pstl.nodiscard.verify.cpp
index 37e843eb90fe1..44e8acfcb877e 100644
--- a/libcxx/test/libcxx/algorithms/pstl.nodiscard.verify.cpp
+++ b/libcxx/test/libcxx/algorithms/pstl.nodiscard.verify.cpp
@@ -83,4 +83,8 @@ void test() {
std::lexicographical_compare(std::execution::par, std::begin(a), std::end(a), std::begin(b), std::end(b));
// expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
std::lexicographical_compare(std::execution::par, std::begin(a), std::end(a), std::begin(b), std::end(b), pred2);
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::search_n(std::execution::par, std::begin(a), std::end(a), 1, 1);
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::search_n(std::execution::par, std::begin(a), std::end(a), 1, 1, pred2);
}
diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.search/pstl.search_n.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.search/pstl.search_n.pass.cpp
new file mode 100644
index 0000000000000..86f4bb970cf3c
--- /dev/null
+++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.search/pstl.search_n.pass.cpp
@@ -0,0 +1,156 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 ExecutionPolicy, class ForwardIterator, class Size, class T>
+// ForwardIterator
+// search_n(ExecutionPolicy&& exec,
+// ForwardIterator first, ForwardIterator last,
+// Size count, const T& value);
+
+#include <algorithm>
+#include <cassert>
+#include <iterator>
+
+#include "test_execution_policies.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+#include "type_algorithms.h"
+#include "runway_sample.h"
+
+EXECUTION_POLICY_SFINAE_TEST(search_n);
+
+static_assert(sfinae_test_search_n<int, int*, int*, int, int>);
+static_assert(!sfinae_test_search_n<std::execution::parallel_policy, int*, int*, int, int>);
+
+template <class Iter>
+struct Test {
+ template <class ExecutionPolicy>
+ void operator()(ExecutionPolicy&& policy) {
+ { // check the return types
+ int a[] = {0};
+ auto res = std::search_n(policy, Iter(std::begin(a)), Iter(std::begin(a)), 1, 0);
+ static_assert(std::is_same_v<decltype(res), Iter>);
+ }
+ { // single element range with count = 1, matching
+ int a[] = {5};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 1, 5);
+ assert(ret == Iter(std::begin(a)));
+ }
+ { // single element range with count = 1, not matching
+ int a[] = {5};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 1, 3);
+ assert(ret == Iter(std::end(a)));
+ }
+ { // simple test - single match in the middle, count = 1
+ int a[] = {1, 2, 3, 4, 5, 6};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 1, 3);
+ assert(ret == Iter(std::begin(a) + 2));
+ }
+ { // matching part begins at the front
+ int a[] = {7, 7, 3, 7, 3, 6};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 2, 7);
+ assert(ret == Iter(std::begin(a)));
+ }
+ { // matching part ends at the back
+ int a[] = {9, 3, 6, 4, 4};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 2, 4);
+ assert(ret == Iter(std::begin(a) + 3));
+ }
+ { // pattern does not match
+ int a[] = {9, 3, 6, 4, 8};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 1, 1);
+ assert(ret == Iter(std::end(a)));
+ }
+ { // range and pattern are identical
+ int a[] = {1, 1, 1, 1};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 4, 1);
+ assert(ret == Iter(std::begin(a)));
+ }
+ { // pattern is longer than range
+ int a[] = {5};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 2, 5);
+ assert(ret == Iter(std::end(a)));
+ }
+ { // pattern is longer than range
+ int a[] = {3, 3, 3};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 4, 3);
+ assert(ret == Iter(std::end(a)));
+ }
+ { // pattern has zero length
+ int a[] = {6, 7, 8};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 0, 7);
+ assert(ret == Iter(std::begin(a)));
+ }
+ { // range has zero length
+ int a[] = {0};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::begin(a)), 1, 1);
+ assert(ret == Iter(std::begin(a)));
+ }
+ { // check that the first match is returned
+ { // match is at the start
+ int a[] = {6, 6, 8, 6, 6, 8, 6, 6, 8};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 2, 6);
+ assert(ret == Iter(std::begin(a)));
+ }
+ { // match is in the middle
+ int a[] = {6, 8, 8, 6, 6, 8, 6, 6, 8};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 2, 6);
+ assert(ret == Iter(std::begin(a) + 3));
+ }
+ { // match is at the end
+ int a[] = {6, 6, 8, 6, 6, 8, 6, 6, 6};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 3, 6);
+ assert(ret == Iter(std::begin(a) + 6));
+ }
+ { // multiple overlapping potential matches
+ int a[] = {3, 3, 3, 3, 3};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 3, 3);
+ assert(ret == Iter(std::begin(a)));
+ }
+ }
+ { // first almost matches, second is a match
+ int a[] = {6, 6, 6, 7, 6, 6, 6, 6};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 4, 6);
+ assert(ret == Iter(std::begin(a) + 4));
+ }
+ { // big haystack, small needle (size=1)
+ int a[1073];
+ std::fill(std::begin(a), std::end(a), 0);
+ runway_sample(std::size(a), [&](size_t i) {
+ a[i] = 1;
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 1, 1);
+ assert(ret == Iter(std::begin(a) + i));
+ a[i] = 0;
+ });
+ }
+ { // big haystack, medium needle (size=73)
+ int a[1073];
+ constexpr size_t needle_size = 73;
+ std::fill(std::begin(a), std::end(a), 0);
+ runway_sample(std::size(a) - needle_size + 1, [&](size_t i) {
+ std::fill(std::begin(a) + i, std::begin(a) + i + needle_size, 1);
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), needle_size, 1);
+ assert(ret == Iter(std::begin(a) + i));
+ std::fill(std::begin(a) + i, std::begin(a) + i + needle_size, 0);
+ });
+ }
+ { // big haystack, same size needle
+ int a[1073];
+ std::fill(std::begin(a), std::end(a), 1);
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), std::size(a), 1);
+ assert(ret == Iter(std::begin(a)));
+ }
+ }
+};
+
+int main(int, char**) {
+ types::for_each(types::forward_iterator_list<const int*>{}, TestIteratorWithPolicies<Test>{});
+ return 0;
+}
diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.search/pstl.search_n_pred.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.search/pstl.search_n_pred.pass.cpp
new file mode 100644
index 0000000000000..1d273e6caadfc
--- /dev/null
+++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.search/pstl.search_n_pred.pass.cpp
@@ -0,0 +1,160 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 ExecutionPolicy, class ForwardIterator, class Size, class T, class BinaryPredicate>
+// ForwardIterator
+// search_n(ExecutionPolicy&& exec,
+// ForwardIterator first, ForwardIterator last,
+// Size count, const T& value, BinaryPredicate pred);
+
+#include <algorithm>
+#include <cassert>
+#include <iterator>
+
+#include "test_execution_policies.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+#include "type_algorithms.h"
+#include "runway_sample.h"
+
+EXECUTION_POLICY_SFINAE_TEST(search_n);
+
+static_assert(sfinae_test_search_n<int, int*, int*, int, int, bool (*)(int, int)>);
+static_assert(!sfinae_test_search_n<std::execution::parallel_policy, int*, int*, int, int, bool (*)(int, int)>);
+
+struct Pred {
+ bool operator()(int hay, int needle) const { return hay * 2 == needle; }
+};
+
+template <class Iter>
+struct Test {
+ template <class ExecutionPolicy>
+ void operator()(ExecutionPolicy&& policy) {
+ { // check the return types
+ int a[] = {0};
+ auto res = std::search_n(policy, Iter(std::begin(a)), Iter(std::begin(a)), 1, 0, Pred{});
+ static_assert(std::is_same_v<decltype(res), Iter>);
+ }
+ { // single element range with count = 1, matching
+ int a[] = {5};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 1, 10, Pred{});
+ assert(ret == Iter(std::begin(a)));
+ }
+ { // single element range with count = 1, not matching
+ int a[] = {5};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 1, 6, Pred{});
+ assert(ret == Iter(std::end(a)));
+ }
+ { // simple test - single match in the middle, count = 1
+ int a[] = {1, 2, 3, 4, 5, 6};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 1, 6, Pred{});
+ assert(ret == Iter(std::begin(a) + 2));
+ }
+ { // matching part begins at the front
+ int a[] = {7, 7, 3, 7, 3, 6};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 2, 14, Pred{});
+ assert(ret == Iter(std::begin(a)));
+ }
+ { // matching part ends at the back
+ int a[] = {9, 3, 6, 4, 4};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 2, 8, Pred{});
+ assert(ret == Iter(std::begin(a) + 3));
+ }
+ { // pattern does not match
+ int a[] = {9, 3, 6, 4, 8};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 1, 1, Pred{});
+ assert(ret == Iter(std::end(a)));
+ }
+ { // range and pattern are identical
+ int a[] = {1, 1, 1, 1};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 4, 2, Pred{});
+ assert(ret == Iter(std::begin(a)));
+ }
+ { // pattern is longer than range
+ int a[] = {5};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 2, 10, Pred{});
+ assert(ret == Iter(std::end(a)));
+ }
+ { // pattern is longer than range
+ int a[] = {3, 3, 3};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 4, 6, Pred{});
+ assert(ret == Iter(std::end(a)));
+ }
+ { // pattern has zero length
+ int a[] = {6, 7, 8};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 0, 14, Pred{});
+ assert(ret == Iter(std::begin(a)));
+ }
+ { // range has zero length
+ int a[] = {0};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::begin(a)), 1, 2, Pred{});
+ assert(ret == Iter(std::begin(a)));
+ }
+ { // check that the first match is returned
+ { // match is at the start
+ int a[] = {6, 6, 8, 6, 6, 8, 6, 6, 8};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 2, 12, Pred{});
+ assert(ret == Iter(std::begin(a)));
+ }
+ { // match is in the middle
+ int a[] = {6, 8, 8, 6, 6, 8, 6, 6, 8};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 2, 12, Pred{});
+ assert(ret == Iter(std::begin(a) + 3));
+ }
+ { // match is at the end
+ int a[] = {6, 6, 8, 6, 6, 8, 6, 6, 6};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 3, 12, Pred{});
+ assert(ret == Iter(std::begin(a) + 6));
+ }
+ { // multiple overlapping potential matches
+ int a[] = {3, 3, 3, 3, 3};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 3, 6, Pred{});
+ assert(ret == Iter(std::begin(a)));
+ }
+ }
+ { // first almost matches, second is a match
+ int a[] = {6, 6, 6, 7, 6, 6, 6, 6};
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 4, 12, Pred{});
+ assert(ret == Iter(std::begin(a) + 4));
+ }
+ { // big haystack, small needle (size=1)
+ int a[1073];
+ std::fill(std::begin(a), std::end(a), 0);
+ runway_sample(std::size(a), [&](size_t i) {
+ a[i] = 1;
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), 1, 2, Pred{});
+ assert(ret == Iter(std::begin(a) + i));
+ a[i] = 0;
+ });
+ }
+ { // big haystack, medium needle (size=73)
+ int a[1073];
+ constexpr size_t needle_size = 73;
+ std::fill(std::begin(a), std::end(a), 0);
+ runway_sample(std::size(a) - needle_size + 1, [&](size_t i) {
+ std::fill(std::begin(a) + i, std::begin(a) + i + needle_size, 1);
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), needle_size, 2, Pred{});
+ assert(ret == Iter(std::begin(a) + i));
+ std::fill(std::begin(a) + i, std::begin(a) + i + needle_size, 0);
+ });
+ }
+ { // big haystack, same size needle
+ int a[1073];
+ std::fill(std::begin(a), std::end(a), 1);
+ auto ret = std::search_n(policy, Iter(std::begin(a)), Iter(std::end(a)), std::size(a), 2, Pred{});
+ assert(ret == Iter(std::begin(a)));
+ }
+ }
+};
+
+int main(int, char**) {
+ types::for_each(types::forward_iterator_list<const int*>{}, TestIteratorWithPolicies<Test>{});
+ return 0;
+}
diff --git a/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp b/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp
index 063210a13af7e..4cc24672032dc 100644
--- a/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp
+++ b/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp
@@ -346,6 +346,18 @@ int main(int, char**) {
});
}
+ {
+ auto pred = maybe_throw(tokens[5], [](int x, int y) -> bool { return x == y; });
+
+ // search_n(first, last, n, val)
+ assert_non_throwing([=, &policy] { (void)std::search_n(policy, std::move(first1), std::move(last1), 1, 1); });
+
+ // search_n(first, last, n, val, pred)
+ assert_non_throwing([=, &policy] {
+ (void)std::search_n(policy, std::move(first1), std::move(last1), 1, 1, pred);
+ });
+ }
+
{
auto compare = maybe_throw(tokens[5], [](int x, int y) -> bool { return x < y; });
>From 9424f6d44bd384a2258a584ebcfe7214a804fe12 Mon Sep 17 00:00:00 2001
From: Michael Kazakov <mike.kazakov at gmail.com>
Date: Tue, 4 Aug 2026 22:18:07 +0100
Subject: [PATCH 2/6] Added requirements to the tests
---
.../alg.nonmodifying/alg.search/pstl.search_n.pass.cpp | 4 ++++
.../alg.nonmodifying/alg.search/pstl.search_n_pred.pass.cpp | 4 ++++
2 files changed, 8 insertions(+)
diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.search/pstl.search_n.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.search/pstl.search_n.pass.cpp
index 86f4bb970cf3c..b5896a05c359c 100644
--- a/libcxx/test/std/algorithms/alg.nonmodifying/alg.search/pstl.search_n.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.search/pstl.search_n.pass.cpp
@@ -6,6 +6,10 @@
//
//===----------------------------------------------------------------------===//
+// REQUIRES: std-at-least-c++17
+
+// UNSUPPORTED: libcpp-has-no-incomplete-pstl
+
// <algorithm>
// template<class ExecutionPolicy, class ForwardIterator, class Size, class T>
diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.search/pstl.search_n_pred.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.search/pstl.search_n_pred.pass.cpp
index 1d273e6caadfc..da1caa9fc5169 100644
--- a/libcxx/test/std/algorithms/alg.nonmodifying/alg.search/pstl.search_n_pred.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.search/pstl.search_n_pred.pass.cpp
@@ -6,6 +6,10 @@
//
//===----------------------------------------------------------------------===//
+// REQUIRES: std-at-least-c++17
+
+// UNSUPPORTED: libcpp-has-no-incomplete-pstl
+
// <algorithm>
// template<class ExecutionPolicy, class ForwardIterator, class Size, class T, class BinaryPredicate>
>From d11a246bc390f420df5ebe45855278f0473449c4 Mon Sep 17 00:00:00 2001
From: Michael Kazakov <mike.kazakov at gmail.com>
Date: Tue, 4 Aug 2026 22:35:05 +0100
Subject: [PATCH 3/6] Modulemap update
---
libcxx/include/module.modulemap.in | 3 +++
1 file changed, 3 insertions(+)
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 41c1ac1595234..11f8d46542bc6 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -2433,6 +2433,9 @@ module std {
module reverse {
header "__pstl/cpu_algos/reverse.h"
}
+ module search_n {
+ header "__pstl/cpu_algos/search_n.h"
+ }
module stable_sort {
header "__pstl/cpu_algos/stable_sort.h"
export std_core.utility_core.empty
>From 6ae4c5478f56790d8e2076d07fc6e87413f1ab25 Mon Sep 17 00:00:00 2001
From: Michael Kazakov <mike.kazakov at gmail.com>
Date: Tue, 4 Aug 2026 23:04:02 +0100
Subject: [PATCH 4/6] Renamed _T -> _Tp
---
libcxx/include/__algorithm/pstl.h | 8 ++++----
libcxx/include/__pstl/backends/serial.h | 12 ++++++++----
libcxx/include/__pstl/cpu_algos/search_n.h | 12 ++++++++----
3 files changed, 20 insertions(+), 12 deletions(-)
diff --git a/libcxx/include/__algorithm/pstl.h b/libcxx/include/__algorithm/pstl.h
index 099302ef2952f..9a9eb8905f2dd 100644
--- a/libcxx/include/__algorithm/pstl.h
+++ b/libcxx/include/__algorithm/pstl.h
@@ -795,11 +795,11 @@ _LIBCPP_HIDE_FROM_ABI _ForwardOutIterator rotate_copy(
template <class _ExecutionPolicy,
class _ForwardIterator,
class _Size,
- class _T,
+ class _Tp,
class _RawPolicy = __remove_cvref_t<_ExecutionPolicy>,
enable_if_t<is_execution_policy_v<_RawPolicy>, int> = 0>
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI _ForwardIterator search_n(
- _ExecutionPolicy&& __policy, _ForwardIterator __first, _ForwardIterator __last, _Size __count, const _T& __value) {
+ _ExecutionPolicy&& __policy, _ForwardIterator __first, _ForwardIterator __last, _Size __count, const _Tp& __value) {
_LIBCPP_REQUIRE_CPP17_FORWARD_ITERATOR(_ForwardIterator, "search_n requires ForwardIterators");
using _Implementation = __pstl::__dispatch<__pstl::__search_n, __pstl::__current_configuration, _RawPolicy>;
return __pstl::__handle_exception<_Implementation>(
@@ -814,7 +814,7 @@ template <class _ExecutionPolicy,
template <class _ExecutionPolicy,
class _ForwardIterator,
class _Size,
- class _T,
+ class _Tp,
class _BinaryPredicate,
class _RawPolicy = __remove_cvref_t<_ExecutionPolicy>,
enable_if_t<is_execution_policy_v<_RawPolicy>, int> = 0>
@@ -823,7 +823,7 @@ template <class _ExecutionPolicy,
_ForwardIterator __first,
_ForwardIterator __last,
_Size __count,
- const _T& __value,
+ const _Tp& __value,
_BinaryPredicate __pred) {
_LIBCPP_REQUIRE_CPP17_FORWARD_ITERATOR(_ForwardIterator, "search_n requires ForwardIterators");
using _Implementation = __pstl::__dispatch<__pstl::__search_n, __pstl::__current_configuration, _RawPolicy>;
diff --git a/libcxx/include/__pstl/backends/serial.h b/libcxx/include/__pstl/backends/serial.h
index 073051e84e9f2..4294224268e44 100644
--- a/libcxx/include/__pstl/backends/serial.h
+++ b/libcxx/include/__pstl/backends/serial.h
@@ -120,10 +120,14 @@ struct __reverse<__serial_backend_tag, _ExecutionPolicy> {
template <class _ExecutionPolicy>
struct __search_n<__serial_backend_tag, _ExecutionPolicy> {
- template <class _Policy, class _ForwardIterator, class _Size, class _T, class _Predicate>
- _LIBCPP_HIDE_FROM_ABI optional<_ForwardIterator> operator()(
- _Policy&&, _ForwardIterator __first, _ForwardIterator __last, _Size __count, const _T& __value, _Predicate __pred)
- const noexcept {
+ template <class _Policy, class _ForwardIterator, class _Size, class _Tp, class _Predicate>
+ _LIBCPP_HIDE_FROM_ABI optional<_ForwardIterator>
+ operator()(_Policy&&,
+ _ForwardIterator __first,
+ _ForwardIterator __last,
+ _Size __count,
+ const _Tp& __value,
+ _Predicate __pred) const noexcept {
return std::search_n(std::move(__first), std::move(__last), __count, __value, std::move(__pred));
}
};
diff --git a/libcxx/include/__pstl/cpu_algos/search_n.h b/libcxx/include/__pstl/cpu_algos/search_n.h
index 2672b1d84a2fa..6489a40e3eeea 100644
--- a/libcxx/include/__pstl/cpu_algos/search_n.h
+++ b/libcxx/include/__pstl/cpu_algos/search_n.h
@@ -36,10 +36,14 @@ namespace __pstl {
template <class _Backend, class _RawExecutionPolicy>
struct __cpu_parallel_search_n {
- template <class _Policy, class _ForwardIterator, class _Size, class _T, class _Predicate>
- _LIBCPP_HIDE_FROM_ABI optional<_ForwardIterator> operator()(
- _Policy&&, _ForwardIterator __first, _ForwardIterator __last, _Size __count, const _T& __value, _Predicate __pred)
- const noexcept {
+ template <class _Policy, class _ForwardIterator, class _Size, class _Tp, class _Predicate>
+ _LIBCPP_HIDE_FROM_ABI optional<_ForwardIterator>
+ operator()(_Policy&&,
+ _ForwardIterator __first,
+ _ForwardIterator __last,
+ _Size __count,
+ const _Tp& __value,
+ _Predicate __pred) const noexcept {
if constexpr (__is_parallel_execution_policy_v<_RawExecutionPolicy> &&
__has_random_access_iterator_category_or_concept<_ForwardIterator>::value) {
typedef typename std::iterator_traits<_ForwardIterator>::difference_type _DifferenceType;
>From 3c6ad981dc661cabfabd8055c8eb7eec7dde4f00 Mon Sep 17 00:00:00 2001
From: Michael Kazakov <mike.kazakov at gmail.com>
Date: Wed, 5 Aug 2026 09:08:08 +0100
Subject: [PATCH 5/6] Missing include
---
libcxx/include/__pstl/cpu_algos/search_n.h | 1 +
1 file changed, 1 insertion(+)
diff --git a/libcxx/include/__pstl/cpu_algos/search_n.h b/libcxx/include/__pstl/cpu_algos/search_n.h
index 6489a40e3eeea..b98ae33a4374a 100644
--- a/libcxx/include/__pstl/cpu_algos/search_n.h
+++ b/libcxx/include/__pstl/cpu_algos/search_n.h
@@ -14,6 +14,7 @@
#include <__functional/operations.h>
#include <__iterator/concepts.h>
#include <__iterator/iterator_traits.h>
+#include <__optional/nullopt_t.h>
#include <__optional/optional.h>
#include <__pstl/backend_fwd.h>
#include <__pstl/cpu_algos/cpu_traits.h>
>From 80ecc2d61c1fa570c590ba89136a99fee28409b6 Mon Sep 17 00:00:00 2001
From: Michael Kazakov <mike.kazakov at gmail.com>
Date: Thu, 6 Aug 2026 12:21:06 +0100
Subject: [PATCH 6/6] Use a serial std::search_n() inside __parallel_find
---
libcxx/include/__pstl/cpu_algos/search_n.h | 43 +++++++++-------------
1 file changed, 17 insertions(+), 26 deletions(-)
diff --git a/libcxx/include/__pstl/cpu_algos/search_n.h b/libcxx/include/__pstl/cpu_algos/search_n.h
index b98ae33a4374a..4fd2bef73ff7f 100644
--- a/libcxx/include/__pstl/cpu_algos/search_n.h
+++ b/libcxx/include/__pstl/cpu_algos/search_n.h
@@ -20,6 +20,7 @@
#include <__pstl/cpu_algos/cpu_traits.h>
#include <__pstl/cpu_algos/find_if.h>
#include <__type_traits/is_execution_policy.h>
+#include <__utility/convert_to_integral.h>
#include <__utility/move.h>
#include <__utility/pair.h>
@@ -48,41 +49,31 @@ struct __cpu_parallel_search_n {
if constexpr (__is_parallel_execution_policy_v<_RawExecutionPolicy> &&
__has_random_access_iterator_category_or_concept<_ForwardIterator>::value) {
typedef typename std::iterator_traits<_ForwardIterator>::difference_type _DifferenceType;
- if (static_cast<_DifferenceType>(__count) <= 0) {
+ _DifferenceType __integral_count = std::__convert_to_integral(__count);
+ if (__integral_count <= 0) {
return __first; // If the count is non-positive, the first iterator is returned.
}
_DifferenceType __size = __last - __first;
- if (__size < static_cast<_DifferenceType>(__count)) {
+ if (__size < __integral_count) {
return __last; // The range is too small to contain the requested number of consecutive elements.
}
- // We're only interested in the range where a potential match can start.
- _DifferenceType __n = __size - static_cast<_DifferenceType>(__count) + 1;
- _ForwardIterator __last2 = __first + __n;
+ // Calculate the length of the tail where a potential match cannot start by definition.
+ _DifferenceType __crop = __integral_count - 1;
+ // We're only interested in the range where a potential match can start: [first, last - crop)
+ _ForwardIterator __last2 = __last - __crop;
// Run a parallel chunked find_if, covering the range where a potential match can start.
auto __res = __pstl::__parallel_find<_Backend>(
__first,
__last2,
- [__count, &__value, &__pred](_ForwardIterator __brick_first, _ForwardIterator __brick_last) {
- // [__brick_first, __brick_first + __count) is guaranteed to be a valid range.
- // __brick_first can walk past __brick_last.
- for (; __brick_first < __brick_last; ++__brick_first) {
- if (!__pred(*__brick_first, __value)) {
- continue;
- }
- // We found a start of a potential match, now check the next __count - 1 elements.
- _ForwardIterator __match_start = __brick_first;
- _Size __matches = static_cast<_Size>(0);
- while (true) {
- if (++__matches == __count) {
- return __match_start; // Found a full match, return the start of the match.
- }
- ++__brick_first;
- if (!__pred(*__brick_first, __value)) {
- break; // The match was broken, continue searching for a new potential match.
- }
- }
- }
- return __brick_last; // No matches starting in this chunk, return the end of the chunk.
+ [__integral_count, __crop, &__value, &__pred](_ForwardIterator __brick_first, _ForwardIterator __brick_last) {
+ // Uncrop the range to allow std::search_n to find a full match, which can go beyond __brick_last.
+ _ForwardIterator __brick_last_uncropped = __brick_last + __crop;
+ // Run a serial std::search_n inside each of the chunks in parallel.
+ _ForwardIterator __ret =
+ std::search_n(__brick_first, __brick_last_uncropped, __integral_count, __value, __pred);
+ // The returned iterator is either a match inside [__brick_first, __brick_last) or a miss encoded as
+ // __brick_last_uncropped. Return the miss as __brick_last to conform to expectations of __parallel_find().
+ return __ret == __brick_last_uncropped ? __brick_last : __ret;
},
less<>{}, // `less` here means the lowest index among the matches
true // `true` here means we want the first match, not the last
More information about the libcxx-commits
mailing list