[libcxx-commits] [libcxx] [libc++][pstl] Default implementation of parallel std::adjacent_difference (PR #207585)

Michael G. Kazakov via libcxx-commits libcxx-commits at lists.llvm.org
Sun Jul 19 07:48:29 PDT 2026


https://github.com/mikekazakov updated https://github.com/llvm/llvm-project/pull/207585

>From 02ae8f53a83139d9280f86098a7517a3036c7986 Mon Sep 17 00:00:00 2001
From: Michael Kazakov <mike.kazakov at gmail.com>
Date: Sun, 5 Jul 2026 14:17:28 +0100
Subject: [PATCH 1/6] Implementation of adjacent_difference on top of a binary
 transform

---
 libcxx/include/__numeric/pstl.h               |  39 ++++
 libcxx/include/__pstl/backend_fwd.h           |   7 +
 libcxx/include/__pstl/backends/default.h      |  35 +++-
 .../pstl.iterator-requirements.verify.cpp     |  11 ++
 .../algorithms/pstl.nodiscard.verify.cpp      |   5 +
 .../pstl.exception_handling.pass.cpp          |  16 +-
 .../pstl.adjacent_difference.pass.cpp         | 171 +++++++++++++++++
 .../pstl.adjacent_difference_op.pass.cpp      | 181 ++++++++++++++++++
 8 files changed, 461 insertions(+), 4 deletions(-)
 create mode 100644 libcxx/test/std/numerics/numeric.ops/adjacent.difference/pstl.adjacent_difference.pass.cpp
 create mode 100644 libcxx/test/std/numerics/numeric.ops/adjacent.difference/pstl.adjacent_difference_op.pass.cpp

diff --git a/libcxx/include/__numeric/pstl.h b/libcxx/include/__numeric/pstl.h
index ecbc9f5348333..0b85cfb8c2327 100644
--- a/libcxx/include/__numeric/pstl.h
+++ b/libcxx/include/__numeric/pstl.h
@@ -165,6 +165,45 @@ template <class _ExecutionPolicy,
       std::move(__transform));
 }
 
+template <class _ExecutionPolicy,
+          class _ForwardIterator1,
+          class _ForwardIterator2,
+          class _RawPolicy                                    = __remove_cvref_t<_ExecutionPolicy>,
+          enable_if_t<is_execution_policy_v<_RawPolicy>, int> = 0>
+[[nodiscard]] _LIBCPP_HIDE_FROM_ABI _ForwardIterator2 adjacent_difference(
+    _ExecutionPolicy&& __policy, _ForwardIterator1 __first1, _ForwardIterator1 __last1, _ForwardIterator2 __first2) {
+  _LIBCPP_REQUIRE_CPP17_FORWARD_ITERATOR(_ForwardIterator1, "adjacent_difference requires ForwardIterators");
+  _LIBCPP_REQUIRE_CPP17_FORWARD_ITERATOR(_ForwardIterator2, "adjacent_difference requires ForwardIterators");
+  using _Implementation =
+      __pstl::__dispatch<__pstl::__adjacent_difference, __pstl::__current_configuration, _RawPolicy>;
+  return __pstl::__handle_exception<_Implementation>(
+      std::forward<_ExecutionPolicy>(__policy), std::move(__first1), std::move(__last1), std::move(__first2), minus{});
+}
+
+template <class _ExecutionPolicy,
+          class _ForwardIterator1,
+          class _ForwardIterator2,
+          class _BinaryOperation,
+          class _RawPolicy                                    = __remove_cvref_t<_ExecutionPolicy>,
+          enable_if_t<is_execution_policy_v<_RawPolicy>, int> = 0>
+[[nodiscard]] _LIBCPP_HIDE_FROM_ABI _ForwardIterator2 adjacent_difference(
+    _ExecutionPolicy&& __policy,
+    _ForwardIterator1 __first1,
+    _ForwardIterator1 __last1,
+    _ForwardIterator2 __first2,
+    _BinaryOperation __op) {
+  _LIBCPP_REQUIRE_CPP17_FORWARD_ITERATOR(_ForwardIterator1, "adjacent_difference requires ForwardIterators");
+  _LIBCPP_REQUIRE_CPP17_FORWARD_ITERATOR(_ForwardIterator2, "adjacent_difference requires ForwardIterators");
+  using _Implementation =
+      __pstl::__dispatch<__pstl::__adjacent_difference, __pstl::__current_configuration, _RawPolicy>;
+  return __pstl::__handle_exception<_Implementation>(
+      std::forward<_ExecutionPolicy>(__policy),
+      std::move(__first1),
+      std::move(__last1),
+      std::move(__first2),
+      std::move(__op));
+}
+
 _LIBCPP_END_NAMESPACE_STD
 
 #endif // _LIBCPP_HAS_EXPERIMENTAL_PSTL && _LIBCPP_STD_VER >= 17
diff --git a/libcxx/include/__pstl/backend_fwd.h b/libcxx/include/__pstl/backend_fwd.h
index a84a768cad570..2ba5b4434fc33 100644
--- a/libcxx/include/__pstl/backend_fwd.h
+++ b/libcxx/include/__pstl/backend_fwd.h
@@ -317,6 +317,13 @@ struct __is_sorted;
 // optional<bool>
 // operator()(_Policy&& __policy, _ForwardIterator __first, _ForwardIterator __last, _Comp&& __comp) const noexcept;
 
