[libcxx-commits] [libcxx] [libc++][pstl] Add more tests of parallel std::for_each[_n] (PR #231918)
via libcxx-commits
libcxx-commits at lists.llvm.org
Wed Oct 14 07:50:30 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-libcxx
Author: Michael G. Kazakov (mikekazakov)
<details>
<summary>Changes</summary>
The existing test coverage of parallel `std::for_each[_n]()` is rather shallow, this PR adds more tests.
---
Full diff: https://github.com/llvm/llvm-project/pull/231918.diff
2 Files Affected:
- (modified) libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/pstl.for_each.pass.cpp (+113-15)
- (modified) libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/pstl.for_each_n.pass.cpp (+115-19)
``````````diff
diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/pstl.for_each.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/pstl.for_each.pass.cpp
index 97b0220309720e..c2d68d8fd48cc3 100644
--- a/libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/pstl.for_each.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/pstl.for_each.pass.cpp
@@ -6,7 +6,7 @@
//
//===----------------------------------------------------------------------===//
-// UNSUPPORTED: c++03, c++11, c++14
+// REQUIRES: std-at-least-c++17
// UNSUPPORTED: libcpp-has-no-incomplete-pstl
@@ -18,39 +18,137 @@
// Function f);
#include <algorithm>
-#include <atomic>
#include <cassert>
+#include <cstddef>
+#include <iterator>
+#include <numeric>
+#include <type_traits>
#include <vector>
-#include "test_macros.h"
#include "test_execution_policies.h"
#include "test_iterators.h"
-#include <type_algorithms.h>
+#include "test_macros.h"
+#include "type_algorithms.h"
+#include "runway_sample.h"
EXECUTION_POLICY_SFINAE_TEST(for_each);
static_assert(sfinae_test_for_each<int, int*, int*, bool (*)(int)>);
static_assert(!sfinae_test_for_each<std::execution::parallel_policy, int*, int*, bool (*)(int)>);
+struct NonCopyableNonMovable {
+ NonCopyableNonMovable() = default;
+ explicit NonCopyableNonMovable(int i) : i_(i) {}
+ NonCopyableNonMovable(const NonCopyableNonMovable&) = delete;
+ NonCopyableNonMovable(NonCopyableNonMovable&&) = delete;
+ NonCopyableNonMovable& operator=(const NonCopyableNonMovable&) = delete;
+ NonCopyableNonMovable& operator=(NonCopyableNonMovable&&) = delete;
+ int value() const { return i_; }
+ void visit() { ++visits_; }
+ int visits() const { return visits_; }
+ void reset() { visits_ = 0; }
+
+private:
+ int i_ = 0;
+ int visits_ = 0;
+};
+
template <class Iter>
struct Test {
- template <class Policy>
- void operator()(Policy&& policy) {
- int sizes[] = {0, 1, 2, 100};
- for (auto size : sizes) {
- std::vector<int> a(size);
- std::vector<Bool> called(size);
- std::for_each(policy, Iter(std::data(a)), Iter(std::data(a) + std::size(a)), [&](int& v) {
- assert(!called[&v - a.data()]);
- called[&v - a.data()] = true;
+ template <class ExecutionPolicy>
+ void operator()(ExecutionPolicy&& policy) {
+ { // Check the return type
+ int a[] = {0};
+ auto f = [](int&) {};
+ static_assert(std::is_same_v<decltype(std::for_each(policy, Iter(std::begin(a)), Iter(std::begin(a)), f)), void>);
+ }
+ { // Empty range, the function is never called
+ int a[] = {42};
+ std::for_each(policy, Iter(std::begin(a)), Iter(std::begin(a)), [](int&) { assert(false); });
+ assert(a[0] == 42);
+ }
+ { // Single element
+ int a[] = {42};
+ std::for_each(policy, Iter(std::begin(a)), Iter(std::end(a)), [](int& v) { ++v; });
+ assert(a[0] == 43);
+ }
+ { // Two elements
+ int a[] = {1, 2};
+ std::for_each(policy, Iter(std::begin(a)), Iter(std::end(a)), [](int& v) { v *= 10; });
+ assert(a[0] == 10);
+ assert(a[1] == 20);
+ }
+ { // Three elements
+ int a[] = {1, 2, 3};
+ std::for_each(policy, Iter(std::begin(a)), Iter(std::end(a)), [](int& v) { v *= 10; });
+ assert(a[0] == 10);
+ assert(a[1] == 20);
+ assert(a[2] == 30);
+ }
+ { // Four elements, only a sub-range is processed
+ int a[] = {1, 2, 3, 4};
+ std::for_each(policy, Iter(std::begin(a) + 1), Iter(std::begin(a) + 3), [](int& v) { v = -v; });
+ assert(a[0] == 1);
+ assert(a[1] == -2);
+ assert(a[2] == -3);
+ assert(a[3] == 4);
+ }
+ { // The function can take its argument by value
+ int a[] = {1, 2, 3, 4};
+ bool seen[] = {false, false, false, false};
+ std::for_each(policy, Iter(std::begin(a)), Iter(std::end(a)), [&](int v) {
+ assert(!seen[v - 1]);
+ seen[v - 1] = true;
+ });
+ assert(std::all_of(std::begin(seen), std::end(seen), [](bool b) { return b; }));
+ }
+ { // Check that every single element in [0, i) is visited exactly once and the rest is untouched
+ int a[1073];
+ int visits[1073];
+ runway_sample(std::size(a) + 1, [&](std::size_t i) {
+ std::iota(std::begin(a), std::end(a), 0);
+ std::fill(std::begin(visits), std::end(visits), 0);
+ std::for_each(policy, Iter(std::begin(a)), Iter(std::begin(a) + i), [&](int& v) {
+ ++visits[&v - std::begin(a)];
+ v = -v;
+ });
+ for (std::size_t j = 0; j < std::size(a); ++j) {
+ assert(visits[j] == (j < i ? 1 : 0));
+ assert(a[j] == (j < i ? -static_cast<int>(j) : static_cast<int>(j)));
+ }
+ });
+ }
+ }
+};
+
+template <class Iter>
+struct TestNonCopyableNonMovable {
+ template <class ExecutionPolicy>
+ void operator()(ExecutionPolicy&& policy) {
+ { // Few elements
+ NonCopyableNonMovable a[] = {NonCopyableNonMovable(1), NonCopyableNonMovable(5), NonCopyableNonMovable(7)};
+ std::for_each(policy, Iter(std::begin(a)), Iter(std::end(a)), [](NonCopyableNonMovable& x) { x.visit(); });
+ assert(a[0].visits() == 1 && a[1].visits() == 1 && a[2].visits() == 1);
+ assert(a[0].value() == 1 && a[1].value() == 5 && a[2].value() == 7);
+ }
+ { // Many elements, check that every single element in [0, i) is visited exactly once
+ NonCopyableNonMovable a[1073];
+ runway_sample(std::size(a) + 1, [&](std::size_t i) {
+ std::for_each(policy, Iter(std::begin(a)), Iter(std::begin(a) + i), [](NonCopyableNonMovable& x) {
+ x.visit();
+ });
+ for (std::size_t j = 0; j < std::size(a); ++j) {
+ assert(a[j].visits() == (j < i ? 1 : 0));
+ a[j].reset();
+ }
});
- assert(std::all_of(std::begin(called), std::end(called), [](bool b) { return b; }));
}
}
};
int main(int, char**) {
types::for_each(types::forward_iterator_list<int*>{}, TestIteratorWithPolicies<Test>{});
-
+ types::for_each(
+ types::forward_iterator_list<NonCopyableNonMovable*>{}, TestIteratorWithPolicies<TestNonCopyableNonMovable>{});
return 0;
}
diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/pstl.for_each_n.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/pstl.for_each_n.pass.cpp
index d79a77719f1e94..1a3fc00b233f2e 100644
--- a/libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/pstl.for_each_n.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/pstl.for_each_n.pass.cpp
@@ -6,7 +6,7 @@
//
//===----------------------------------------------------------------------===//
-// UNSUPPORTED: c++03, c++11, c++14
+// REQUIRES: std-at-least-c++17
// UNSUPPORTED: libcpp-has-no-incomplete-pstl
@@ -17,48 +17,144 @@
// Function f);
#include <algorithm>
-#include <atomic>
#include <cassert>
+#include <cstddef>
+#include <iterator>
+#include <numeric>
#include <type_traits>
-#include <vector>
-#include "test_macros.h"
#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(for_each_n);
static_assert(sfinae_test_for_each_n<int, int*, int, bool (*)(int)>);
static_assert(!sfinae_test_for_each_n<std::execution::parallel_policy, int*, int, bool (*)(int)>);
+struct NonCopyableNonMovable {
+ NonCopyableNonMovable() = default;
+ explicit NonCopyableNonMovable(int i) : i_(i) {}
+ NonCopyableNonMovable(const NonCopyableNonMovable&) = delete;
+ NonCopyableNonMovable(NonCopyableNonMovable&&) = delete;
+ NonCopyableNonMovable& operator=(const NonCopyableNonMovable&) = delete;
+ NonCopyableNonMovable& operator=(NonCopyableNonMovable&&) = delete;
+ int value() const { return i_; }
+ void visit() { ++visits_; }
+ int visits() const { return visits_; }
+ void reset() { visits_ = 0; }
+
+private:
+ int i_ = 0;
+ int visits_ = 0;
+};
+
template <class Iter>
struct Test {
- template <class Policy>
- void operator()(Policy&& policy) {
+ template <class ExecutionPolicy>
+ void operator()(ExecutionPolicy&& policy) {
{ // Check the return type
int a[] = {0};
- auto res = std::for_each_n(policy, Iter(std::begin(a)), 0, [](int) {});
+ auto res = std::for_each_n(policy, Iter(std::begin(a)), 0, [](int&) {});
static_assert(std::is_same_v<decltype(res), Iter>);
}
- { // Check that every single element is visited
- int sizes[] = {0, 1, 2, 100};
- for (auto size : sizes) {
- std::vector<int> a(size);
- std::vector<Bool> called(size);
- auto res = std::for_each_n(policy, Iter(std::data(a)), std::size(a), [&](int& v) {
- assert(!called[&v - a.data()]);
- called[&v - a.data()] = true;
+ { // Empty range, the function is never called
+ int a[] = {42};
+ auto res = std::for_each_n(policy, Iter(std::begin(a)), 0, [](int&) { assert(false); });
+ assert(res == Iter(std::begin(a)));
+ assert(a[0] == 42);
+ }
+ { // Single element
+ int a[] = {42};
+ auto res = std::for_each_n(policy, Iter(std::begin(a)), 1, [](int& v) { ++v; });
+ assert(res == Iter(std::end(a)));
+ assert(a[0] == 43);
+ }
+ { // Two elements
+ int a[] = {1, 2};
+ auto res = std::for_each_n(policy, Iter(std::begin(a)), 2, [](int& v) { v *= 10; });
+ assert(res == Iter(std::end(a)));
+ assert(a[0] == 10);
+ assert(a[1] == 20);
+ }
+ { // Three elements
+ int a[] = {1, 2, 3};
+ auto res = std::for_each_n(policy, Iter(std::begin(a)), 3, [](int& v) { v *= 10; });
+ assert(res == Iter(std::end(a)));
+ assert(a[0] == 10);
+ assert(a[1] == 20);
+ assert(a[2] == 30);
+ }
+ { // Four elements, only a sub-range is processed
+ int a[] = {1, 2, 3, 4};
+ auto res = std::for_each_n(policy, Iter(std::begin(a) + 1), 2, [](int& v) { v = -v; });
+ assert(res == Iter(std::begin(a) + 3));
+ assert(a[0] == 1);
+ assert(a[1] == -2);
+ assert(a[2] == -3);
+ assert(a[3] == 4);
+ }
+ { // The function can take its argument by value
+ int a[] = {1, 2, 3, 4};
+ bool seen[] = {false, false, false, false};
+ auto res = std::for_each_n(policy, Iter(std::begin(a)), std::size(a), [&](int v) {
+ assert(!seen[v - 1]);
+ seen[v - 1] = true;
+ });
+ assert(res == Iter(std::end(a)));
+ assert(std::all_of(std::begin(seen), std::end(seen), [](bool b) { return b; }));
+ }
+ { // Check that every single element in [0, i) is visited exactly once and the rest is untouched
+ int a[1073];
+ int visits[1073];
+ runway_sample(std::size(a) + 1, [&](std::size_t i) {
+ std::iota(std::begin(a), std::end(a), 0);
+ std::fill(std::begin(visits), std::end(visits), 0);
+ auto res = std::for_each_n(policy, Iter(std::begin(a)), i, [&](int& v) {
+ ++visits[&v - std::begin(a)];
+ v = -v;
});
- assert(std::all_of(std::begin(called), std::end(called), [](bool b) { return b; }));
- assert(res == Iter(std::data(a) + std::size(a)));
- }
+ assert(res == Iter(std::begin(a) + i));
+ for (std::size_t j = 0; j < std::size(a); ++j) {
+ assert(visits[j] == (j < i ? 1 : 0));
+ assert(a[j] == (j < i ? -static_cast<int>(j) : static_cast<int>(j)));
+ }
+ });
+ }
+ }
+};
+
+template <class Iter>
+struct TestNonCopyableNonMovable {
+ template <class ExecutionPolicy>
+ void operator()(ExecutionPolicy&& policy) {
+ { // Few elements
+ NonCopyableNonMovable a[] = {NonCopyableNonMovable(1), NonCopyableNonMovable(5), NonCopyableNonMovable(7)};
+ auto res =
+ std::for_each_n(policy, Iter(std::begin(a)), std::size(a), [](NonCopyableNonMovable& x) { x.visit(); });
+ assert(res == Iter(std::end(a)));
+ assert(a[0].visits() == 1 && a[1].visits() == 1 && a[2].visits() == 1);
+ assert(a[0].value() == 1 && a[1].value() == 5 && a[2].value() == 7);
+ }
+ { // Many elements, check that every single element in [0, i) is visited exactly once
+ NonCopyableNonMovable a[1073];
+ runway_sample(std::size(a) + 1, [&](std::size_t i) {
+ auto res = std::for_each_n(policy, Iter(std::begin(a)), i, [](NonCopyableNonMovable& x) { x.visit(); });
+ assert(res == Iter(std::begin(a) + i));
+ for (std::size_t j = 0; j < std::size(a); ++j) {
+ assert(a[j].visits() == (j < i ? 1 : 0));
+ a[j].reset();
+ }
+ });
}
}
};
int main(int, char**) {
types::for_each(types::forward_iterator_list<int*>{}, TestIteratorWithPolicies<Test>{});
-
+ types::for_each(
+ types::forward_iterator_list<NonCopyableNonMovable*>{}, TestIteratorWithPolicies<TestNonCopyableNonMovable>{});
return 0;
}
``````````
</details>
https://github.com/llvm/llvm-project/pull/231918
More information about the libcxx-commits
mailing list