[libcxx-commits] [libcxx] [libc++][pstl] Implement pstl std::min_element (PR #173970)

Marcell Leleszi via libcxx-commits libcxx-commits at lists.llvm.org
Tue Dec 30 06:12:44 PST 2025


https://github.com/mleleszi updated https://github.com/llvm/llvm-project/pull/173970

>From 6f271ead81a2f3e6dc58abc70ac8620fcffff7cb Mon Sep 17 00:00:00 2001
From: mleleszi <mleleszi at icloud.com>
Date: Fri, 26 Dec 2025 17:39:33 +0100
Subject: [PATCH 1/4] Implement pstl std::min_element

---
 libcxx/docs/Status/PSTLPaper.csv              |   2 +-
 libcxx/include/CMakeLists.txt                 |   1 +
 libcxx/include/__algorithm/pstl.h             |  23 ++
 libcxx/include/__functional/operations.h      |   6 +
 .../include/__functional/ranges_operations.h  |   3 +
 libcxx/include/__pstl/backend_fwd.h           |   6 +
 libcxx/include/__pstl/backends/libdispatch.h  |   5 +
 libcxx/include/__pstl/backends/serial.h       |   9 +
 libcxx/include/__pstl/backends/std_thread.h   |   5 +
 libcxx/include/__pstl/cpu_algos/min_element.h | 216 ++++++++++++++++++
 libcxx/include/__type_traits/desugars_to.h    |   8 +
 libcxx/include/module.modulemap.in            |   3 +
 .../algorithms/pstl.min_element.bench.cpp     | 119 ++++++++++
 .../pstl.iterator-requirements.verify.cpp     |   5 +
 .../alg.min.max/pstl.min_element.pass.cpp     | 108 +++++++++
 15 files changed, 518 insertions(+), 1 deletion(-)
 create mode 100644 libcxx/include/__pstl/cpu_algos/min_element.h
 create mode 100644 libcxx/test/benchmarks/algorithms/pstl.min_element.bench.cpp
 create mode 100644 libcxx/test/std/algorithms/alg.sorting/alg.min.max/pstl.min_element.pass.cpp

diff --git a/libcxx/docs/Status/PSTLPaper.csv b/libcxx/docs/Status/PSTLPaper.csv
index 3fb5adfe3ec46..569eb81541468 100644
--- a/libcxx/docs/Status/PSTLPaper.csv
+++ b/libcxx/docs/Status/PSTLPaper.csv
@@ -33,7 +33,7 @@ Section,Description,Assignee,Complete
 | `[alg.lex.comparison] <https://wg21.link/alg.lex.comparison>`_,std::lexicographical_compare,Nikolas Klauser,|Not Started|
 | `[alg.min.max] <https://wg21.link/alg.min.max>`_,std::max_element,Nikolas Klauser,|Not Started|
 | `[alg.merge] <https://wg21.link/alg.merge>`_,std::merge,Nikolas Klauser,|Complete|
-| `[alg.min.max] <https://wg21.link/alg.min.max>`_,std::min_element,Nikolas Klauser,|Not Started|
+| `[alg.min.max] <https://wg21.link/alg.min.max>`_,std::min_element,Marcell Leleszi,|Complete|
 | `[alg.min.max] <https://wg21.link/alg.min.max>`_,std::minmax_element,Nikolas Klauser,|Not Started|
 | `[mismatch] <https://wg21.link/mismatch>`_,std::mismatch,Nikolas Klauser,|Not Started|
 | `[alg.move] <https://wg21.link/alg.move>`_,std::move,Nikolas Klauser,|Complete|
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 9df40eab678a2..348b3a9a6fd0b 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -659,6 +659,7 @@ set(files
   __pstl/cpu_algos/find_if.h
   __pstl/cpu_algos/for_each.h
   __pstl/cpu_algos/merge.h
+  __pstl/cpu_algos/min_element.h
   __pstl/cpu_algos/stable_sort.h
   __pstl/cpu_algos/transform.h
   __pstl/cpu_algos/transform_reduce.h
diff --git a/libcxx/include/__algorithm/pstl.h b/libcxx/include/__algorithm/pstl.h
index eea07e2b96b64..8886c133db7e6 100644
--- a/libcxx/include/__algorithm/pstl.h
+++ b/libcxx/include/__algorithm/pstl.h
@@ -654,6 +654,29 @@ _LIBCPP_HIDE_FROM_ABI _ForwardOutIterator transform(
       std::move(__op));
 }
 
+template <class _ExecutionPolicy,
+          class _ForwardIterator,
+          class _Compare,
+          class _RawPolicy                                    = __remove_cvref_t<_ExecutionPolicy>,
+          enable_if_t<is_execution_policy_v<_RawPolicy>, int> = 0>
+_LIBCPP_HIDE_FROM_ABI _ForwardIterator
+min_element(_ExecutionPolicy&& __policy, _ForwardIterator __first, _ForwardIterator __last, _Compare __comp) {
+  _LIBCPP_REQUIRE_CPP17_FORWARD_ITERATOR(_ForwardIterator, "min_element requires ForwardIterators");
+  using _Implementation = __pstl::__dispatch<__pstl::__min_element, __pstl::__current_configuration, _RawPolicy>;
+  return __pstl::__handle_exception<_Implementation>(
+      std::forward<_ExecutionPolicy>(__policy), std::move(__first), std::move(__last), std::move(__comp));
+}
+
+template <class _ExecutionPolicy,
+          class _ForwardIterator,
+          class _RawPolicy                                    = __remove_cvref_t<_ExecutionPolicy>,
+          enable_if_t<is_execution_policy_v<_RawPolicy>, int> = 0>
+_LIBCPP_HIDE_FROM_ABI _ForwardIterator
+min_element(_ExecutionPolicy&& __policy, _ForwardIterator __first, _ForwardIterator __last) {
+  _LIBCPP_REQUIRE_CPP17_FORWARD_ITERATOR(_ForwardIterator, "min_element requires ForwardIterators");
+  return std::min_element(std::forward<_ExecutionPolicy>(__policy), std::move(__first), std::move(__last), less{});
+}
+
 _LIBCPP_END_NAMESPACE_STD
 
 #endif // _LIBCPP_HAS_EXPERIMENTAL_PSTL && _LIBCPP_STD_VER >= 17
diff --git a/libcxx/include/__functional/operations.h b/libcxx/include/__functional/operations.h
index 7f315ca851c08..882b0b0a8c737 100644
--- a/libcxx/include/__functional/operations.h
+++ b/libcxx/include/__functional/operations.h
@@ -462,6 +462,9 @@ _LIBCPP_CTAD_SUPPORTED_FOR_TYPE(greater);
 template <class _Tp>
 inline const bool __desugars_to_v<__greater_tag, greater<_Tp>, _Tp, _Tp> = true;
 
+template <class _Tp>
+inline const bool __desugars_to_v<__totally_ordered_greater_tag, greater<_Tp>, _Tp, _Tp> = is_integral<_Tp>::value;
+
 #if _LIBCPP_STD_VER >= 14
 template <>
 struct greater<void> {
@@ -477,6 +480,9 @@ struct greater<void> {
 template <class _Tp, class _Up>
 inline const bool __desugars_to_v<__greater_tag, greater<>, _Tp, _Up> = true;
 
+template <class _Tp>
+inline const bool __desugars_to_v<__totally_ordered_greater_tag, greater<>, _Tp, _Tp> = is_integral<_Tp>::value;
+
 template <class _Tp>
 struct __make_transparent<greater<_Tp>> {
   using type _LIBCPP_NODEBUG = greater<>;
diff --git a/libcxx/include/__functional/ranges_operations.h b/libcxx/include/__functional/ranges_operations.h
index dc9da061af264..8368afa76591b 100644
--- a/libcxx/include/__functional/ranges_operations.h
+++ b/libcxx/include/__functional/ranges_operations.h
@@ -109,6 +109,9 @@ inline const bool __desugars_to_v<__less_tag, ranges::less, _Tp, _Up> = true;
 template <class _Tp, class _Up>
 inline const bool __desugars_to_v<__greater_tag, ranges::greater, _Tp, _Up> = true;
 
+template <class _Tp, class _Up>
+inline const bool __desugars_to_v<__totally_ordered_greater_tag, ranges::greater, _Tp, _Up> = true;
+
 template <>
 inline const bool __is_generic_transparent_comparator_v<ranges::less> = true;
 
diff --git a/libcxx/include/__pstl/backend_fwd.h b/libcxx/include/__pstl/backend_fwd.h
index a7d53b6a1c989..523ea73faff9e 100644
--- a/libcxx/include/__pstl/backend_fwd.h
+++ b/libcxx/include/__pstl/backend_fwd.h
@@ -297,6 +297,12 @@ struct __reduce;
 // operator()(_Policy&&, _ForwardIterator __first, _ForwardIterator __last,
 //                       _Tp __init, _BinaryOperation __op) const noexcept;
 
+template <class _Backend, class _ExecutionPolicy>
+struct __min_element;
+// template <class _Policy, class _ForwardIterator, class _Comp>
+// optional<_ForwardIterator>
+// operator()(_Policy&&, _ForwardIterator __first, _ForwardIterator __last, _Comp __comp) const noexcept;
+
 } // namespace __pstl
 _LIBCPP_END_NAMESPACE_STD
 
diff --git a/libcxx/include/__pstl/backends/libdispatch.h b/libcxx/include/__pstl/backends/libdispatch.h
index 88d4231d29a0a..b73f0e6353033 100644
--- a/libcxx/include/__pstl/backends/libdispatch.h
+++ b/libcxx/include/__pstl/backends/libdispatch.h
@@ -33,6 +33,7 @@
 #include <__pstl/cpu_algos/find_if.h>
 #include <__pstl/cpu_algos/for_each.h>
 #include <__pstl/cpu_algos/merge.h>
+#include <__pstl/cpu_algos/min_element.h>
 #include <__pstl/cpu_algos/stable_sort.h>
 #include <__pstl/cpu_algos/transform.h>
 #include <__pstl/cpu_algos/transform_reduce.h>
@@ -392,6 +393,10 @@ template <class _ExecutionPolicy>
 struct __fill<__libdispatch_backend_tag, _ExecutionPolicy>
     : __cpu_parallel_fill<__libdispatch_backend_tag, _ExecutionPolicy> {};
 
+template <class _ExecutionPolicy>
+struct __min_element<__libdispatch_backend_tag, _ExecutionPolicy>
+    : __cpu_parallel_min_element<__libdispatch_backend_tag, _ExecutionPolicy> {};
+
 } // namespace __pstl
 _LIBCPP_END_NAMESPACE_STD
 
diff --git a/libcxx/include/__pstl/backends/serial.h b/libcxx/include/__pstl/backends/serial.h
index f4142016ccc79..e147c5898599c 100644
--- a/libcxx/include/__pstl/backends/serial.h
+++ b/libcxx/include/__pstl/backends/serial.h
@@ -175,6 +175,15 @@ struct __transform_reduce_binary<__serial_backend_tag, _ExecutionPolicy> {
   }
 };
 
+template <class _ExecutionPolicy>
+struct __min_element<__serial_backend_tag, _ExecutionPolicy> {
+  template <class _Policy, class _ForwardIterator, class _Comp>
+  _LIBCPP_HIDE_FROM_ABI optional<_ForwardIterator>
+  operator()(_Policy&&, _ForwardIterator __first, _ForwardIterator __last, _Comp&& __comp) const noexcept {
+    return std::min_element(std::move(__first), std::move(__last), std::forward<_Comp>(__comp));
+  }
+};
+
 } // namespace __pstl
 _LIBCPP_END_NAMESPACE_STD
 