+template <class _Backend, class _ExecutionPolicy>
+struct __adjacent_difference;
+// template <class _Policy, class _ForwardIterator1, class _ForwardIterator2, class _BinaryOperation>
+// optional<_ForwardIterator2>
+// operator()(_Policy&& __policy, _ForwardIterator1 __first1, _ForwardIterator1 __last1,
+//                                _ForwardIterator2 __first2, _BinaryOperation &&__op) const noexcept;
+
 } // namespace __pstl
 _LIBCPP_END_NAMESPACE_STD
 
diff --git a/libcxx/include/__pstl/backends/default.h b/libcxx/include/__pstl/backends/default.h
index 5c78d4efa412b..f34c284dd2a84 100644
--- a/libcxx/include/__pstl/backends/default.h
+++ b/libcxx/include/__pstl/backends/default.h
@@ -92,11 +92,12 @@ namespace __pstl {
 //
 // transform and transform_binary family
 // -------------------------------------
-// - replace_copy_if
-// - replace_copy
-// - move
+// - adjacent_difference
 // - copy
 // - copy_n
+// - move
+// - replace_copy
+// - replace_copy_if
 // - reverse_copy
 // - rotate_copy
 //
@@ -575,6 +576,34 @@ struct __reverse_copy<__default_backend_tag, _ExecutionPolicy> {
   }
 };
 
+template <class _ExecutionPolicy>
+struct __adjacent_difference<__default_backend_tag, _ExecutionPolicy> {
+  template <class _Policy, class _ForwardIterator1, class _ForwardIterator2, class _BinaryOperation>
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI optional<_ForwardIterator2>
+  operator()(_Policy&& __policy,
+             _ForwardIterator1 __first1,
+             _ForwardIterator1 __last1,
+             _ForwardIterator2 __first2,
+             _BinaryOperation&& __op) const noexcept {
+    using _TransformBinary = __dispatch<__transform_binary, __current_configuration, _ExecutionPolicy>;
+    if (__first1 == __last1)
+      return __first2; // edge case: empty input range, just return the output iterator
+    *__first2 = *__first1;
+    ++__first2;
+    _ForwardIterator1 __first_left = std::next(__first1);
+    if (__first_left == __last1)
+      return __first2; // edge case: not enough elements to perform adjacent difference, just return the output iterator
+    // Process as a binary transform of two iterator ranges: [__first1 + 1, __last1) and [__first1, __last1 - 1)
+    return _TransformBinary()(
+        __policy,
+        std::move(__first_left),
+        std::move(__last1),
+        std::move(__first1),
+        std::move(__first2),
+        std::forward<_BinaryOperation>(__op));
+  }
+};
+
 } // namespace __pstl
 _LIBCPP_END_NAMESPACE_STD
 
diff --git a/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp b/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
index fbe3460b67f17..402d8fbf9131a 100644
--- a/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
+++ b/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
@@ -211,4 +211,15 @@ void f(non_forward_iterator non_fwd,
     (void)std::transform_reduce(
         pol, non_fwd, non_fwd, val, func, func); // expected-error@*:* {{static assertion failed: transform_reduce}}
   }
+
+  {
+    (void)std::adjacent_difference(
+        pol, it, it, non_fwd); // expected-error@*:* {{static assertion failed: adjacent_difference}}
+    (void)std::adjacent_difference(
+        pol, it, it, non_fwd, func); // expected-error@*:* {{static assertion failed: adjacent_difference}}
+    (void)std::adjacent_difference(
+        pol, non_fwd, non_fwd, it); // expected-error@*:* {{static assertion failed: adjacent_difference}}
+    (void)std::adjacent_difference(
+        pol, non_fwd, non_fwd, it, func); // expected-error@*:* {{static assertion failed: adjacent_difference}}
+  }
 }
diff --git a/libcxx/test/libcxx/algorithms/pstl.nodiscard.verify.cpp b/libcxx/test/libcxx/algorithms/pstl.nodiscard.verify.cpp
index 049c8dc88c2da..f4fffd456a331 100644
--- a/libcxx/test/libcxx/algorithms/pstl.nodiscard.verify.cpp
+++ b/libcxx/test/libcxx/algorithms/pstl.nodiscard.verify.cpp
@@ -15,6 +15,7 @@
 #include <algorithm>
 #include <execution>
 #include <iterator>
+#include <numeric>
 
 void test() {
   int a[]    = {1};
@@ -58,4 +59,8 @@ void test() {
   std::is_sorted(std::execution::par, std::begin(a), std::end(a), pred2);
   // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
   std::reverse_copy(std::execution::par, std::begin(a), std::end(a), std::begin(b));
+  // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::adjacent_difference(std::execution::par, std::begin(a), std::end(a), std::begin(b));
+  // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::adjacent_difference(std::execution::par, std::begin(a), std::end(a), std::begin(b), pred2);
 }
diff --git a/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp b/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp
index 8af2a0a15fd5c..e00046fdb1dd6 100644
--- a/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp
+++ b/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp
@@ -85,7 +85,7 @@ int main(int, char**) {
       auto last1       = util::throw_on_move_iterator(std::end(a), tokens[1].active() ? 1 : -1);
       auto first2      = util::throw_on_move_iterator(std::begin(b), tokens[2].active() ? 1 : -1);
       auto last2       = util::throw_on_move_iterator(std::end(b), tokens[3].active() ? 1 : -1);
-      auto dest        = util::throw_on_move_iterator(std::end(storage), tokens[4].active() ? 1 : -1);
+      auto dest        = util::throw_on_move_iterator(std::begin(storage), tokens[4].active() ? 1 : -1);
       auto maybe_throw = [](ThrowToken const& token, auto f) {
         return [&token, f](auto... args) {
           if (token.active())
@@ -365,6 +365,20 @@ int main(int, char**) {
           (void)std::reduce(policy, std::move(first1), std::move(last1), init, reduction);
         });
       }
+
+      {
+        auto op = maybe_throw(tokens[5], [](int x, int y) -> int { return x + y; });
+
+        // adjacent_difference(first, last, dest)
+        assert_non_throwing([=, &policy] {
+          (void)std::adjacent_difference(policy, std::move(first1), std::move(last1), std::move(dest));
+        });
+
+        // adjacent_difference(first, last, dest, op)
+        assert_non_throwing([=, &policy] {
+          (void)std::adjacent_difference(policy, std::move(first1), std::move(last1), std::move(dest), op);
+        });
+      }
     }
   });
 }
