[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