[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