diff --git a/libcxx/test/std/numerics/numeric.ops/adjacent.difference/pstl.adjacent_difference.pass.cpp b/libcxx/test/std/numerics/numeric.ops/adjacent.difference/pstl.adjacent_difference.pass.cpp
new file mode 100644
index 0000000000000..bb98004c1c62a
--- /dev/null
+++ b/libcxx/test/std/numerics/numeric.ops/adjacent.difference/pstl.adjacent_difference.pass.cpp
@@ -0,0 +1,171 @@
+//===----------------------------------------------------------------------===//
+//
+// 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>
+//   ForwardIterator2 adjacent_difference(ExecutionPolicy&& exec,
+//                                        ForwardIterator1 first1,
+//                                        ForwardIterator1 last1,
+//                                        ForwardIterator2 first2);
+
+#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"
+
+EXECUTION_POLICY_SFINAE_TEST(adjacent_difference);
+
+static_assert(sfinae_test_adjacent_difference<int, int*, int*, int*>);
+static_assert(!sfinae_test_adjacent_difference<std::execution::parallel_policy, int*, int*, int*>);
+
+template <class Callable>
+void runway_sample(size_t size, Callable callable) {
+  constexpr size_t affix = 16;
+  // 0, 1, 2, ..., 15, 16, 50, 157, 493, 1548, ...
+  for (size_t i = 0; i < size; i = i < affix ? i + 1 : size_t(3.1415 * i)) {
+    callable(i);
+  }
+  if (size <= affix)
+    return;
+  // size - 16, size - 15, ..., size - 1
+  for (size_t i = size - affix; i < size; ++i) {
+    callable(i);
+  }
+}
+
+class X {
+  int i_;
+
+  X& operator=(const X&);
+
+public:
+  explicit X(int i) : i_(i) {}
+  X(const X& x) : i_(x.i_) {}
+  X& operator=(X&& x) {
+    i_   = x.i_;
+    x.i_ = -1;
+    return *this;
+  }
+
+  friend X operator-(const X& x, const X& y) { return X(x.i_ - y.i_); }
+
+  friend class Y;
+};
+
+class Y {
+  int i_;
+
+  Y& operator=(const Y&);
+
+public:
+  explicit Y(int i) : i_(i) {}
+  Y(const Y& y) : i_(y.i_) {}
+  void operator=(const X& x) { i_ = x.i_; }
+  bool operator==(const Y& y) const { return i_ == y.i_; }
+};
+
+template <class Iter1, class Iter2>
+struct Test {
+  template <class ExecutionPolicy>
+  void operator()(ExecutionPolicy&& policy) {
+    {
+      int ia[] = {10};
+      int ir[] = {42};
+      int ib[] = {42};
+      Iter2 r  = std::adjacent_difference(policy, Iter1(std::begin(ia)), Iter1(std::begin(ia)), Iter2(std::begin(ib)));
+      assert(r == Iter2(std::begin(ib)));
+      for (size_t i = 0; i < std::size(ia); ++i)
+        assert(ib[i] == ir[i]);
+    }
+    {
+      int ia[] = {10};
+      int ir[] = {10};
+      int ib[] = {42};
+      Iter2 r  = std::adjacent_difference(policy, Iter1(std::begin(ia)), Iter1(std::end(ia)), Iter2(std::begin(ib)));
+      assert(r == Iter2(std::end(ib)));
+      for (size_t i = 0; i < std::size(ia); ++i)
+        assert(ib[i] == ir[i]);
+    }
+    {
+      int ia[]              = {10, 30};
+      int ir[]              = {10, 20};
+      int ib[std::size(ia)] = {0};
+      Iter2 r = std::adjacent_difference(policy, Iter1(std::begin(ia)), Iter1(std::end(ia)), Iter2(std::begin(ib)));
+      assert(r == Iter2(std::end(ib)));
+      for (size_t i = 0; i < std::size(ia); ++i)
+        assert(ib[i] == ir[i]);
+    }
+    {
+      int ia[]              = {15, 10, 6, 3, 1};
+      int ir[]              = {15, -5, -4, -3, -2};
+      int ib[std::size(ia)] = {0};
+      Iter2 r = std::adjacent_difference(policy, Iter1(std::begin(ia)), Iter1(std::end(ia)), Iter2(std::begin(ib)));
+      assert(r == Iter2(std::end(ib)));
+      for (size_t i = 0; i < std::size(ia); ++i)
+        assert(ib[i] == ir[i]);
+    }
+    {
+      int ia[1073];
+      int ib[1073];
+      std::iota(std::begin(ia), std::end(ia), 1);
+      runway_sample(std::size(ia), [&](size_t i) {
+        Iter2 r =
+            std::adjacent_difference(policy, Iter1(std::begin(ia)), Iter1(std::begin(ia) + i), Iter2(std::begin(ib)));
+        assert(r == Iter2(std::begin(ib) + i));
+        assert(std::all_of(std::begin(ib), std::begin(ib) + i, [](int x) { return x == 1; }));
+      });
+    }
+  }
+};
+
+template <class Iter1, class Iter2>
+struct TestCustomTypes {
+  template <class ExecutionPolicy>
+  void operator()(ExecutionPolicy&& policy) {
+    {
+      X ia[3] = {X(1), X(5), X(7)};
+      Y ir[3] = {Y(1), Y(4), Y(2)};
+      Y ib[3] = {Y(0), Y(0), Y(0)};
+      Iter2 r = std::adjacent_difference(policy, Iter1(std::begin(ia)), Iter1(std::end(ia)), Iter2(std::begin(ib)));
+      assert(r == Iter2(std::end(ib)));
+      for (size_t i = 0; i < std::size(ia); ++i)
+        assert(ib[i] == ir[i]);
+    }
+  }
+};
+
+int main(int, char**) {
+  types::for_each(
+      types::concatenate_t<types::forward_iterator_list<int*>, 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<Test, Iter>::template apply>{});
+      }});
+  types::for_each(
+      types::concatenate_t<types::forward_iterator_list<X*>, types::forward_iterator_list<const X*>>{},
+      types::apply_type_identity{[](auto v) {
+        using Iter = typename decltype(v)::type;
+        types::for_each(
+            types::forward_iterator_list<Y*>{},
+            TestIteratorWithPolicies<types::partial_instantiation<TestCustomTypes, Iter>::template apply>{});
+      }});
+  return 0;
+}
diff --git a/libcxx/test/std/numerics/numeric.ops/adjacent.difference/pstl.adjacent_difference_op.pass.cpp b/libcxx/test/std/numerics/numeric.ops/adjacent.difference/pstl.adjacent_difference_op.pass.cpp
new file mode 100644
index 0000000000000..26d65f9a77c10
--- /dev/null
+++ b/libcxx/test/std/numerics/numeric.ops/adjacent.difference/pstl.adjacent_difference_op.pass.cpp
@@ -0,0 +1,181 @@
+//===----------------------------------------------------------------------===//
+//
+// 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>
+//   ForwardIterator2 adjacent_difference(ExecutionPolicy&& exec,
+//                                        ForwardIterator1 first1,
+//                                        ForwardIterator1 last1,
+//                                        ForwardIterator2 first2,
+//                                        BinaryOperation op);
+
+#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"
+
+EXECUTION_POLICY_SFINAE_TEST(adjacent_difference);
+
+static_assert(sfinae_test_adjacent_difference<int, int*, int*, int*, int (*)(int, int)>);
+static_assert(!sfinae_test_adjacent_difference<std::execution::parallel_policy, int*, int*, int*, int (*)(int, int)>);
+
+template <class Callable>
+void runway_sample(size_t size, Callable callable) {
+  constexpr size_t affix = 16;
+  // 0, 1, 2, ..., 15, 16, 50, 157, 493, 1548, ...
+  for (size_t i = 0; i < size; i = i < affix ? i + 1 : size_t(3.1415 * i)) {
+    callable(i);
+  }
+  if (size <= affix)
+    return;
+  // size - 16, size - 15, ..., size - 1
+  for (size_t i = size - affix; i < size; ++i) {
+    callable(i);
+  }
+}
+
+class X {
+  int i_;
+  X& operator=(const X&);
+
+public:
+  explicit X(int i) : i_(i) {}
+  X(const X& x) : i_(x.i_) {}
+  X& operator=(X&& x) {
+    i_   = x.i_;
+    x.i_ = -1;
+    return *this;
+  }
+  friend class Y;
+  friend struct Op;
+};
+
+class Y {
+  int i_;
+  Y& operator=(const Y&);
+
+public:
+  explicit Y(int i) : i_(i) {}
+  Y(const Y& y) : i_(y.i_) {}
+  void operator=(const X& x) { i_ = x.i_; }
+  bool operator==(const Y& y) const { return i_ == y.i_; }
+};
+
+// ensures that the predicate is not equivalent to std::minus
+struct Op {
+  int operator()(int l, int r) const { return l * 2 - r; }
+  X operator()(const X& l, const X& r) const { return X(l.i_ * 2 - r.i_); }
+};
+
+template <class Iter1, class Iter2>
+struct Test {
+  template <class ExecutionPolicy>
+  void operator()(ExecutionPolicy&& policy) {
+    {
+      int ia[] = {10};
+      int ir[] = {42};
+      int ib[] = {42};
+      Iter2 r =
+          std::adjacent_difference(policy, Iter1(std::begin(ia)), Iter1(std::begin(ia)), Iter2(std::begin(ib)), Op{});
+      assert(r == Iter2(std::begin(ib)));
+      for (size_t i = 0; i < std::size(ia); ++i)
+        assert(ib[i] == ir[i]);
+    }
+    {
+      int ia[] = {10};
+      int ir[] = {10};
+      int ib[] = {42};
+      Iter2 r =
+          std::adjacent_difference(policy, Iter1(std::begin(ia)), Iter1(std::end(ia)), Iter2(std::begin(ib)), Op{});
+      assert(r == Iter2(std::end(ib)));
+      for (size_t i = 0; i < std::size(ia); ++i)
+        assert(ib[i] == ir[i]);
+    }
+    {
+      int ia[]              = {10, 30};
+      int ir[]              = {10, 50};
+      int ib[std::size(ia)] = {0};
+      Iter2 r =
+          std::adjacent_difference(policy, Iter1(std::begin(ia)), Iter1(std::end(ia)), Iter2(std::begin(ib)), Op{});
+      assert(r == Iter2(std::end(ib)));
+      for (size_t i = 0; i < std::size(ia); ++i)
+        assert(ib[i] == ir[i]);
+    }
+    {
+      int ia[]              = {15, 10, 6, 3, 1};
+      int ir[]              = {15, 5, 2, 0, -1};
+      int ib[std::size(ia)] = {0};
+      Iter2 r =
+          std::adjacent_difference(policy, Iter1(std::begin(ia)), Iter1(std::end(ia)), Iter2(std::begin(ib)), Op{});
+      assert(r == Iter2(std::end(ib)));
+      for (size_t i = 0; i < std::size(ia); ++i)
+        assert(ib[i] == ir[i]);
+    }
+    {
+      int ia[1073];
+      int ib[1073];
+      std::iota(std::begin(ia), std::end(ia), 1);
+      runway_sample(std::size(ia), [&](size_t i) {
+        Iter2 r = std::adjacent_difference(
+            policy, Iter1(std::begin(ia)), Iter1(std::begin(ia) + i), Iter2(std::begin(ib)), Op{});
+        assert(r == Iter2(std::begin(ib) + i));
+        for (size_t j = 0; j < i; ++j)
+          assert(ib[j] == (int)(j == 0 ? 1 : j + 2));
+      });
+    }
+  }
+};
+
+template <class Iter1, class Iter2>
+struct TestCustomTypes {
+  template <class ExecutionPolicy>
+  void operator()(ExecutionPolicy&& policy) {
+    {
+      X ia[3] = {X(1), X(5), X(8)};
+      Y ir[3] = {Y(1), Y(9), Y(11)};
+      Y ib[3] = {Y(0), Y(0), Y(0)};
+      Iter2 r =
+          std::adjacent_difference(policy, Iter1(std::begin(ia)), Iter1(std::end(ia)), Iter2(std::begin(ib)), Op{});
+      assert(r == Iter2(std::end(ib)));
+      for (size_t i = 0; i < std::size(ia); ++i)
+        assert(ib[i] == ir[i]);
+    }
+  }
+};
+
+int main(int, char**) {
+  types::for_each(
+      types::concatenate_t<types::forward_iterator_list<int*>, 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<Test, Iter>::template apply>{});
+      }});
+  types::for_each(
+      types::concatenate_t<types::forward_iterator_list<X*>, types::forward_iterator_list<const X*>>{},
+      types::apply_type_identity{[](auto v) {
+        using Iter = typename decltype(v)::type;
+        types::for_each(
+            types::forward_iterator_list<Y*>{},
+            TestIteratorWithPolicies<types::partial_instantiation<TestCustomTypes, Iter>::template apply>{});
+      }});
+  return 0;
+}