diff --git a/libcxx/include/__pstl/backends/std_thread.h b/libcxx/include/__pstl/backends/std_thread.h
index dd2c3f15403e3..1b1f4f206e30d 100644
--- a/libcxx/include/__pstl/backends/std_thread.h
+++ b/libcxx/include/__pstl/backends/std_thread.h
@@ -17,6 +17,7 @@
 #include <__pstl/cpu_algos/find_if.h>
 #include <__pstl/cpu_algos/for_each.h>
 #include <__pstl/cpu_algos/merge.h>
+#include <__pstl/cpu_algos/min_element.h>
 #include <__pstl/cpu_algos/stable_sort.h>
 #include <__pstl/cpu_algos/transform.h>
 #include <__pstl/cpu_algos/transform_reduce.h>
@@ -129,6 +130,10 @@ template <class _ExecutionPolicy>
 struct __fill<__std_thread_backend_tag, _ExecutionPolicy>
     : __cpu_parallel_fill<__std_thread_backend_tag, _ExecutionPolicy> {};
 
+template <class _ExecutionPolicy>
+struct __min_element<__std_thread_backend_tag, _ExecutionPolicy>
+    : __cpu_parallel_min_element<__std_thread_backend_tag, _ExecutionPolicy> {};
+
 } // namespace __pstl
 _LIBCPP_END_NAMESPACE_STD
 
