[libcxx-commits] [libcxx] [libc++][pstl] Implementation of parallel std::transform_inclusive_scan (PR #223802)
Michael G. Kazakov via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Sep 15 12:55:14 PDT 2026
https://github.com/mikekazakov created https://github.com/llvm/llvm-project/pull/223802
None
>From 5519d9f079c11ac06547e28d44b3e81abb985e8f Mon Sep 17 00:00:00 2001
From: Michael Kazakov <mike.kazakov at gmail.com>
Date: Tue, 15 Sep 2026 20:53:04 +0100
Subject: [PATCH] Implementation of 3-passes inclusive scan
---
libcxx/include/CMakeLists.txt | 1 +
libcxx/include/__numeric/pstl.h | 57 +++++
libcxx/include/__pstl/backend_fwd.h | 21 ++
libcxx/include/__pstl/backends/default.h | 38 +++
libcxx/include/__pstl/backends/libdispatch.h | 63 +++++
libcxx/include/__pstl/backends/serial.h | 27 +++
libcxx/include/__pstl/backends/std_thread.h | 24 ++
.../cpu_algos/transform_inclusive_scan_init.h | 97 ++++++++
libcxx/include/module.modulemap.in | 3 +
....transform_inclusive_scan_bop_uop.pass.cpp | 159 +++++++++++++
...sform_inclusive_scan_bop_uop_init.pass.cpp | 216 ++++++++++++++++++
11 files changed, 706 insertions(+)
create mode 100644 libcxx/include/__pstl/cpu_algos/transform_inclusive_scan_init.h
create mode 100644 libcxx/test/std/numerics/numeric.ops/transform.inclusive.scan/pstl.transform_inclusive_scan_bop_uop.pass.cpp
create mode 100644 libcxx/test/std/numerics/numeric.ops/transform.inclusive.scan/pstl.transform_inclusive_scan_bop_uop_init.pass.cpp
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index bc11bda33724b..93e392dd3a523 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -709,6 +709,7 @@ set(files
__pstl/cpu_algos/search.h
__pstl/cpu_algos/search_n.h
__pstl/cpu_algos/stable_sort.h
+ __pstl/cpu_algos/transform_inclusive_scan_init.h
__pstl/cpu_algos/transform.h
__pstl/cpu_algos/transform_reduce.h
__pstl/dispatch.h
diff --git a/libcxx/include/__numeric/pstl.h b/libcxx/include/__numeric/pstl.h
index 0b85cfb8c2327..414dedb7e527a 100644
--- a/libcxx/include/__numeric/pstl.h
+++ b/libcxx/include/__numeric/pstl.h
@@ -82,6 +82,63 @@ reduce(_ExecutionPolicy&& __policy, _ForwardIterator __first, _ForwardIterator _
plus{});
}
+template <class _ExecutionPolicy,
+ class _ForwardIterator1,
+ class _ForwardIterator2,
+ class _BinaryOperation,
+ class _UnaryOperation,
+ class _Tp,
+ class _RawPolicy = __remove_cvref_t<_ExecutionPolicy>,
+ enable_if_t<is_execution_policy_v<_RawPolicy>, int> = 0>
+_LIBCPP_HIDE_FROM_ABI _ForwardIterator2 transform_inclusive_scan(
+ _ExecutionPolicy&& __policy,
+ _ForwardIterator1 __first,
+ _ForwardIterator1 __last,
+ _ForwardIterator2 __result,
+ _BinaryOperation __reduce,
+ _UnaryOperation __transform,
+ _Tp __init) {
+ _LIBCPP_REQUIRE_CPP17_FORWARD_ITERATOR(_ForwardIterator1, "transform_inclusive_scan requires ForwardIterators");
+ _LIBCPP_REQUIRE_CPP17_FORWARD_ITERATOR(_ForwardIterator2, "transform_inclusive_scan requires ForwardIterators");
+ using _Implementation =
+ __pstl::__dispatch<__pstl::__transform_inclusive_scan_init, __pstl::__current_configuration, _RawPolicy>;
+ return __pstl::__handle_exception<_Implementation>(
+ std::forward<_ExecutionPolicy>(__policy),
+ std::move(__first),
+ std::move(__last),
+ std::move(__result),
+ std::move(__reduce),
+ std::move(__transform),
+ std::move(__init));
+}
+
+template <class _ExecutionPolicy,
+ class _ForwardIterator1,
+ class _ForwardIterator2,
+ class _BinaryOperation,
+ class _UnaryOperation,
+ class _RawPolicy = __remove_cvref_t<_ExecutionPolicy>,
+ enable_if_t<is_execution_policy_v<_RawPolicy>, int> = 0>
+_LIBCPP_HIDE_FROM_ABI _ForwardIterator2 transform_inclusive_scan(
+ _ExecutionPolicy&& __policy,
+ _ForwardIterator1 __first,
+ _ForwardIterator1 __last,
+ _ForwardIterator2 __result,
+ _BinaryOperation __reduce,
+ _UnaryOperation __transform) {
+ _LIBCPP_REQUIRE_CPP17_FORWARD_ITERATOR(_ForwardIterator1, "transform_inclusive_scan requires ForwardIterators");
+ _LIBCPP_REQUIRE_CPP17_FORWARD_ITERATOR(_ForwardIterator2, "transform_inclusive_scan requires ForwardIterators");
+ using _Implementation =
+ __pstl::__dispatch<__pstl::__transform_inclusive_scan, __pstl::__current_configuration, _RawPolicy>;
+ return __pstl::__handle_exception<_Implementation>(
+ std::forward<_ExecutionPolicy>(__policy),
+ std::move(__first),
+ std::move(__last),
+ std::move(__result),
+ std::move(__reduce),
+ std::move(__transform));
+}
+
template <class _ExecutionPolicy,
class _ForwardIterator1,
class _ForwardIterator2,
diff --git a/libcxx/include/__pstl/backend_fwd.h b/libcxx/include/__pstl/backend_fwd.h
index 40a335ae1f2ad..4863db1823592 100644
--- a/libcxx/include/__pstl/backend_fwd.h
+++ b/libcxx/include/__pstl/backend_fwd.h
@@ -257,6 +257,27 @@ struct __transform_binary;
// _ForwardOutIterator __result,
// _BinaryOperation __op) const noexcept;
+template <class _Backend, class _ExecutionPolicy>
+struct __transform_inclusive_scan;
+// template <class _Policy, class _ForwardIterator1, class _ForwardIterator2,
+// class _BinaryOperation, class _UnaryOperation>
+// optional<_ForwardIterator2>
+// operator()(_Policy&&, _ForwardIterator1 __first, _ForwardIterator1 __last,
+// _ForwardIterator2 __result,
+// _BinaryOperation __reduce,
+// _UnaryOperation __transform) const noexcept;
+
+template <class _Backend, class _ExecutionPolicy>
+struct __transform_inclusive_scan_init;
+// template <class _Policy, class _ForwardIterator1, class _ForwardIterator2,
+// class _BinaryOperation, class _UnaryOperation, class _Tp>
+// optional<_ForwardIterator2>
+// operator()(_Policy&&, _ForwardIterator1 __first, _ForwardIterator1 __last,
+// _ForwardIterator2 __result,
+// _BinaryOperation __reduce,
+// _UnaryOperation __transform,
+// _Tp __init) const noexcept;
+
template <class _Backend, class _ExecutionPolicy>
struct __replace_copy_if;
// template <class _Policy, class _ForwardIterator, class _ForwardOutIterator, class _Predicate, class _Tp>
diff --git a/libcxx/include/__pstl/backends/default.h b/libcxx/include/__pstl/backends/default.h
index b5d7045bbb9b8..6e0b23c4d0189 100644
--- a/libcxx/include/__pstl/backends/default.h
+++ b/libcxx/include/__pstl/backends/default.h
@@ -906,6 +906,44 @@ struct __adjacent_difference<__default_backend_tag, _ExecutionPolicy> {
}
};
+//////////////////////////////////////////////////////////////
+// transform_inclusive_scan_init family
+//////////////////////////////////////////////////////////////
+
+template <class _ExecutionPolicy>
+struct __transform_inclusive_scan<__default_backend_tag, _ExecutionPolicy> {
+ template <class _Policy,
+ class _ForwardIterator1,
+ class _ForwardIterator2,
+ class _BinaryOperation,
+ class _UnaryOperation>
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI optional<_ForwardIterator2>
+ operator()(_Policy&& __policy,
+ _ForwardIterator1 __first,
+ _ForwardIterator1 __last,
+ _ForwardIterator2 __result,
+ _BinaryOperation __reduce,
+ _UnaryOperation __transform) const noexcept {
+ if (__first != __last) {
+ using _TransformInclusiveScanInit =
+ __dispatch<__transform_inclusive_scan_init, __current_configuration, _ExecutionPolicy>;
+ using _ValueType = __iterator_value_type<_ForwardIterator1>;
+ _ValueType __init = __transform(*__first);
+ *__result++ = __init;
+ if (++__first != __last)
+ return _TransformInclusiveScanInit()(
+ __policy,
+ std::move(__first),
+ std::move(__last),
+ std::move(__result),
+ std::move(__reduce),
+ std::move(__transform),
+ std::move(__init));
+ }
+ return __result;
+ }
+};
+
} // namespace __pstl
_LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/include/__pstl/backends/libdispatch.h b/libcxx/include/__pstl/backends/libdispatch.h
index dbd9fa34e1f87..d5cfff3b6829d 100644
--- a/libcxx/include/__pstl/backends/libdispatch.h
+++ b/libcxx/include/__pstl/backends/libdispatch.h
@@ -50,6 +50,7 @@
#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_inclusive_scan_init.h>
#include <__pstl/cpu_algos/transform_reduce.h>
#include <__utility/empty.h>
#include <__utility/exception_guard.h>
@@ -270,6 +271,64 @@ struct __cpu_traits<__libdispatch_backend_tag> {
__combiner);
}
+ template <class _RandomAccessIterator1,
+ class _RandomAccessIterator2,
+ class _Value,
+ class _Reduction,
+ class _Accumulation,
+ class _Scan>
+ _LIBCPP_HIDE_FROM_ABI static optional<__empty>
+ __scan(_RandomAccessIterator1 __first,
+ _RandomAccessIterator1 __last,
+ _RandomAccessIterator2 __result,
+ _Value __init,
+ _Reduction __reduction,
+ _Accumulation __accumulation,
+ _Scan __scan) {
+ if (__first == __last)
+ return __empty{};
+
+ auto __partitions = __libdispatch::__partition_chunks(__last - __first);
+
+ auto __destroy = [__count = __partitions.__chunk_count_](_Value* __ptr) {
+ std::destroy_n(__ptr, __count);
+ std::allocator<_Value>().deallocate(__ptr, __count);
+ };
+
+ // TODO: use __uninitialized_buffer
+ // TODO: check for malloc failure
+ // TODO: need one less element - the last chunk is not used in fact
+ unique_ptr<_Value[], decltype(__destroy)> __values(
+ std::allocator<_Value>().allocate(__partitions.__chunk_count_), __destroy);
+
+ // First: compute partial reductions for each chunk in parallel
+ __libdispatch::__dispatch_apply(__partitions.__chunk_count_, [&](size_t __chunk) {
+ auto __this_chunk_size = __chunk == 0 ? __partitions.__first_chunk_size_ : __partitions.__chunk_size_;
+ auto __index = __chunk == 0 ? 0
+ : (__chunk * __partitions.__chunk_size_) +
+ (__partitions.__first_chunk_size_ - __partitions.__chunk_size_);
+ std::__construct_at(
+ __values.get() + __chunk, __reduction(__first + __index, __first + __index + __this_chunk_size));
+ });
+
+ // Second: accumulate the partial reductions serially
+ __accumulation(__values.get(), __values.get() + __partitions.__chunk_count_, __init);
+
+ // Third: distribute the accumulated prefix sum to each chunk in a parallel scan
+ __libdispatch::__dispatch_apply(__partitions.__chunk_count_, [&](size_t __chunk) {
+ auto __this_chunk_size = __chunk == 0 ? __partitions.__first_chunk_size_ : __partitions.__chunk_size_;
+ auto __index = __chunk == 0 ? 0
+ : (__chunk * __partitions.__chunk_size_) +
+ (__partitions.__first_chunk_size_ - __partitions.__chunk_size_);
+ __scan(__first + __index,
+ __first + __index + __this_chunk_size,
+ __result + __index,
+ std::move(__chunk == 0 ? __init : __values.get()[__chunk - 1]));
+ });
+
+ return __empty{};
+ }
+
template <class _RandomAccessIterator, class _Comp, class _LeafSort>
_LIBCPP_HIDE_FROM_ABI static optional<__empty>
__stable_sort(_RandomAccessIterator __first, _RandomAccessIterator __last, _Comp __comp, _LeafSort __leaf_sort) {
@@ -422,6 +481,10 @@ template <class _ExecutionPolicy>
struct __transform_binary<__libdispatch_backend_tag, _ExecutionPolicy>
: __cpu_parallel_transform_binary<__libdispatch_backend_tag, _ExecutionPolicy> {};
+template <class _ExecutionPolicy>
+struct __transform_inclusive_scan_init<__libdispatch_backend_tag, _ExecutionPolicy>
+ : __cpu_parallel_transform_inclusive_scan_init<__libdispatch_backend_tag, _ExecutionPolicy> {};
+
template <class _ExecutionPolicy>
struct __transform_reduce<__libdispatch_backend_tag, _ExecutionPolicy>
: __cpu_parallel_transform_reduce<__libdispatch_backend_tag, _ExecutionPolicy> {};
diff --git a/libcxx/include/__pstl/backends/serial.h b/libcxx/include/__pstl/backends/serial.h
index 85c73b0f15db5..033b68eb32fc2 100644
--- a/libcxx/include/__pstl/backends/serial.h
+++ b/libcxx/include/__pstl/backends/serial.h
@@ -24,6 +24,7 @@
#include <__algorithm/stable_sort.h>
#include <__algorithm/transform.h>
#include <__config>
+#include <__numeric/transform_inclusive_scan.h>
#include <__numeric/transform_reduce.h>
#include <__optional/optional.h>
#include <__pstl/backend_fwd.h>
@@ -239,6 +240,32 @@ struct __transform_binary<__serial_backend_tag, _ExecutionPolicy> {
}
};
+template <class _ExecutionPolicy>
+struct __transform_inclusive_scan_init<__serial_backend_tag, _ExecutionPolicy> {
+ template <class _Policy,
+ class _ForwardIterator1,
+ class _ForwardIterator2,
+ class _BinaryOperation,
+ class _UnaryOperation,
+ class _Tp>
+ _LIBCPP_HIDE_FROM_ABI optional<_ForwardIterator2> operator()(
+ _Policy&&,
+ _ForwardIterator1 __first,
+ _ForwardIterator1 __last,
+ _ForwardIterator2 __result,
+ _BinaryOperation __reduce,
+ _UnaryOperation __transform,
+ _Tp __init) const noexcept {
+ return std::transform_inclusive_scan(
+ std::move(__first),
+ std::move(__last),
+ std::move(__result),
+ std::move(__reduce),
+ std::move(__transform),
+ std::move(__init));
+ }
+};
+
template <class _ExecutionPolicy>
struct __transform_reduce<__serial_backend_tag, _ExecutionPolicy> {
template <class _Policy, class _ForwardIterator, class _Tp, class _BinaryOperation, class _UnaryOperation>
diff --git a/libcxx/include/__pstl/backends/std_thread.h b/libcxx/include/__pstl/backends/std_thread.h
index bc7fc020867f8..85ecc38deb064 100644
--- a/libcxx/include/__pstl/backends/std_thread.h
+++ b/libcxx/include/__pstl/backends/std_thread.h
@@ -29,6 +29,7 @@
#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_inclusive_scan_init.h>
#include <__pstl/cpu_algos/transform_reduce.h>
#include <__utility/empty.h>
#include <__utility/move.h>
@@ -67,6 +68,25 @@ struct __cpu_traits<__std_thread_backend_tag> {
return __reduce(std::move(__first), std::move(__last), std::move(__init));
}
+ template <class _RandomAccessIterator1,
+ class _RandomAccessIterator2,
+ class _Value,
+ class _Reduction,
+ class _Accumulation,
+ class _Scan>
+ _LIBCPP_HIDE_FROM_ABI static optional<__empty>
+ __scan(_RandomAccessIterator1 __first,
+ _RandomAccessIterator1 __last,
+ _RandomAccessIterator2 __result,
+ _Value __init,
+ _Reduction /*__reduction*/,
+ _Accumulation /*__accumulation*/,
+ _Scan __scan) {
+ if (__first != __last)
+ __scan(__first, __last, __result, std::move(__init));
+ return __empty{};
+ }
+
template <class _RandomAccessIterator, class _Compare, class _LeafSort>
_LIBCPP_HIDE_FROM_ABI static optional<__empty>
__stable_sort(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp, _LeafSort __leaf_sort) {
@@ -153,6 +173,10 @@ template <class _ExecutionPolicy>
struct __transform_binary<__std_thread_backend_tag, _ExecutionPolicy>
: __cpu_parallel_transform_binary<__std_thread_backend_tag, _ExecutionPolicy> {};
+template <class _ExecutionPolicy>
+struct __transform_inclusive_scan_init<__std_thread_backend_tag, _ExecutionPolicy>
+ : __cpu_parallel_transform_inclusive_scan_init<__std_thread_backend_tag, _ExecutionPolicy> {};
+
template <class _ExecutionPolicy>
struct __transform_reduce<__std_thread_backend_tag, _ExecutionPolicy>
: __cpu_parallel_transform_reduce<__std_thread_backend_tag, _ExecutionPolicy> {};
diff --git a/libcxx/include/__pstl/cpu_algos/transform_inclusive_scan_init.h b/libcxx/include/__pstl/cpu_algos/transform_inclusive_scan_init.h
new file mode 100644
index 0000000000000..cba77725a7e74
--- /dev/null
+++ b/libcxx/include/__pstl/cpu_algos/transform_inclusive_scan_init.h
@@ -0,0 +1,97 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_TRANSFORM_INCLUSIVE_SCAN_INIT_H
+#define _LIBCPP___PSTL_CPU_ALGOS_TRANSFORM_INCLUSIVE_SCAN_INIT_H
+
+#include <__config>
+#include <__functional/identity.h>
+#include <__functional/operations.h>
+#include <__iterator/concepts.h>
+#include <__iterator/iterator_traits.h>
+#include <__numeric/inclusive_scan.h>
+#include <__numeric/transform_inclusive_scan.h>
+#include <__numeric/transform_reduce.h>
+#include <__optional/nullopt_t.h>
+#include <__optional/optional.h>
+#include <__pstl/backend_fwd.h>
+#include <__pstl/cpu_algos/cpu_traits.h>
+#include <__type_traits/is_execution_policy.h>
+#include <__utility/move.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_transform_inclusive_scan_init {
+ template <class _Policy,
+ class _ForwardIterator1,
+ class _ForwardIterator2,
+ class _BinaryOperation,
+ class _UnaryOperation,
+ class _Tp>
+ _LIBCPP_HIDE_FROM_ABI optional<_ForwardIterator2> operator()(
+ _Policy&&,
+ _ForwardIterator1 __first,
+ _ForwardIterator1 __last,
+ _ForwardIterator2 __result,
+ _BinaryOperation __reduce,
+ _UnaryOperation __transform,
+ _Tp __init) const noexcept {
+ if constexpr (__is_parallel_execution_policy_v<_RawExecutionPolicy> &&
+ __has_random_access_iterator_category_or_concept<_ForwardIterator1>::value &&
+ __has_random_access_iterator_category_or_concept<_ForwardIterator2>::value) {
+ auto __ret = __cpu_traits<_Backend>::__scan(
+ __first,
+ __last,
+ __result,
+ std::move(__init),
+ [&](auto __chunk_first, auto __chunk_last) {
+ return std::transform_reduce(
+ __chunk_first + 1, __chunk_last, __transform(*__chunk_first), __reduce, __transform);
+ },
+ [&](auto __prefix_first, auto __prefix_last, auto __init_val) {
+ std::inclusive_scan(__prefix_first, __prefix_last, __prefix_first, __reduce, std::move(__init_val));
+ },
+ [&](auto __chunk_first, auto __chunk_last, auto __chunk_result, auto __chunk_init) {
+ std::transform_inclusive_scan(
+ __chunk_first, __chunk_last, __chunk_result, __reduce, __transform, std::move(__chunk_init));
+ });
+ if (!__ret)
+ return nullopt;
+ return __result + (__last - __first);
+ } else {
+ // Non-random access iterators cannot be processed in parallel, fall back to the sequential implementation.
+ return std::transform_inclusive_scan(
+ std::move(__first),
+ std::move(__last),
+ std::move(__result),
+ std::move(__reduce),
+ std::move(__transform),
+ std::move(__init));
+ }
+ }
+};
+
+} // namespace __pstl
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_STD_VER >= 17
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___PSTL_CPU_ALGOS_TRANSFORM_INCLUSIVE_SCAN_INIT_H
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 214020750e1af..b5fbf6c5aecfe 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -2469,6 +2469,9 @@ module std {
header "__pstl/cpu_algos/stable_sort.h"
export std_core.utility_core.empty
}
+ module transform_inclusive_scan_init {
+ header "__pstl/cpu_algos/transform_inclusive_scan_init.h"
+ }
module transform {
header "__pstl/cpu_algos/transform.h"
}
diff --git a/libcxx/test/std/numerics/numeric.ops/transform.inclusive.scan/pstl.transform_inclusive_scan_bop_uop.pass.cpp b/libcxx/test/std/numerics/numeric.ops/transform.inclusive.scan/pstl.transform_inclusive_scan_bop_uop.pass.cpp
new file mode 100644
index 0000000000000..c61df34828538
--- /dev/null
+++ b/libcxx/test/std/numerics/numeric.ops/transform.inclusive.scan/pstl.transform_inclusive_scan_bop_uop.pass.cpp
@@ -0,0 +1,159 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++17
+
+// UNSUPPORTED: libcpp-has-no-incomplete-pstl
+
+// template <class ExecutionPolicy,
+// class ForwardIterator1,
+// class ForwardIterator2,
+// class BinaryOperation,
+// class UnaryOperation,
+// class T>
+// ForwardIterator2 transform_inclusive_scan(ExecutionPolicy&& exec,
+// ForwardIterator1 first,
+// ForwardIterator1 last,
+// ForwardIterator2 result,
+// BinaryOperation reduce,
+// UnaryOperation transform);
+
+#include <algorithm>
+#include <cassert>
+#include <functional>
+#include <iterator>
+#include <limits>
+#include <numeric>
+
+#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(transform_inclusive_scan);
+
+static_assert(sfinae_test_transform_inclusive_scan<int, int*, int*, int*, int (*)(int, int), int (*)(int)>);
+static_assert(!sfinae_test_transform_inclusive_scan< std::execution::parallel_policy,
+ int*,
+ int*,
+ int*,
+ int (*)(int, int),
+ int (*)(int)>);
+
+struct add_one {
+ template <typename T>
+ constexpr T operator()(T x) const {
+ return x + 1;
+ }
+};
+
+template <typename T>
+constexpr auto triangle(T n) {
+ return n * (n + 1) / 2;
+}
+
+template <class Iter1, class Iter2>
+struct TestInt {
+ template <class ExecutionPolicy>
+ void operator()(ExecutionPolicy&& policy) {
+ { // Check the return type
+ int a[] = {0};
+ auto res = std::transform_inclusive_scan(
+ policy, Iter1(std::begin(a)), Iter1(std::end(a)), Iter2(std::begin(a)), std::plus<int>{}, [](int x) {
+ return x;
+ });
+ static_assert(std::is_same_v<decltype(res), Iter2>);
+ }
+ { // Empty
+ int a[] = {-1};
+ std::transform_inclusive_scan(
+ policy, Iter1(std::begin(a)), Iter1(std::begin(a)), Iter2(std::begin(a)), std::plus<>{}, add_one{});
+ assert(a[0] == -1);
+ }
+ { // Scan a uniform array
+ int a[10];
+ std::fill(std::begin(a), std::end(a), 3);
+ std::transform_inclusive_scan(
+ policy, Iter1(std::begin(a)), Iter1(std::end(a)), Iter2(std::begin(a)), std::plus<>{}, add_one{});
+ for (std::size_t i = 0; i < std::size(a); ++i)
+ assert(a[i] == (static_cast<int>(i) + 1) * 4);
+ }
+ { // Scan an increasing array starting from 0
+ int a[10];
+ std::iota(std::begin(a), std::end(a), 0);
+ std::transform_inclusive_scan(
+ policy, Iter1(std::begin(a)), Iter1(std::end(a)), Iter2(std::begin(a)), std::plus<>{}, add_one{});
+ for (std::size_t i = 0; i < std::size(a); ++i)
+ assert(a[i] == static_cast<int>(triangle(i) + i + 1));
+ }
+ { // Scan an increasing array starting from 1
+ int a[10];
+ std::iota(std::begin(a), std::end(a), 1);
+ std::transform_inclusive_scan(
+ policy, Iter1(std::begin(a)), Iter1(std::end(a)), Iter2(std::begin(a)), std::plus<>{}, add_one{});
+ for (std::size_t i = 0; i < std::size(a); ++i)
+ assert(a[i] == static_cast<int>(triangle(i + 1) + i + 1));
+ }
+ { // Test against pre-computed expected results
+ auto test = [&](auto first, auto last, auto reduce, auto transform, auto exp_first, auto exp_last) {
+ int out[5];
+ // Not in place
+ auto end = std::transform_inclusive_scan(policy, first, last, Iter2(std::begin(out)), reduce, transform);
+ assert(std::equal(Iter2(std::begin(out)), end, exp_first, exp_last));
+ // In place
+ std::copy(first, last, out);
+ end = std::transform_inclusive_scan(
+ policy, Iter2(std::begin(out)), end, Iter2(std::begin(out)), reduce, transform);
+ assert(std::equal(Iter2(std::begin(out)), end, exp_first, exp_last));
+ };
+ const int a[] = {1, 3, 5, 7, 9};
+ const int exp_plus_inc_0[] = {2, 6, 12, 20, 30}; // plus, add_one, init = 0
+ const int exp_mul_inc_0[] = {2, 8, 48, 384, 3840}; // multiplies, add_one, init = 0
+ const int exp_plus_neg_0[] = {-1, -4, -9, -16, -25}; // plus, negate, init = 0
+ const int exp_mul_neg_0[] = {-1, 3, -15, 105, -945}; // multiplies, negate, init = 0
+ static_assert(std::size(a) == std::size(exp_plus_inc_0)); // just to be sure
+ static_assert(std::size(a) == std::size(exp_mul_inc_0)); // just to be sure
+ static_assert(std::size(a) == std::size(exp_plus_neg_0)); // just to be sure
+ static_assert(std::size(a) == std::size(exp_mul_neg_0)); // just to be sure
+ std::plus<> plus;
+ std::multiplies<> mult;
+ std::negate<> negate;
+ for (unsigned int i = 0; i < std::size(a); ++i) {
+ test(Iter1(std::begin(a)), Iter1(std::begin(a) + i), plus, add_one{}, exp_plus_inc_0, exp_plus_inc_0 + i);
+ test(Iter1(std::begin(a)), Iter1(std::begin(a) + i), mult, add_one{}, exp_mul_inc_0, exp_mul_inc_0 + i);
+ test(Iter1(std::begin(a)), Iter1(std::begin(a) + i), plus, negate, exp_plus_neg_0, exp_plus_neg_0 + i);
+ test(Iter1(std::begin(a)), Iter1(std::begin(a) + i), mult, negate, exp_mul_neg_0, exp_mul_neg_0 + i);
+ }
+ }
+ { // Constuct iota from std::plus<> and add_one
+ int ia[1073];
+ int ib[1073];
+ std::fill(std::begin(ia), std::end(ia), 0);
+ runway_sample(std::size(ia) + 1, [&](size_t i) {
+ Iter2 r = std::transform_inclusive_scan(
+ policy, Iter1(std::begin(ia)), Iter1(std::begin(ia) + i), Iter2(std::begin(ib)), std::plus<>{}, add_one{});
+ assert(r == Iter2(std::begin(ib) + i));
+ for (size_t j = 0; j < i; ++j) {
+ assert(ib[j] == static_cast<int>(j) + 1);
+ }
+ std::fill(std::begin(ib), std::begin(ib) + i, -1);
+ });
+ }
+ }
+};
+
+int main(int, char**) {
+ types::for_each(types::forward_iterator_list<const int*>{}, types::apply_type_identity{[](auto v) {
+ using Iter = typename decltype(v)::type;
+ types::for_each(
+ types::forward_iterator_list<int*>{},
+ TestIteratorWithPolicies<types::partial_instantiation<TestInt, Iter>::template apply>{});
+ }});
+ return 0;
+}
diff --git a/libcxx/test/std/numerics/numeric.ops/transform.inclusive.scan/pstl.transform_inclusive_scan_bop_uop_init.pass.cpp b/libcxx/test/std/numerics/numeric.ops/transform.inclusive.scan/pstl.transform_inclusive_scan_bop_uop_init.pass.cpp
new file mode 100644
index 0000000000000..a993816ac91c9
--- /dev/null
+++ b/libcxx/test/std/numerics/numeric.ops/transform.inclusive.scan/pstl.transform_inclusive_scan_bop_uop_init.pass.cpp
@@ -0,0 +1,216 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++17
+
+// UNSUPPORTED: libcpp-has-no-incomplete-pstl
+
+// template <class ExecutionPolicy,
+// class ForwardIterator1,
+// class ForwardIterator2,
+// class BinaryOperation,
+// class UnaryOperation,
+// class T>
+// ForwardIterator2 transform_inclusive_scan(ExecutionPolicy&& exec,
+// ForwardIterator1 first,
+// ForwardIterator1 last,
+// ForwardIterator2 result,
+// BinaryOperation reduce,
+// UnaryOperation transform,
+// T init);
+
+#include <algorithm>
+#include <cassert>
+#include <functional>
+#include <iterator>
+#include <limits>
+#include <numeric>
+
+#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(transform_inclusive_scan);
+
+static_assert(sfinae_test_transform_inclusive_scan<int, int*, int*, int*, int (*)(int, int), int (*)(int), int>);
+static_assert(!sfinae_test_transform_inclusive_scan<
+ std::execution::parallel_policy,
+ int*,
+ int*,
+ int*,
+ int (*)(int, int),
+ int (*)(int),
+ int>);
+
+struct add_one {
+ template <typename T>
+ constexpr T operator()(T x) const {
+ return x + 1;
+ }
+};
+
+template <typename T>
+constexpr auto triangle(T n) {
+ return n * (n + 1) / 2;
+}
+
+template <class Iter1, class Iter2>
+struct TestInt {
+ template <class ExecutionPolicy>
+ void operator()(ExecutionPolicy&& policy) {
+ { // Check the return type
+ int a[] = {0};
+ auto res = std::transform_inclusive_scan(
+ policy,
+ Iter1(std::begin(a)),
+ Iter1(std::end(a)),
+ Iter2(std::begin(a)),
+ std::plus<int>{},
+ [](int x) { return x; },
+ 0);
+ static_assert(std::is_same_v<decltype(res), Iter2>);
+ }
+ { // Empty
+ int a[] = {-1};
+ std::transform_inclusive_scan(
+ policy, Iter1(std::begin(a)), Iter1(std::begin(a)), Iter2(std::begin(a)), std::plus<>{}, add_one{}, 10);
+ assert(a[0] == -1);
+ }
+ { // Scan a uniform array
+ int a[10];
+ std::fill(std::begin(a), std::end(a), 3);
+ std::transform_inclusive_scan(
+ policy, Iter1(std::begin(a)), Iter1(std::end(a)), Iter2(std::begin(a)), std::plus<>{}, add_one{}, 50);
+ for (std::size_t i = 0; i < std::size(a); ++i)
+ assert(a[i] == 50 + (static_cast<int>(i) + 1) * 4);
+ }
+ { // Scan an increasing array starting from 0
+ int a[10];
+ std::iota(std::begin(a), std::end(a), 0);
+ std::transform_inclusive_scan(
+ policy, Iter1(std::begin(a)), Iter1(std::end(a)), Iter2(std::begin(a)), std::plus<>{}, add_one{}, 30);
+ for (std::size_t i = 0; i < std::size(a); ++i)
+ assert(a[i] == static_cast<int>(30 + triangle(i) + i + 1));
+ }
+ { // Scan an increasing array starting from 1
+ int a[10];
+ std::iota(std::begin(a), std::end(a), 1);
+ std::transform_inclusive_scan(
+ policy, Iter1(std::begin(a)), Iter1(std::end(a)), Iter2(std::begin(a)), std::plus<>{}, add_one{}, 40);
+ for (std::size_t i = 0; i < std::size(a); ++i)
+ assert(a[i] == static_cast<int>(40 + triangle(i + 1) + i + 1));
+ }
+ { // Test against pre-computed expected results
+ auto test = [&](auto first, auto last, auto reduce, auto transform, auto init, auto exp_first, auto exp_last) {
+ int out[5];
+ // Not in place
+ auto end = std::transform_inclusive_scan(policy, first, last, Iter2(std::begin(out)), reduce, transform, init);
+ assert(std::equal(Iter2(std::begin(out)), end, exp_first, exp_last));
+ // In place
+ std::copy(first, last, out);
+ end = std::transform_inclusive_scan(
+ policy, Iter2(std::begin(out)), end, Iter2(std::begin(out)), reduce, transform, init);
+ assert(std::equal(Iter2(std::begin(out)), end, exp_first, exp_last));
+ };
+ const int a[] = {1, 3, 5, 7, 9};
+ const int exp_plus_inc_0[] = {2, 6, 12, 20, 30}; // plus, add_one, init = 0
+ const int exp_mul_inc_0[] = {0, 0, 0, 0, 0}; // multiplies, add_one, init = 0
+ const int exp_plus_neg_0[] = {-1, -4, -9, -16, -25}; // plus, negate, init = 0
+ const int exp_mul_neg_0[] = {0, 0, 0, 0, 0}; // multiplies, negate, init = 0
+ const int exp_plus_inc_2[] = {4, 8, 14, 22, 32}; // plus, add_one, init = 2
+ const int exp_mul_inc_2[] = {4, 16, 96, 768, 7680}; // multiplies, add_one, init = 2
+ const int exp_plus_neg_2[] = {1, -2, -7, -14, -23}; // plus, negate, init = 2
+ const int exp_mul_neg_2[] = {-2, 6, -30, 210, -1890}; // multiplies, negate, init = 2
+ static_assert(std::size(a) == std::size(exp_plus_inc_0)); // just to be sure
+ static_assert(std::size(a) == std::size(exp_mul_inc_0)); // just to be sure
+ static_assert(std::size(a) == std::size(exp_plus_neg_0)); // just to be sure
+ static_assert(std::size(a) == std::size(exp_mul_neg_0)); // just to be sure
+ static_assert(std::size(a) == std::size(exp_plus_inc_2)); // just to be sure
+ static_assert(std::size(a) == std::size(exp_mul_inc_2)); // just to be sure
+ static_assert(std::size(a) == std::size(exp_plus_neg_2)); // just to be sure
+ static_assert(std::size(a) == std::size(exp_mul_neg_2)); // just to be sure
+ std::plus<> plus;
+ std::multiplies<> mult;
+ std::negate<> negate;
+ for (unsigned int i = 0; i < std::size(a); ++i) {
+ test(Iter1(std::begin(a)), Iter1(std::begin(a) + i), plus, add_one{}, 0, exp_plus_inc_0, exp_plus_inc_0 + i);
+ test(Iter1(std::begin(a)), Iter1(std::begin(a) + i), mult, add_one{}, 0, exp_mul_inc_0, exp_mul_inc_0 + i);
+ test(Iter1(std::begin(a)), Iter1(std::begin(a) + i), plus, negate, 0, exp_plus_neg_0, exp_plus_neg_0 + i);
+ test(Iter1(std::begin(a)), Iter1(std::begin(a) + i), mult, negate, 0, exp_mul_neg_0, exp_mul_neg_0 + i);
+ test(Iter1(std::begin(a)), Iter1(std::begin(a) + i), plus, add_one{}, 2, exp_plus_inc_2, exp_plus_inc_2 + i);
+ test(Iter1(std::begin(a)), Iter1(std::begin(a) + i), mult, add_one{}, 2, exp_mul_inc_2, exp_mul_inc_2 + i);
+ test(Iter1(std::begin(a)), Iter1(std::begin(a) + i), plus, negate, 2, exp_plus_neg_2, exp_plus_neg_2 + i);
+ test(Iter1(std::begin(a)), Iter1(std::begin(a) + i), mult, negate, 2, exp_mul_neg_2, exp_mul_neg_2 + i);
+ }
+ }
+ { // Constuct iota from std::plus<> and add_one
+ int ia[1073];
+ int ib[1073];
+ std::fill(std::begin(ia), std::end(ia), 0);
+ runway_sample(std::size(ia) + 1, [&](size_t i) {
+ Iter2 r = std::transform_inclusive_scan(
+ policy,
+ Iter1(std::begin(ia)),
+ Iter1(std::begin(ia) + i),
+ Iter2(std::begin(ib)),
+ std::plus<>{},
+ add_one{},
+ 1);
+ assert(r == Iter2(std::begin(ib) + i));
+ for (size_t j = 0; j < i; ++j) {
+ assert(ib[j] == static_cast<int>(j) + 2);
+ }
+ std::fill(std::begin(ib), std::begin(ib) + i, -1);
+ });
+ }
+ }
+};
+
+template <class Iter1, class Iter2>
+struct TestUnsignedCharToSize_t {
+ template <class ExecutionPolicy>
+ void operator()(ExecutionPolicy&& policy) {
+ // Make sure that the calculations are done using the init typedef
+ unsigned char v[10];
+ std::iota(std::begin(v), std::end(v), static_cast<unsigned char>(1));
+ std::size_t res[10];
+ std::transform_inclusive_scan(
+ policy,
+ Iter1(std::begin(v)),
+ Iter1(std::end(v)),
+ Iter2(std::begin(res)),
+ std::multiplies<>(),
+ add_one{},
+ std::size_t{1});
+ std::size_t j = 2;
+ assert(res[0] == 2);
+ for (std::size_t i = 1; i < std::size(res); ++i) {
+ j *= i + 2;
+ assert(res[i] == j);
+ }
+ }
+};
+
+int main(int, char**) {
+ types::for_each(types::forward_iterator_list<const int*>{}, types::apply_type_identity{[](auto v) {
+ using Iter = typename decltype(v)::type;
+ types::for_each(
+ types::forward_iterator_list<int*>{},
+ TestIteratorWithPolicies<types::partial_instantiation<TestInt, Iter>::template apply>{});
+ }});
+ types::for_each(
+ types::forward_iterator_list<const unsigned char*>{}, types::apply_type_identity{[](auto v) {
+ using Iter = typename decltype(v)::type;
+ types::for_each(
+ types::forward_iterator_list<std::size_t*>{},
+ TestIteratorWithPolicies<types::partial_instantiation<TestUnsignedCharToSize_t, Iter>::template apply>{});
+ }});
+ return 0;
+}
More information about the libcxx-commits
mailing list