>From f62e25b47970412a33d6c44c4b4820ad3669b680 Mon Sep 17 00:00:00 2001
From: Michael Kazakov <mike.kazakov at gmail.com>
Date: Sat, 11 Jul 2026 10:34:09 +0100
Subject: [PATCH 2/6] Renamed the variable names in __adjacent_difference

---
 libcxx/include/__pstl/backends/default.h | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/libcxx/include/__pstl/backends/default.h b/libcxx/include/__pstl/backends/default.h
index f34c284dd2a84..28bc75be147aa 100644
--- a/libcxx/include/__pstl/backends/default.h
+++ b/libcxx/include/__pstl/backends/default.h
@@ -583,23 +583,23 @@ struct __adjacent_difference<__default_backend_tag, _ExecutionPolicy> {
   operator()(_Policy&& __policy,
              _ForwardIterator1 __first1,
              _ForwardIterator1 __last1,
-             _ForwardIterator2 __first2,
+             _ForwardIterator2 __result,
              _BinaryOperation&& __op) const noexcept {
     using _TransformBinary = __dispatch<__transform_binary, __current_configuration, _ExecutionPolicy>;
     if (__first1 == __last1)
-      return __first2; // edge case: empty input range, just return the output iterator
-    *__first2 = *__first1;
-    ++__first2;
-    _ForwardIterator1 __first_left = std::next(__first1);
-    if (__first_left == __last1)
-      return __first2; // edge case: not enough elements to perform adjacent difference, just return the output iterator
+      return __result; // edge case: empty input range, just return the output iterator
+    *__result = *__first1;
+    ++__result;
+    _ForwardIterator1 __first2 = std::next(__first1);
+    if (__first2 == __last1)
+      return __result; // edge case: not enough elements to perform adjacent difference, just return the output iterator
     // Process as a binary transform of two iterator ranges: [__first1 + 1, __last1) and [__first1, __last1 - 1)
     return _TransformBinary()(
         __policy,
-        std::move(__first_left),
+        std::move(__first2),
         std::move(__last1),
         std::move(__first1),
-        std::move(__first2),
+        std::move(__result),
         std::forward<_BinaryOperation>(__op));
   }
 };