diff --git a/libcxx/include/__pstl/cpu_algos/min_element.h b/libcxx/include/__pstl/cpu_algos/min_element.h
new file mode 100644
index 0000000000000..a3e440314bd74
--- /dev/null
+++ b/libcxx/include/__pstl/cpu_algos/min_element.h
@@ -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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___PSTL_CPU_ALGOS_MIN_ELEMENT_H
+#define _LIBCPP___PSTL_CPU_ALGOS_MIN_ELEMENT_H
+
+#include <__algorithm/min_element.h>
+#include <__config>
+#include <__iterator/concepts.h>
+#include <__iterator/iterator_traits.h>
+#include <__pstl/backend_fwd.h>
+#include <__pstl/cpu_algos/cpu_traits.h>
+#include <__type_traits/desugars_to.h>
+#include <__type_traits/is_execution_policy.h>
+#include <__type_traits/is_trivially_copyable.h>
+#include <__utility/move.h>
+#include <__utility/unreachable.h>
+#include <optional>
+
+#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 {
+
+// Check if the comparator is totally ordered, and if it is,
+// we can use == instead of the double comparison !(comp(a,b) && !comp(b,a)).
+template <class _Compare, class _Tp>
+inline constexpr bool __desugars_to_totally_ordered_v =
+    __desugars_to_v<__totally_ordered_less_tag, _Compare, _Tp, _Tp> ||
+    __desugars_to_v<__totally_ordered_greater_tag, _Compare, _Tp, _Tp>;
+
+template <class _Backend, class _Index, class _DifferenceType, class _Compare>
+_LIBCPP_HIDE_FROM_ABI _Index __simd_min_element(_Index __first, _DifferenceType __n, _Compare __comp) noexcept {
+  if (__n == 0)
+    return __first;
+
+  using _ValueType                   = __iterator_value_type<_Index>;
+  constexpr size_t __lane_size       = __cpu_traits<_Backend>::__lane_size;
+  const _DifferenceType __block_size = __lane_size / sizeof(_ValueType);
+
+  if (__n < 2 * __block_size || __block_size < 2) {
+    _Index __result = __first;
+    for (_DifferenceType __i = 1; __i < __n; ++__i) {
+      if (__comp(__first[__i], *__result)) {
+        __result = __first + __i;
+      }
+    }
+    return __result;
+  }
+
+  // Pass 1: find minimum value
+  alignas(__lane_size) char __lane_buffer[__lane_size];
+  _ValueType* __lane = reinterpret_cast<_ValueType*>(__lane_buffer);
+
+  // initializer
+  _PSTL_PRAGMA_SIMD
+  for (_DifferenceType __i = 0; __i < __block_size; ++__i) {
+    _ValueType __a = __first[__i];
+    _ValueType __b = __first[__block_size + __i];
+    ::new (__lane + __i) _ValueType(__comp(__a, __b) ? __a : __b);
+  }
+
+  // main loop
+  _DifferenceType __i                    = 2 * __block_size;
+  const _DifferenceType __last_iteration = __block_size * (__n / __block_size);
+  for (; __i < __last_iteration; __i += __block_size) {
+    _PSTL_PRAGMA_SIMD
+    for (_DifferenceType __j = 0; __j < __block_size; ++__j) {
+      if (__comp(__first[__i + __j], __lane[__j])) {
+        __lane[__j] = __first[__i + __j];
+      }
+    }
+  }
+
+  // remainder
+  for (_DifferenceType __j = 0; __j < __n - __last_iteration; ++__j) {
+    if (__comp(__first[__last_iteration + __j], __lane[__j])) {
+      __lane[__j] = __first[__last_iteration + __j];
+    }
+  }
+
+  // combiner
+  _ValueType __min_val = __lane[0];
+  for (_DifferenceType __j = 1; __j < __block_size; ++__j) {
+    if (__comp(__lane[__j], __min_val)) {
+      __min_val = __lane[__j];
+    }
+  }
+
+  // destroyer
+  _PSTL_PRAGMA_SIMD
+  for (_DifferenceType __j = 0; __j < __block_size; ++__j) {
+    __lane[__j].~_ValueType();
+  }
+
+  // Pass 2: find first index with minimum value
+  constexpr _DifferenceType __find_block_size                          = __lane_size / sizeof(_DifferenceType);
+  alignas(__lane_size) _DifferenceType __found_lane[__find_block_size] = {0};
+  _DifferenceType __begin                                              = 0;
+
+  while (__n - __begin >= __find_block_size) {
+    _DifferenceType __found = 0;
+    _PSTL_PRAGMA_SIMD_REDUCTION(| : __found)
+    for (_DifferenceType __k = 0; __k < __find_block_size; ++__k) {
+      _DifferenceType __t;
+      if constexpr (__desugars_to_totally_ordered_v<_Compare, _ValueType>) {
+        __t = __first[__begin + __k] == __min_val;
+      } else {
+        __t = !__comp(__first[__begin + __k], __min_val) && !__comp(__min_val, __first[__begin + __k]);
+      }
+      __found_lane[__k] = __t;
+      __found |= __t;
+    }
+    if (__found) {
+      for (_DifferenceType __k = 0; __k < __find_block_size; ++__k) {
+        if (__found_lane[__k]) {
+          return __first + __begin + __k;
+        }
+      }
+    }
+    __begin += __find_block_size;
+  }
+
+  // remainder
+  while (__begin < __n) {
+    bool __is_equal;
+    if constexpr (__desugars_to_totally_ordered_v<_Compare, _ValueType>) {
+      __is_equal = __first[__begin] == __min_val;
+    } else {
+      __is_equal = !__comp(__first[__begin], __min_val) && !__comp(__min_val, __first[__begin]);
+    }
+    if (__is_equal) {
+      return __first + __begin;
+    }
+    ++__begin;
+  }
+
+  __libcpp_unreachable();
+}
+
+template <class _Backend, class _RawExecutionPolicy>
+struct __cpu_parallel_min_element {
+  template <class _Policy, class _ForwardIterator, class _Compare>
+  _LIBCPP_HIDE_FROM_ABI optional<_ForwardIterator>
+  operator()(_Policy&&, _ForwardIterator __first, _ForwardIterator __last, _Compare __comp) const noexcept {
+    if constexpr (__is_parallel_execution_policy_v<_RawExecutionPolicy> &&
+                  __has_random_access_iterator_category_or_concept<_ForwardIterator>::value) {
+      if (__first == __last) {
+        return __last;
+      }
+
+      return __cpu_traits<_Backend>::__transform_reduce(
+          std::move(__first),
+          __last,
+          [](_ForwardIterator __iter) { return __iter; },
+          __last,
+          [__comp, __last](_ForwardIterator __lhs_min, _ForwardIterator __rhs_min) {
+            if (__lhs_min == __last)
+              return __rhs_min;
+            if (__rhs_min == __last)
+              return __lhs_min;
+            if (__comp(*__lhs_min, *__rhs_min))
+              return __lhs_min;
+            if (__comp(*__rhs_min, *__lhs_min))
+              return __rhs_min;
+            return __lhs_min < __rhs_min ? __lhs_min : __rhs_min;
+          },
+          [__comp, __last](_ForwardIterator __brick_first, _ForwardIterator __brick_last, _ForwardIterator __acc) {
+            _ForwardIterator __local_min;
+            if constexpr (__is_unsequenced_execution_policy_v<__remove_parallel_policy_t<_RawExecutionPolicy>> &&
+                          is_trivially_copyable_v<__iterator_value_type<_ForwardIterator>>) {
+              __local_min =
+                  __pstl::__simd_min_element<_Backend>(std::move(__brick_first), __brick_last - __brick_first, __comp);
+            } else {
+              __local_min = std::min_element(std::move(__brick_first), __brick_last, __comp);
+            }
+            if (__local_min == __brick_last)
+              return __acc;
+            if (__acc == __last)
+              return __local_min;
+            if (__comp(*__local_min, *__acc))
+              return __local_min;
+            if (__comp(*__acc, *__local_min))
+              return __acc;
+            return __local_min < __acc ? __local_min : __acc;
+          });
+    } else if constexpr (__is_unsequenced_execution_policy_v<_RawExecutionPolicy> &&
+                         __has_random_access_iterator_category_or_concept<_ForwardIterator>::value &&
+                         is_trivially_copyable_v<__iterator_value_type<_ForwardIterator>>) {
+      return __pstl::__simd_min_element<_Backend>(__first, __last - __first, std::move(__comp));
+    } else {
+      return std::min_element(std::move(__first), std::move(__last), std::move(__comp));
+    }
+  }
+};
+
+} // namespace __pstl
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_STD_VER >= 17
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___PSTL_CPU_ALGOS_MIN_ELEMENT_H
diff --git a/libcxx/include/__type_traits/desugars_to.h b/libcxx/include/__type_traits/desugars_to.h
index 029b3c6336837..fb0a458dc66bb 100644
--- a/libcxx/include/__type_traits/desugars_to.h
+++ b/libcxx/include/__type_traits/desugars_to.h
@@ -40,6 +40,14 @@ struct __greater_tag {};
 // additional semantic requirements on that operation.
 struct __totally_ordered_less_tag {};
 