>From 012594a84a7b0bbc55c1e9457adf28531d01c97d Mon Sep 17 00:00:00 2001
From: Michael Kazakov <mike.kazakov at gmail.com>
Date: Sat, 11 Jul 2026 20:01:44 +0100
Subject: [PATCH 3/6] Moved runway_sample() to test/support/runway_sample.h

---
 .../pstl.adjacent_difference.pass.cpp         |  16 +--
 .../pstl.adjacent_difference_op.pass.cpp      |  16 +--
 libcxx/test/support/runway_sample.h           |  58 ++++++++++
 .../test.support/runway_sample.pass.cpp       | 101 ++++++++++++++++++
 4 files changed, 161 insertions(+), 30 deletions(-)
 create mode 100644 libcxx/test/support/runway_sample.h
 create mode 100644 libcxx/test/support/test.support/runway_sample.pass.cpp

diff --git a/libcxx/test/std/numerics/numeric.ops/adjacent.difference/pstl.adjacent_difference.pass.cpp b/libcxx/test/std/numerics/numeric.ops/adjacent.difference/pstl.adjacent_difference.pass.cpp
index bb98004c1c62a..4dfe3ad58adf8 100644
--- a/libcxx/test/std/numerics/numeric.ops/adjacent.difference/pstl.adjacent_difference.pass.cpp
+++ b/libcxx/test/std/numerics/numeric.ops/adjacent.difference/pstl.adjacent_difference.pass.cpp
@@ -29,27 +29,13 @@
 #include "test_iterators.h"
 #include "test_macros.h"
 #include "type_algorithms.h"
+#include "runway_sample.h"
 
 EXECUTION_POLICY_SFINAE_TEST(adjacent_difference);
 
 static_assert(sfinae_test_adjacent_difference<int, int*, int*, int*>);
 static_assert(!sfinae_test_adjacent_difference<std::execution::parallel_policy, int*, int*, int*>);
 
-template <class Callable>
-void runway_sample(size_t size, Callable callable) {
-  constexpr size_t affix = 16;
-  // 0, 1, 2, ..., 15, 16, 50, 157, 493, 1548, ...
-  for (size_t i = 0; i < size; i = i < affix ? i + 1 : size_t(3.1415 * i)) {
-    callable(i);
-  }
-  if (size <= affix)
-    return;
-  // size - 16, size - 15, ..., size - 1
-  for (size_t i = size - affix; i < size; ++i) {
-    callable(i);
-  }
-}
-
 class X {
   int i_;
 
diff --git a/libcxx/test/std/numerics/numeric.ops/adjacent.difference/pstl.adjacent_difference_op.pass.cpp b/libcxx/test/std/numerics/numeric.ops/adjacent.difference/pstl.adjacent_difference_op.pass.cpp
index 26d65f9a77c10..f6b9b480f4ba8 100644
--- a/libcxx/test/std/numerics/numeric.ops/adjacent.difference/pstl.adjacent_difference_op.pass.cpp
+++ b/libcxx/test/std/numerics/numeric.ops/adjacent.difference/pstl.adjacent_difference_op.pass.cpp
@@ -31,27 +31,13 @@
 #include "test_iterators.h"
 #include "test_macros.h"
 #include "type_algorithms.h"
+#include "runway_sample.h"
 
 EXECUTION_POLICY_SFINAE_TEST(adjacent_difference);
 
 static_assert(sfinae_test_adjacent_difference<int, int*, int*, int*, int (*)(int, int)>);
 static_assert(!sfinae_test_adjacent_difference<std::execution::parallel_policy, int*, int*, int*, int (*)(int, int)>);
 
-template <class Callable>
-void runway_sample(size_t size, Callable callable) {
-  constexpr size_t affix = 16;
-  // 0, 1, 2, ..., 15, 16, 50, 157, 493, 1548, ...
-  for (size_t i = 0; i < size; i = i < affix ? i + 1 : size_t(3.1415 * i)) {
-    callable(i);
-  }
-  if (size <= affix)
-    return;
-  // size - 16, size - 15, ..., size - 1
-  for (size_t i = size - affix; i < size; ++i) {
-    callable(i);
-  }
-}
-
 class X {
   int i_;
   X& operator=(const X&);
diff --git a/libcxx/test/support/runway_sample.h b/libcxx/test/support/runway_sample.h
new file mode 100644
index 0000000000000..16ecd58cef747
--- /dev/null
+++ b/libcxx/test/support/runway_sample.h
@@ -0,0 +1,58 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 TEST_SUPPORT_RUNWAY_SAMPLE_H
+#define TEST_SUPPORT_RUNWAY_SAMPLE_H
+
+#include <cstddef>
+#include <algorithm>
+#include "test_macros.h"
+
+// runway_sample(size, callable) calls callable(i) for a set of sample indices i in the range [0, size).
+// they are chosen in 3 phases:
+//   - prefix, sampled densely with increments of 1
+//   - middle, sampled sparsely with uncanny numbers
+//   - suffix, sampled densely with increments of 1
+//
+// Examples of sample indices for various sizes:
+// size = 0:
+// <no samples>
+// size = 1:
+// 0
+// size = 10:
+// 0 1 2 3 4 5 6 7 8 9
+// size = 100:
+// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 50 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
+// size = 1000:
+// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 50 157 493 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999
+template <class Callable>
+TEST_CONSTEXPR_CXX14 void runway_sample(std::size_t size, Callable callable) {
+  constexpr std::size_t affix = 16;
+  std::size_t i               = 0;
+
+  // 0, 1, 2, ..., 15
+  for (; i < std::min(size, affix); ++i) {
+    callable(i);
+  }
+
+  if (size <= affix) {
+    return;
+  }
+
+  // 16, 50, 157, 493, 1549, 4868, ...
+  for (std::size_t j = i; j + affix < size; i = j, j = j * 3 + j / 7) {
+    callable(j);
+  }
+
+  // size - 16, size - 15, ..., size - 1
+  i = std::max(i, size - affix);
+  for (; i < size; ++i) {
+    callable(i);
+  }
+}
+
+#endif // TEST_SUPPORT_RUNWAY_SAMPLE_H
diff --git a/libcxx/test/support/test.support/runway_sample.pass.cpp b/libcxx/test/support/test.support/runway_sample.pass.cpp
new file mode 100644
index 0000000000000..9fc288f881082
--- /dev/null
+++ b/libcxx/test/support/test.support/runway_sample.pass.cpp
@@ -0,0 +1,101 @@
+//===----------------------------------------------------------------------===//
+//
+// 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++11
+
+// "support/runway_sample.h"
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <type_traits>
+
+#include "test_macros.h"
+#include "runway_sample.h"
+
+TEST_CONSTEXPR_CXX14 bool test() {
+  {
+    bool fail = false;
+    runway_sample(0, [&](size_t /*i*/) { fail = true; });
+    if (fail) {
+      return false;
+    }
+  }
+  {
+    size_t expected[] = {0};
+    size_t n          = 0;
+    bool fail         = false;
+    runway_sample(0, [&](size_t i) {
+      if (expected[n++] != i)
+        fail = true;
+    });
+    if (fail) {
+      return false;
+    }
+  }
+  {
+    size_t expected[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+    size_t n          = 0;
+    bool fail         = false;
+    runway_sample(10, [&](size_t i) {
+      if (expected[n++] != i)
+        fail = true;
+    });
+    if (fail) {
+      return false;
+    }
+  }
+  {
+    size_t expected[] = {
+        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29};
+    size_t n  = 0;
+    bool fail = false;
+    runway_sample(30, [&](size_t i) {
+      if (expected[n++] != i)
+        fail = true;
+    });
+    if (fail) {
+      return false;
+    }
+  }
+  {
+    size_t expected[] = {0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15, 16,
+                         50, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99};
+    size_t n          = 0;
+    bool fail         = false;
+    runway_sample(100, [&](size_t i) {
+      if (expected[n++] != i)
+        fail = true;
+    });
+    if (fail) {
+      return false;
+    }
+  }
+  {
+    size_t expected[] = {0,   1,   2,   3,   4,   5,   6,   7,   8,   9,   10,  11,  12,  13,  14,  15,  16,  50,
+                         157, 493, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999};
+    size_t n          = 0;
+    bool fail         = false;
+    runway_sample(1000, [&](size_t i) {
+      if (expected[n++] != i)
+        fail = true;
+    });
+    if (fail) {
+      return false;
+    }
+  }
+  return true;
+}
+
+int main() {
+  assert(test());
+#if TEST_STD_VER >= 14
+  static_assert(test());
+#endif
+  return 0;
+}

>From 1552e8b1db839c7356ba1679fda4bdfc669dd685 Mon Sep 17 00:00:00 2001
From: Michael Kazakov <mike.kazakov at gmail.com>
Date: Sat, 11 Jul 2026 20:05:51 +0100
Subject: [PATCH 4/6] Added a note about X and Y in the tests

---
 .../adjacent.difference/pstl.adjacent_difference.pass.cpp     | 4 ++++
 .../adjacent.difference/pstl.adjacent_difference_op.pass.cpp  | 4 ++++
 2 files changed, 8 insertions(+)

diff --git a/libcxx/test/std/numerics/numeric.ops/adjacent.difference/pstl.adjacent_difference.pass.cpp b/libcxx/test/std/numerics/numeric.ops/adjacent.difference/pstl.adjacent_difference.pass.cpp
index 4dfe3ad58adf8..d06f5b64e2f9f 100644
--- a/libcxx/test/std/numerics/numeric.ops/adjacent.difference/pstl.adjacent_difference.pass.cpp
+++ b/libcxx/test/std/numerics/numeric.ops/adjacent.difference/pstl.adjacent_difference.pass.cpp
@@ -36,6 +36,10 @@ EXECUTION_POLICY_SFINAE_TEST(adjacent_difference);
 static_assert(sfinae_test_adjacent_difference<int, int*, int*, int*>);
 static_assert(!sfinae_test_adjacent_difference<std::execution::parallel_policy, int*, int*, int*>);
 
+// Types X and Y are provided to test adjacent_difference() against custom types.
+// X is a source type that supports subtraction.
+// Y is a destination type that can be assigned from X and compared for equality.
+
 class X {
   int i_;
 
diff --git a/libcxx/test/std/numerics/numeric.ops/adjacent.difference/pstl.adjacent_difference_op.pass.cpp b/libcxx/test/std/numerics/numeric.ops/adjacent.difference/pstl.adjacent_difference_op.pass.cpp
index f6b9b480f4ba8..d623cda058d04 100644
--- a/libcxx/test/std/numerics/numeric.ops/adjacent.difference/pstl.adjacent_difference_op.pass.cpp
+++ b/libcxx/test/std/numerics/numeric.ops/adjacent.difference/pstl.adjacent_difference_op.pass.cpp
@@ -38,6 +38,10 @@ EXECUTION_POLICY_SFINAE_TEST(adjacent_difference);
 static_assert(sfinae_test_adjacent_difference<int, int*, int*, int*, int (*)(int, int)>);
 static_assert(!sfinae_test_adjacent_difference<std::execution::parallel_policy, int*, int*, int*, int (*)(int, int)>);
 
+// Types X and Y are provided to test adjacent_difference() against custom types.
+// X is a source type that supports subtraction.
+// Y is a destination type that can be assigned from X and compared for equality.
+
 class X {
   int i_;
   X& operator=(const X&);

>From 40282b01dd8f83ff30534284abe9deddbf0e4136 Mon Sep 17 00:00:00 2001
From: Michael Kazakov <mike.kazakov at gmail.com>
Date: Sat, 11 Jul 2026 21:06:40 +0100
Subject: [PATCH 5/6] Run runway_sample test on C++17+

---
 libcxx/test/support/test.support/runway_sample.pass.cpp | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/libcxx/test/support/test.support/runway_sample.pass.cpp b/libcxx/test/support/test.support/runway_sample.pass.cpp
index 9fc288f881082..30e0ad20e0f85 100644
--- a/libcxx/test/support/test.support/runway_sample.pass.cpp
+++ b/libcxx/test/support/test.support/runway_sample.pass.cpp
@@ -6,7 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 
-// REQUIRES: std-at-least-c++11
+// REQUIRES: std-at-least-c++17
 
 // "support/runway_sample.h"
 
@@ -18,7 +18,7 @@
 #include "test_macros.h"
 #include "runway_sample.h"
 
-TEST_CONSTEXPR_CXX14 bool test() {
+constexpr bool test() {
   {
     bool fail = false;
     runway_sample(0, [&](size_t /*i*/) { fail = true; });
@@ -94,8 +94,6 @@ TEST_CONSTEXPR_CXX14 bool test() {
 
 int main() {
   assert(test());
-#if TEST_STD_VER >= 14
   static_assert(test());
-#endif
   return 0;
 }

>From 1759d17216f82de691906a23e19876be0262b231 Mon Sep 17 00:00:00 2001
From: Michael Kazakov <mike.kazakov at gmail.com>
Date: Wed, 15 Jul 2026 21:26:32 +0100
Subject: [PATCH 6/6] Use assert() in the runway_sample() unit test

---
 .../test.support/runway_sample.pass.cpp       | 82 +++++++------------
 1 file changed, 29 insertions(+), 53 deletions(-)

diff --git a/libcxx/test/support/test.support/runway_sample.pass.cpp b/libcxx/test/support/test.support/runway_sample.pass.cpp
index 30e0ad20e0f85..6c6eaafc3439b 100644
--- a/libcxx/test/support/test.support/runway_sample.pass.cpp
+++ b/libcxx/test/support/test.support/runway_sample.pass.cpp
@@ -20,80 +20,56 @@
 
 constexpr bool test() {
   {
-    bool fail = false;
-    runway_sample(0, [&](size_t /*i*/) { fail = true; });
-    if (fail) {
-      return false;
-    }
+    runway_sample(0, [&](std::size_t /*i*/) { assert(false); });
   }
   {
-    size_t expected[] = {0};
-    size_t n          = 0;
-    bool fail         = false;
-    runway_sample(0, [&](size_t i) {
-      if (expected[n++] != i)
-        fail = true;
+    std::size_t expected[] = {0};
+    std::size_t n          = 0;
+    runway_sample(1, [&](std::size_t i) {
+      assert(expected[n] == i);
+      n++;
     });
-    if (fail) {
-      return false;
-    }
   }
   {
-    size_t expected[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
-    size_t n          = 0;
-    bool fail         = false;
-    runway_sample(10, [&](size_t i) {
-      if (expected[n++] != i)
-        fail = true;
+    std::size_t expected[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+    std::size_t n          = 0;
+    runway_sample(10, [&](std::size_t i) {
+      assert(expected[n] == i);
+      n++;
     });
-    if (fail) {
-      return false;
-    }
   }
   {
-    size_t expected[] = {
+    std::size_t expected[] = {
         0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29};
-    size_t n  = 0;
-    bool fail = false;
-    runway_sample(30, [&](size_t i) {
-      if (expected[n++] != i)
-        fail = true;
+    std::size_t n = 0;
+    runway_sample(30, [&](std::size_t i) {
+      assert(expected[n] == i);
+      n++;
     });
-    if (fail) {
-      return false;
-    }
   }
   {
-    size_t expected[] = {0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15, 16,
-                         50, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99};
-    size_t n          = 0;
-    bool fail         = false;
-    runway_sample(100, [&](size_t i) {
-      if (expected[n++] != i)
-        fail = true;
+    std::size_t expected[] = {0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15, 16,
+                              50, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99};
+    std::size_t n          = 0;
+    runway_sample(100, [&](std::size_t i) {
+      assert(expected[n] == i);
+      n++;
     });
-    if (fail) {
-      return false;
-    }
   }
   {
-    size_t expected[] = {0,   1,   2,   3,   4,   5,   6,   7,   8,   9,   10,  11,  12,  13,  14,  15,  16,  50,
-                         157, 493, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999};
-    size_t n          = 0;
-    bool fail         = false;
-    runway_sample(1000, [&](size_t i) {
-      if (expected[n++] != i)
-        fail = true;
+    std::size_t expected[] = {0,   1,   2,   3,   4,   5,   6,   7,   8,   9,   10,  11,  12,  13,  14,  15,  16,  50,
+                              157, 493, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999};
+    std::size_t n          = 0;
+    runway_sample(1000, [&](std::size_t i) {
+      assert(expected[n] == i);
+      n++;
     });
-    if (fail) {
-      return false;
-    }
   }
   return true;
 }
 
 int main() {
-  assert(test());
+  test();
   static_assert(test());
   return 0;
 }



More information about the libcxx-commits mailing list