+// syntactically, the operation is equivalent to calling `a > b`, and these expressions
+// have to be true for any `a` and `b`:
+// - `(a > b) == (b < a)`
+// - `(!(a > b) && !(b > a)) == (a == b)`
+// For example, this is satisfied for std::greater on integral types, but also for ranges::greater on all types due to
+// additional semantic requirements on that operation.
+struct __totally_ordered_greater_tag {};
+
 // This class template is used to determine whether an operation "desugars"
 // (or boils down) to a given canonical operation.
 //
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index ce168f77dfea4..9a119f47cfb09 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -2336,6 +2336,9 @@ module std [system] {
       module merge {
         header "__pstl/cpu_algos/merge.h"
       }
+      module min_element {
+        header "__pstl/cpu_algos/min_element.h"
+      }
       module stable_sort {
         header "__pstl/cpu_algos/stable_sort.h"
         export std_core.utility_core.empty
diff --git a/libcxx/test/benchmarks/algorithms/pstl.min_element.bench.cpp b/libcxx/test/benchmarks/algorithms/pstl.min_element.bench.cpp
new file mode 100644
index 0000000000000..60f11e5ccc9a4
--- /dev/null
+++ b/libcxx/test/benchmarks/algorithms/pstl.min_element.bench.cpp
@@ -0,0 +1,119 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+#include <algorithm>
+#include <cmath>
+#include <execution>
+#include <functional>
+#include <string>
+#include <vector>
+
+#include <benchmark/benchmark.h>
+
+struct Seq {
+  static constexpr const auto& policy() { return std::execution::seq; }
+};
+struct Unseq {
+  static constexpr const auto& policy() { return std::execution::unseq; }
+};
+struct Par {
+  static constexpr const auto& policy() { return std::execution::par; }
+};
+struct ParUnseq {
+  static constexpr const auto& policy() { return std::execution::par_unseq; }
+};
+
+struct MinFirst {
+  static size_t pos(size_t) { return 0; }
+};
+struct MinMiddle {
+  static size_t pos(size_t size) { return size / 2; }
+};
+struct MinLast {
+  static size_t pos(size_t size) { return size - 1; }
+};
+
+template <class Benchmark>
+void run_sizes(Benchmark benchmark) {
+  benchmark->Arg(64)->Arg(512)->Arg(1024)->Arg(4096)->Arg(65536)->Arg(262144);
+}
+
+template <class T, class Policy, class Position>
+void BM_min_element(benchmark::State& state) {
+  std::vector<T> vec(state.range(), 3);
+  vec[Position::pos(vec.size())] = 1;
+
+  for (auto _ : state) {
+    benchmark::DoNotOptimize(vec);
+    benchmark::DoNotOptimize(std::min_element(Policy::policy(), vec.begin(), vec.end()));
+  }
+}
+
+struct Point3D {
+  double x, y, z;
+  Point3D(double v = 0.0) : x(v), y(v), z(v) {}
+};
+
+struct ExpensiveComparator {
+  bool operator()(const Point3D& a, const Point3D& b) const {
+    double dist_a = std::sqrt(a.x * a.x + a.y * a.y + a.z * a.z);
+    double dist_b = std::sqrt(b.x * b.x + b.y * b.y + b.z * b.z);
+    return dist_a < dist_b;
+  }
+};
+
+template <class Policy, class Position>
+void BM_min_element_expensive(benchmark::State& state) {
+  std::vector<Point3D> vec(state.range(), Point3D(3.0));
+  vec[Position::pos(vec.size())] = Point3D(0.5);
+
+  for (auto _ : state) {
+    benchmark::DoNotOptimize(vec);
+    benchmark::DoNotOptimize(std::min_element(Policy::policy(), vec.begin(), vec.end(), ExpensiveComparator{}));
+  }
+}
+
+template <class Policy, class Position>
+void BM_min_element_non_trivially_copyable(benchmark::State& state) {
+  std::vector<std::string> vec(state.range(), "3");
+  vec[Position::pos(vec.size())] = "1";
+
+  for (auto _ : state) {
+    benchmark::DoNotOptimize(vec);
+    benchmark::DoNotOptimize(std::min_element(Policy::policy(), vec.begin(), vec.end()));
+  }
+}
+
+#define BENCH_ALL_POLICIES(BenchFunc, Pos)                                                                             \
+  BENCHMARK(BenchFunc<Seq, Pos>)->Apply(run_sizes);                                                                    \
+  BENCHMARK(BenchFunc<Unseq, Pos>)->Apply(run_sizes);                                                                  \
+  BENCHMARK(BenchFunc<Par, Pos>)->Apply(run_sizes);                                                                    \
+  BENCHMARK(BenchFunc<ParUnseq, Pos>)->Apply(run_sizes)
+
+#define BENCH_ALL_POLICIES_T(BenchFunc, Type, Pos)                                                                     \
+  BENCHMARK(BenchFunc<Type, Seq, Pos>)->Apply(run_sizes);                                                              \
+  BENCHMARK(BenchFunc<Type, Unseq, Pos>)->Apply(run_sizes);                                                            \
+  BENCHMARK(BenchFunc<Type, Par, Pos>)->Apply(run_sizes);                                                              \
+  BENCHMARK(BenchFunc<Type, ParUnseq, Pos>)->Apply(run_sizes)
+
+BENCH_ALL_POLICIES_T(BM_min_element, int, MinFirst);
+BENCH_ALL_POLICIES_T(BM_min_element, int, MinMiddle);
+BENCH_ALL_POLICIES_T(BM_min_element, int, MinLast);
+
+BENCH_ALL_POLICIES(BM_min_element_expensive, MinFirst);
+BENCH_ALL_POLICIES(BM_min_element_expensive, MinMiddle);
+BENCH_ALL_POLICIES(BM_min_element_expensive, MinLast);
+
+BENCH_ALL_POLICIES(BM_min_element_non_trivially_copyable, MinFirst);
+BENCH_ALL_POLICIES(BM_min_element_non_trivially_copyable, MinMiddle);
+BENCH_ALL_POLICIES(BM_min_element_non_trivially_copyable, MinLast);
+
+BENCHMARK_MAIN();
diff --git a/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp b/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
index e5bd7e764c59b..b5ee7164f2dff 100644
--- a/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
+++ b/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
@@ -190,4 +190,9 @@ void f(non_forward_iterator non_fwd, non_output_iterator non_output, std::execut
     (void)std::transform_reduce(
         pol, non_fwd, non_fwd, val, func, func); // expected-error@*:* {{static assertion failed: transform_reduce}}
   }
+
+  {
+    (void)std::min_element(pol, non_fwd, non_fwd);       // expected-error@*:* {{static assertion failed: min_element}}
+    (void)std::min_element(pol, non_fwd, non_fwd, pred); // expected-error@*:* {{static assertion failed: min_element}}
+  }
 }
diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.min.max/pstl.min_element.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.min.max/pstl.min_element.pass.cpp
new file mode 100644
index 0000000000000..2ea129201bf6f
--- /dev/null
+++ b/libcxx/test/std/algorithms/alg.sorting/alg.min.max/pstl.min_element.pass.cpp
@@ -0,0 +1,108 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// <algorithm>
+
+// template<class ExecutionPolicy, class ForwardIterator>
+//   ForwardIterator min_element(ExecutionPolicy&& exec,
+//                               ForwardIterator first, ForwardIterator last);
+//
+// template<class ExecutionPolicy, class ForwardIterator, class Compare>
+//   ForwardIterator min_element(ExecutionPolicy&& exec,
+//                               ForwardIterator first, ForwardIterator last,
+//                               Compare comp);
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <string>
+#include <vector>
+
+#include "test_macros.h"
+#include "test_execution_policies.h"
+#include "test_iterators.h"
+
+template <class Iter>
+struct Test {
+  template <class Policy>
+  void operator()(Policy&& policy) {
+    { // simple test
+      int a[] = {5, 3, 8, 1, 9, 2};
+      assert(base(std::min_element(policy, Iter(std::begin(a)), Iter(std::end(a)))) == a + 3);
+    }
+    { // std::greater
+      int a[] = {5, 3, 8, 1, 9, 2};
+      assert(base(std::min_element(policy, Iter(std::begin(a)), Iter(std::end(a)), std::greater<>())) == a + 4);
+    }
+    { // custom comparator
+      int a[] = {5, 3, 8, 1, 9, 2};
+      assert(base(std::min_element(policy, Iter(std::begin(a)), Iter(std::end(a)), [](int lhs, int rhs) {
+               return lhs < rhs;
+             })) == a + 3);
+    }
+    { // empty range
+      int a[] = {5, 3, 8, 1, 9, 2};
+      assert(base(std::min_element(policy, Iter(std::begin(a)), Iter(std::begin(a)))) == std::begin(a));
+    }
+    { // single element
+      int a[] = {5};
+      assert(base(std::min_element(policy, Iter(std::begin(a)), Iter(std::end(a)))) == std::begin(a));
+    }
+    { // first occurrence is returned when multiple minimums exist
+      int a[] = {2, 1, 1, 3, 1, 2};
+      assert(base(std::min_element(policy, Iter(std::begin(a)), Iter(std::end(a)))) == a + 1);
+    }
+    { // min at the beginning
+      std::vector<int> v(100, 10);
+      v[0] = 1;
+      assert(base(std::min_element(policy, Iter(std::data(v)), Iter(std::data(v) + std::size(v)))) == std::data(v));
+    }
+    { // min at the end
+      std::vector<int> v(100, 10);
+      v[99] = 1;
+      assert(base(std::min_element(policy, Iter(std::data(v)), Iter(std::data(v) + std::size(v)))) ==
+             std::data(v) + 99);
+    }
+    { // check that a large range works
+      std::vector<int> v(100000, 10);
+      v[50000] = 1;
+      assert(base(std::min_element(policy, Iter(std::data(v)), Iter(std::data(v) + std::size(v)))) ==
+             std::data(v) + 50000);
+    }
+    { // check that a size not divisible by SIMD block size works
+      std::vector<int> v(49, 10);
+      v[48] = 1;
+      assert(base(std::min_element(policy, Iter(std::data(v)), Iter(std::data(v) + std::size(v)))) ==
+             std::data(v) + 48);
+    }
+    { // all elements equal - should return first element
+      std::vector<int> v(10000, 42);
+      assert(base(std::min_element(policy, Iter(std::data(v)), Iter(std::data(v) + std::size(v)))) == std::data(v));
+    }
+  }
+};
+
+int main(int, char**) {
+  types::for_each(types::forward_iterator_list<int*>{}, TestIteratorWithPolicies<Test>{});
+  types::for_each(types::random_access_iterator_list<int*>{}, TestIteratorWithPolicies<Test>{});
+
+  test_execution_policies([](auto&& policy) {
+    { // non-trivially copyable
+      std::vector<std::string> v(10000, "c++23");
+      v[1001] = "c++17";
+      v[2001] = "c++17";
+      assert(std::min_element(policy, v.begin(), v.end()) == v.begin() + 1001);
+    }
+  });
+
+  return 0;
+}

>From ac0b0fdace10604b550cd4f4f53d272a90f8f33a Mon Sep 17 00:00:00 2001
From: mleleszi <mleleszi at icloud.com>
Date: Tue, 30 Dec 2025 11:12:52 +0100
Subject: [PATCH 2/4] Add missing header

---
 libcxx/include/__pstl/backends/serial.h                          | 1 +
 .../algorithms/alg.sorting/alg.min.max/pstl.min_element.pass.cpp | 1 +
 2 files changed, 2 insertions(+)

diff --git a/libcxx/include/__pstl/backends/serial.h b/libcxx/include/__pstl/backends/serial.h
index e147c5898599c..47a59ad02430c 100644
--- a/libcxx/include/__pstl/backends/serial.h
+++ b/libcxx/include/__pstl/backends/serial.h
@@ -13,6 +13,7 @@
 #include <__algorithm/find_if.h>
 #include <__algorithm/for_each.h>
 #include <__algorithm/merge.h>
+#include <__algorithm/min_element.h>
 #include <__algorithm/stable_sort.h>
 #include <__algorithm/transform.h>
 #include <__config>
diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.min.max/pstl.min_element.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.min.max/pstl.min_element.pass.cpp
index 2ea129201bf6f..91588521e06c8 100644
--- a/libcxx/test/std/algorithms/alg.sorting/alg.min.max/pstl.min_element.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.sorting/alg.min.max/pstl.min_element.pass.cpp
@@ -24,6 +24,7 @@
 #include <algorithm>
 #include <cassert>
 #include <cmath>
+#include <functional>
 #include <string>
 #include <vector>
 

>From dbbacd3dff4bbaad1399f961df06f55aa29a12b5 Mon Sep 17 00:00:00 2001
From: mleleszi <mleleszi at icloud.com>
Date: Tue, 30 Dec 2025 14:20:44 +0100
Subject: [PATCH 3/4] Fix benchmark cpp version

---
 .../test/benchmarks/algorithms/pstl.min_element.bench.cpp  | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/libcxx/test/benchmarks/algorithms/pstl.min_element.bench.cpp b/libcxx/test/benchmarks/algorithms/pstl.min_element.bench.cpp
index 60f11e5ccc9a4..f2496f0ad799c 100644
--- a/libcxx/test/benchmarks/algorithms/pstl.min_element.bench.cpp
+++ b/libcxx/test/benchmarks/algorithms/pstl.min_element.bench.cpp
@@ -6,7 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 
-// REQUIRES: std-at-least-c++17
+// REQUIRES: std-at-least-c++20
 // UNSUPPORTED: libcpp-has-no-incomplete-pstl
 
 #include <algorithm>
@@ -41,10 +41,7 @@ struct MinLast {
   static size_t pos(size_t size) { return size - 1; }
 };
 
-template <class Benchmark>
-void run_sizes(Benchmark benchmark) {
-  benchmark->Arg(64)->Arg(512)->Arg(1024)->Arg(4096)->Arg(65536)->Arg(262144);
-}
+void run_sizes(auto benchmark) { benchmark->Arg(64)->Arg(512)->Arg(1024)->Arg(4096)->Arg(65536)->Arg(262144); }
 
 template <class T, class Policy, class Position>
 void BM_min_element(benchmark::State& state) {

>From 7dd0f4e4129a23f6ceb8f630d0372eaa3eb163bb Mon Sep 17 00:00:00 2001
From: mleleszi <mleleszi at icloud.com>
Date: Tue, 30 Dec 2025 15:12:24 +0100
Subject: [PATCH 4/4] Add nodiscard to public functions

---
 libcxx/include/__algorithm/pstl.h             |  4 +--
 .../pstl.min_element.nodiscard.verify.cpp     | 35 +++++++++++++++++++
 2 files changed, 37 insertions(+), 2 deletions(-)
 create mode 100644 libcxx/test/std/algorithms/alg.sorting/alg.min.max/pstl.min_element.nodiscard.verify.cpp

diff --git a/libcxx/include/__algorithm/pstl.h b/libcxx/include/__algorithm/pstl.h
index 8886c133db7e6..804af4ef157b9 100644
--- a/libcxx/include/__algorithm/pstl.h
+++ b/libcxx/include/__algorithm/pstl.h
@@ -659,7 +659,7 @@ template <class _ExecutionPolicy,
           class _Compare,
           class _RawPolicy                                    = __remove_cvref_t<_ExecutionPolicy>,
           enable_if_t<is_execution_policy_v<_RawPolicy>, int> = 0>
-_LIBCPP_HIDE_FROM_ABI _ForwardIterator
+[[nodiscard]] _LIBCPP_HIDE_FROM_ABI _ForwardIterator
 min_element(_ExecutionPolicy&& __policy, _ForwardIterator __first, _ForwardIterator __last, _Compare __comp) {
   _LIBCPP_REQUIRE_CPP17_FORWARD_ITERATOR(_ForwardIterator, "min_element requires ForwardIterators");
   using _Implementation = __pstl::__dispatch<__pstl::__min_element, __pstl::__current_configuration, _RawPolicy>;
@@ -671,7 +671,7 @@ template <class _ExecutionPolicy,
           class _ForwardIterator,
           class _RawPolicy                                    = __remove_cvref_t<_ExecutionPolicy>,
           enable_if_t<is_execution_policy_v<_RawPolicy>, int> = 0>
-_LIBCPP_HIDE_FROM_ABI _ForwardIterator
+[[nodiscard]] _LIBCPP_HIDE_FROM_ABI _ForwardIterator
 min_element(_ExecutionPolicy&& __policy, _ForwardIterator __first, _ForwardIterator __last) {
   _LIBCPP_REQUIRE_CPP17_FORWARD_ITERATOR(_ForwardIterator, "min_element requires ForwardIterators");
   return std::min_element(std::forward<_ExecutionPolicy>(__policy), std::move(__first), std::move(__last), less{});
diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.min.max/pstl.min_element.nodiscard.verify.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.min.max/pstl.min_element.nodiscard.verify.cpp
new file mode 100644
index 0000000000000..6a33ba35e4853
--- /dev/null
+++ b/libcxx/test/std/algorithms/alg.sorting/alg.min.max/pstl.min_element.nodiscard.verify.cpp
@@ -0,0 +1,35 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// <algorithm>
+
+// template<class ExecutionPolicy, class ForwardIterator>
+//   [[nodiscard]] ForwardIterator min_element(ExecutionPolicy&& exec,
+//                                             ForwardIterator first, ForwardIterator last);
+//
+// template<class ExecutionPolicy, class ForwardIterator, class Compare>
+//   [[nodiscard]] ForwardIterator min_element(ExecutionPolicy&& exec,
+//                                             ForwardIterator first, ForwardIterator last,
+//                                             Compare comp);
+
+#include <algorithm>
+#include <execution>
+#include <functional>
+
+int main(int, char**) {
+  int arr[] = {1, 2, 3};
+
+  // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::min_element(std::execution::seq, arr, arr + 3);
+  // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::min_element(std::execution::seq, arr, arr + 3, std::less<int>());
+}



More information about the libcxx-commits mailing list