From ae89178c746afde363d6c335ae6df73bfa40598f Mon Sep 17 00:00:00 2001 From: Alfredo Correa Date: Sat, 13 Jun 2026 02:15:17 -0700 Subject: [PATCH 1/7] add ordering (argsort) algorithm with caller-provided output ordering() writes the index permutation that sorts a 1D range without moving the data; output goes into a caller-provided random-access range (no internal allocation). Adds test/ordering.cpp. Co-Authored-By: Claude Opus 4.8 --- include/boost/multi/algorithms/ordering.hpp | 44 ++++++++ test/ordering.cpp | 112 ++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 include/boost/multi/algorithms/ordering.hpp create mode 100644 test/ordering.cpp diff --git a/include/boost/multi/algorithms/ordering.hpp b/include/boost/multi/algorithms/ordering.hpp new file mode 100644 index 000000000..b54a6882f --- /dev/null +++ b/include/boost/multi/algorithms/ordering.hpp @@ -0,0 +1,44 @@ +// Copyright 2026 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// `ordering` computes the permutation of indices that would sort a (multi)dimensional +// range *without moving the data* (a.k.a. argsort). The result is written into a +// caller-provided output range (no internal allocation; the sort is in-place over the +// indices via `std::sort`). Because it orders by index, it handles non-zero-based +// arrays naturally and never copies/moves the (possibly proxy-referenced) elements. + +// #pragma once +#ifndef BOOST_MULTI_ALGORITHMS_ORDERING_HPP +#define BOOST_MULTI_ALGORITHMS_ORDERING_HPP + +#include // for std::sort, std::copy +#include // for std::less + +namespace boost::multi { + +// Writes into [first, ...) the permutation of `arr`'s indices such that +// `arr[result[0]], arr[result[1]], ...` is non-decreasing according to `comp`. +// `first` must point to a mutable random-access range of at least `arr.size()` elements. +// `arr` is not modified. Returns the end of the written range. +template +auto ordering(Array1D const& arr, RandomAccessIt first, Compare comp) -> RandomAccessIt { + auto const ext = arr.extension(); + RandomAccessIt const last = std::copy(ext.begin(), ext.end(), first); // seed output with the index values of `arr` + + std::sort( + first, last, + [&arr, comp](auto idx1, auto idx2) { return comp(arr[idx1], arr[idx2]); } + ); + + return last; +} + +template +auto ordering(Array1D const& arr, RandomAccessIt first) -> RandomAccessIt { + return ordering(arr, first, std::less<>{}); +} + +} // end namespace boost::multi + +#endif // BOOST_MULTI_ALGORITHMS_ORDERING_HPP diff --git a/test/ordering.cpp b/test/ordering.cpp new file mode 100644 index 000000000..1f443928f --- /dev/null +++ b/test/ordering.cpp @@ -0,0 +1,112 @@ +// Copyright 2026 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include // for ordering +#include // for array + +#include + +#include // for array +#include // for greater + +namespace multi = boost::multi; + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // basic ascending order into a caller-provided std::array + { + multi::array const arr = {3, 1, 2}; + + std::array order{}; + + BOOST_TEST( multi::ordering(arr, order.begin()) == order.end() ); + + BOOST_TEST( order[0] == 1 ); + BOOST_TEST( order[1] == 2 ); + BOOST_TEST( order[2] == 0 ); + + // the data must be untouched + BOOST_TEST( arr[0] == 3 ); + BOOST_TEST( arr[1] == 1 ); + BOOST_TEST( arr[2] == 2 ); + + // reading through the order gives a sorted view + BOOST_TEST( arr[order[0]] == 1 ); + BOOST_TEST( arr[order[1]] == 2 ); + BOOST_TEST( arr[order[2]] == 3 ); + } + + // descending order via custom comparator + { + multi::array const arr = {3, 1, 2}; + + std::array order{}; + + multi::ordering(arr, order.begin(), std::greater<>{}); + + BOOST_TEST( arr[order[0]] == 3 ); + BOOST_TEST( arr[order[1]] == 2 ); + BOOST_TEST( arr[order[2]] == 1 ); + } + + // already sorted -> identity permutation + { + multi::array const arr = {1, 2, 3, 4}; + + std::array order{}; + + multi::ordering(arr, order.begin()); + + BOOST_TEST( order[0] == 0 ); + BOOST_TEST( order[1] == 1 ); + BOOST_TEST( order[2] == 2 ); + BOOST_TEST( order[3] == 3 ); + } + + // output into a caller-provided multi::array, floating-point elements + { + multi::array const arr = {2.5, -1.0, 0.0, 9.9, 3.3}; + + multi::array order(arr.extents()); + + BOOST_TEST( multi::ordering(arr, order.begin()) == order.end() ); + + for(multi::index k = 0; k + 1 != order.size(); ++k) { // NOLINT(altera-unroll-loops,altera-id-dependent-backward-branch) + BOOST_TEST( arr[order[k]] <= arr[order[k + 1]] ); + } + } + + // ties: std::sort is not stable, so only require the result to be sorted and a valid permutation + { + multi::array const arr = {2, 2, 1, 3, 1}; + + multi::array order(arr.extents()); + + multi::ordering(arr, order.begin()); + + for(multi::index k = 0; k + 1 != order.size(); ++k) { // NOLINT(altera-unroll-loops,altera-id-dependent-backward-branch) + BOOST_TEST( arr[order[k]] <= arr[order[k + 1]] ); + } + + multi::index sum = 0; + for(auto idx : order) { // NOLINT(altera-unroll-loops,altera-id-dependent-backward-branch) + sum += idx; + } + BOOST_TEST( sum == (0 + 1 + 2 + 3 + 4) ); // it is a permutation of {0..4} + } + + // caller-provided raw pointer (via std::array::data) as output + { + multi::array const arr = {5, 4, 6}; + + std::array buf{}; + + BOOST_TEST( multi::ordering(arr, buf.data()) == buf.end() ); + + BOOST_TEST( arr[buf[0]] == 4 ); + BOOST_TEST( arr[buf[1]] == 5 ); + BOOST_TEST( arr[buf[2]] == 6 ); + } + + return boost::report_errors(); +} From ecafb168dd1b91b35061cbd95661238c1c4bb468 Mon Sep 17 00:00:00 2001 From: Alfredo Correa Date: Sat, 13 Jun 2026 10:03:19 -0700 Subject: [PATCH 2/7] fix MSVC: compare raw pointers, not std::array wrapped end iterator Co-Authored-By: Claude Opus 4.8 --- test/ordering.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/ordering.cpp b/test/ordering.cpp index 1f443928f..6445a33ba 100644 --- a/test/ordering.cpp +++ b/test/ordering.cpp @@ -9,6 +9,7 @@ #include // for array #include // for greater +#include // for next namespace multi = boost::multi; @@ -101,7 +102,8 @@ auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugpro std::array buf{}; - BOOST_TEST( multi::ordering(arr, buf.data()) == buf.end() ); + // compare raw pointers on both sides: MSVC's std::array::end() is a wrapped iterator, not a pointer + BOOST_TEST( multi::ordering(arr, buf.data()) == std::next(buf.data(), 3) ); BOOST_TEST( arr[buf[0]] == 4 ); BOOST_TEST( arr[buf[1]] == 5 ); From ddc6aca735da9950fdfc34163a4da37ad1116d37 Mon Sep 17 00:00:00 2001 From: Alfredo Correa Date: Sat, 13 Jun 2026 11:27:46 -0700 Subject: [PATCH 3/7] fix gcc -O3 -Warray-bounds false positive: use heap-backed output buffers std::sort inlined over a compile-time-sized std::array<,3> trips gcc's -Warray-bounds (introsort's threshold-16 path is dead but flagged at -O3). Use multi::array output buffers (and data_elements() for the pointer case) which gcc cannot bound, so the false positive disappears. Co-Authored-By: Claude Opus 4.8 --- test/ordering.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/test/ordering.cpp b/test/ordering.cpp index 6445a33ba..953d43565 100644 --- a/test/ordering.cpp +++ b/test/ordering.cpp @@ -7,18 +7,17 @@ #include -#include // for array #include // for greater #include // for next namespace multi = boost::multi; auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) - // basic ascending order into a caller-provided std::array + // basic ascending order into a caller-provided output buffer { multi::array const arr = {3, 1, 2}; - std::array order{}; + multi::array order(arr.extents()); BOOST_TEST( multi::ordering(arr, order.begin()) == order.end() ); @@ -41,7 +40,7 @@ auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugpro { multi::array const arr = {3, 1, 2}; - std::array order{}; + multi::array order(arr.extents()); multi::ordering(arr, order.begin(), std::greater<>{}); @@ -54,7 +53,7 @@ auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugpro { multi::array const arr = {1, 2, 3, 4}; - std::array order{}; + multi::array order(arr.extents()); multi::ordering(arr, order.begin()); @@ -64,7 +63,7 @@ auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugpro BOOST_TEST( order[3] == 3 ); } - // output into a caller-provided multi::array, floating-point elements + // floating-point elements; verify the order yields a sorted sequence { multi::array const arr = {2.5, -1.0, 0.0, 9.9, 3.3}; @@ -90,20 +89,19 @@ auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugpro } multi::index sum = 0; - for(auto idx : order) { // NOLINT(altera-unroll-loops,altera-id-dependent-backward-branch) + for(auto idx : order) { // NOLINT(altera-unroll-loops) sum += idx; } BOOST_TEST( sum == (0 + 1 + 2 + 3 + 4) ); // it is a permutation of {0..4} } - // caller-provided raw pointer (via std::array::data) as output + // caller-provided raw pointer (the most general output) via data_elements() { multi::array const arr = {5, 4, 6}; - std::array buf{}; + multi::array buf(arr.extents()); - // compare raw pointers on both sides: MSVC's std::array::end() is a wrapped iterator, not a pointer - BOOST_TEST( multi::ordering(arr, buf.data()) == std::next(buf.data(), 3) ); + BOOST_TEST( multi::ordering(arr, buf.data_elements()) == std::next(buf.data_elements(), 3) ); BOOST_TEST( arr[buf[0]] == 4 ); BOOST_TEST( arr[buf[1]] == 5 ); From d101f565b09d60c581f0b16f88b39a94a212e5a7 Mon Sep 17 00:00:00 2001 From: Alfredo Correa Date: Sat, 13 Jun 2026 12:34:04 -0700 Subject: [PATCH 4/7] add apply_ordering: gather src into caller-provided dst by a permutation dst[k] = src[order[k]]; no internal allocation, src untouched. Works for N-dimensional src (gathers whole slices), enabling e.g. reordering the rows of a matrix by an order computed from a separate key column. Co-Authored-By: Claude Opus 4.8 --- include/boost/multi/algorithms/ordering.hpp | 15 +++++++ test/ordering.cpp | 43 +++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/include/boost/multi/algorithms/ordering.hpp b/include/boost/multi/algorithms/ordering.hpp index b54a6882f..e3045abe5 100644 --- a/include/boost/multi/algorithms/ordering.hpp +++ b/include/boost/multi/algorithms/ordering.hpp @@ -14,6 +14,7 @@ #include // for std::sort, std::copy #include // for std::less +#include // for std::forward namespace boost::multi { @@ -39,6 +40,20 @@ auto ordering(Array1D const& arr, RandomAccessIt first) -> RandomAccessIt { return ordering(arr, first, std::less<>{}); } +// Applies an ordering (or any index permutation) `order` to `src`, gathering the result +// into the caller-provided `dst`: `dst[k] = src[order[k]]`. No internal allocation. +// `order` is a random-access iterator over indices (at least `dst.size()` of them). +// Works for N-dimensional `src`: `src[order[k]]` is a whole slice and the assignment +// copies it element-wise into the corresponding slice of `dst`. `src` is not modified. +template +auto apply_ordering(Array const& src, OrderIt order, DestArray&& dst) -> DestArray&& { + for(auto&& dst_elem : dst) { // NOLINT(altera-unroll-loops) + dst_elem = src[*order]; + ++order; + } + return std::forward(dst); +} + } // end namespace boost::multi #endif // BOOST_MULTI_ALGORITHMS_ORDERING_HPP diff --git a/test/ordering.cpp b/test/ordering.cpp index 953d43565..3e40f46a8 100644 --- a/test/ordering.cpp +++ b/test/ordering.cpp @@ -108,5 +108,48 @@ auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugpro BOOST_TEST( arr[buf[2]] == 6 ); } + // apply_ordering: gather a 1D array into a caller-provided destination (sorted copy) + { + multi::array const arr = {3, 1, 2}; + + multi::array order(arr.extents()); + multi::ordering(arr, order.begin()); + + multi::array sorted(arr.extents()); + multi::apply_ordering(arr, order.begin(), sorted); + + BOOST_TEST( sorted[0] == 1 ); + BOOST_TEST( sorted[1] == 2 ); + BOOST_TEST( sorted[2] == 3 ); + + // source untouched + BOOST_TEST( arr[0] == 3 ); + BOOST_TEST( arr[1] == 1 ); + BOOST_TEST( arr[2] == 2 ); + } + + // apply_ordering: reorder the ROWS of a 2D array by an order computed from a separate key + { + multi::array const mat = { + {3, 30}, + {1, 10}, + {2, 20}, + }; + multi::array const key = {3, 1, 2}; + + multi::array order(key.extents()); + multi::ordering(key, order.begin()); + + multi::array out(mat.extents()); + multi::apply_ordering(mat, order.begin(), out); + + BOOST_TEST( out[0][0] == 1 ); + BOOST_TEST( out[0][1] == 10 ); + BOOST_TEST( out[1][0] == 2 ); + BOOST_TEST( out[1][1] == 20 ); + BOOST_TEST( out[2][0] == 3 ); + BOOST_TEST( out[2][1] == 30 ); + } + return boost::report_errors(); } From b93a51892ae02434ef34cc2b4650187c260df18c Mon Sep 17 00:00:00 2001 From: Alfredo Correa Date: Sat, 13 Jun 2026 13:00:13 -0700 Subject: [PATCH 5/7] drop apply_ordering: out-of-place is std::transform, in-place is boost::algorithm::apply_permutation Applying an ordering needs no dedicated function: gather is a std::transform over the index range (or a lazy views::transform), and in-place reordering is boost::algorithm::apply_permutation. Document both idioms in the header instead. Co-Authored-By: Claude Opus 4.8 --- include/boost/multi/algorithms/ordering.hpp | 21 ++++------ test/ordering.cpp | 43 --------------------- 2 files changed, 7 insertions(+), 57 deletions(-) diff --git a/include/boost/multi/algorithms/ordering.hpp b/include/boost/multi/algorithms/ordering.hpp index e3045abe5..34c68120e 100644 --- a/include/boost/multi/algorithms/ordering.hpp +++ b/include/boost/multi/algorithms/ordering.hpp @@ -14,7 +14,6 @@ #include // for std::sort, std::copy #include // for std::less -#include // for std::forward namespace boost::multi { @@ -40,19 +39,13 @@ auto ordering(Array1D const& arr, RandomAccessIt first) -> RandomAccessIt { return ordering(arr, first, std::less<>{}); } -// Applies an ordering (or any index permutation) `order` to `src`, gathering the result -// into the caller-provided `dst`: `dst[k] = src[order[k]]`. No internal allocation. -// `order` is a random-access iterator over indices (at least `dst.size()` of them). -// Works for N-dimensional `src`: `src[order[k]]` is a whole slice and the assignment -// copies it element-wise into the corresponding slice of `dst`. `src` is not modified. -template -auto apply_ordering(Array const& src, OrderIt order, DestArray&& dst) -> DestArray&& { - for(auto&& dst_elem : dst) { // NOLINT(altera-unroll-loops) - dst_elem = src[*order]; - ++order; - } - return std::forward(dst); -} +// To *apply* the resulting `order` (or any index permutation) there is no need for a +// dedicated algorithm here: +// - out of place (gather): dst[k] = src[order[k]], i.e. +// std::transform(order.begin(), order.end(), dst.begin(), [&src](auto idx) { return src[idx]; }); +// (or, lazily, a view: order | std::views::transform([&src](auto idx) { return src[idx]; })) +// - in place: boost::algorithm::apply_permutation(src.begin(), src.end(), order.begin(), order.end()); +// Both work for N-dimensional `src`, where `src[idx]` is a whole slice. } // end namespace boost::multi diff --git a/test/ordering.cpp b/test/ordering.cpp index 3e40f46a8..953d43565 100644 --- a/test/ordering.cpp +++ b/test/ordering.cpp @@ -108,48 +108,5 @@ auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugpro BOOST_TEST( arr[buf[2]] == 6 ); } - // apply_ordering: gather a 1D array into a caller-provided destination (sorted copy) - { - multi::array const arr = {3, 1, 2}; - - multi::array order(arr.extents()); - multi::ordering(arr, order.begin()); - - multi::array sorted(arr.extents()); - multi::apply_ordering(arr, order.begin(), sorted); - - BOOST_TEST( sorted[0] == 1 ); - BOOST_TEST( sorted[1] == 2 ); - BOOST_TEST( sorted[2] == 3 ); - - // source untouched - BOOST_TEST( arr[0] == 3 ); - BOOST_TEST( arr[1] == 1 ); - BOOST_TEST( arr[2] == 2 ); - } - - // apply_ordering: reorder the ROWS of a 2D array by an order computed from a separate key - { - multi::array const mat = { - {3, 30}, - {1, 10}, - {2, 20}, - }; - multi::array const key = {3, 1, 2}; - - multi::array order(key.extents()); - multi::ordering(key, order.begin()); - - multi::array out(mat.extents()); - multi::apply_ordering(mat, order.begin(), out); - - BOOST_TEST( out[0][0] == 1 ); - BOOST_TEST( out[0][1] == 10 ); - BOOST_TEST( out[1][0] == 2 ); - BOOST_TEST( out[1][1] == 20 ); - BOOST_TEST( out[2][0] == 3 ); - BOOST_TEST( out[2][1] == 30 ); - } - return boost::report_errors(); } From cdd14c6443e97b8c3624a2bff1415de9eb0369e3 Mon Sep 17 00:00:00 2001 From: Alfredo Correa Date: Sat, 13 Jun 2026 14:03:45 -0700 Subject: [PATCH 6/7] ordering: pluggable sort backend (thrust::sort via ADL) + execution-policy overloads Stop hard-coding std::sort. Dispatch the sort through ADL with a std::sort fallback (priority_tag), so thrust::sort is used automatically for thrust iterators without ambiguity (a plain 'using std::sort' would tie with the same-signature thrust::sort). Add overloads taking an execution policy as the first argument (std::execution::par, or a thrust policy), disambiguated via a has_extension trait. Seeding stays a qualified std::copy to keep the ADL surface to just the sort. Verified locally against real thrust (CPP backend), std policies, and the std/pointer fallback. Co-Authored-By: Claude Opus 4.8 --- include/boost/multi/algorithms/ordering.hpp | 83 ++++++++++++++++++--- test/ordering.cpp | 20 +++++ 2 files changed, 93 insertions(+), 10 deletions(-) diff --git a/include/boost/multi/algorithms/ordering.hpp b/include/boost/multi/algorithms/ordering.hpp index 34c68120e..377554cbe 100644 --- a/include/boost/multi/algorithms/ordering.hpp +++ b/include/boost/multi/algorithms/ordering.hpp @@ -4,30 +4,73 @@ // `ordering` computes the permutation of indices that would sort a (multi)dimensional // range *without moving the data* (a.k.a. argsort). The result is written into a -// caller-provided output range (no internal allocation; the sort is in-place over the -// indices via `std::sort`). Because it orders by index, it handles non-zero-based -// arrays naturally and never copies/moves the (possibly proxy-referenced) elements. +// caller-provided output range (no internal allocation). Because it orders by index, +// it handles non-zero-based arrays naturally and never copies/moves the (possibly +// proxy-referenced) elements. +// +// The sort backend is not hard-coded to `std::sort`: it dispatches to an ADL-found +// `sort` (e.g. `thrust::sort` when the output iterator is a thrust iterator) and falls +// back to `std::sort` otherwise. Overloads taking an execution policy as the first +// argument forward it (e.g. `std::execution::par`, or a thrust execution policy). +// NOTE: because dispatch is by ADL, `boost::multi` must not declare an iterator-triple +// `sort(first, last, comp)`, which would otherwise be selected for Multi iterators. // #pragma once #ifndef BOOST_MULTI_ALGORITHMS_ORDERING_HPP #define BOOST_MULTI_ALGORITHMS_ORDERING_HPP -#include // for std::sort, std::copy -#include // for std::less +#include // for std::sort, std::copy +#include // for std::less +#include // for std::enable_if_t, std::void_t +#include // for std::forward, std::declval namespace boost::multi { +namespace detail { +// detects a Multi array/subarray (has `.extension()`), used to tell an array argument +// apart from an execution-policy argument in the policy overloads below. +template struct has_extension : std::false_type {}; +template +struct has_extension().extension())>> : std::true_type {}; + +// overload-priority tag: priority_tag<1> is preferred over priority_tag<0>. +template struct priority_tag : priority_tag {}; +template<> struct priority_tag<0> {}; + +// Prefer an ADL-found `sort` (e.g. `thrust::sort` for thrust iterators) and fall back to +// `std::sort`. Crucially, `std::sort` is NOT brought into scope in the ADL overload, so a +// same-signature `thrust::sort` wins unambiguously instead of tying with `std::sort`. +template +auto sort_dispatch(priority_tag<1> /*prefer ADL*/, It first, It last, Compare comp) + -> decltype(sort(first, last, comp)) { + return sort(first, last, comp); // ADL only (no `using std::sort`) +} +template +auto sort_dispatch(priority_tag<0> /*fallback*/, It first, It last, Compare comp) -> void { + std::sort(first, last, comp); +} + +template +auto sort_dispatch(priority_tag<1> /*prefer ADL*/, Policy&& policy, It first, It last, Compare comp) + -> decltype(sort(std::forward(policy), first, last, comp)) { + return sort(std::forward(policy), first, last, comp); // ADL only (e.g. thrust policies) +} +template +auto sort_dispatch(priority_tag<0> /*fallback*/, Policy&& policy, It first, It last, Compare comp) -> void { + std::sort(std::forward(policy), first, last, comp); // std execution policies +} +} // namespace detail + // Writes into [first, ...) the permutation of `arr`'s indices such that // `arr[result[0]], arr[result[1]], ...` is non-decreasing according to `comp`. // `first` must point to a mutable random-access range of at least `arr.size()` elements. // `arr` is not modified. Returns the end of the written range. -template +template::value, int> = 0> // NOLINT(modernize-use-constraints) for C++17 auto ordering(Array1D const& arr, RandomAccessIt first, Compare comp) -> RandomAccessIt { - auto const ext = arr.extension(); - RandomAccessIt const last = std::copy(ext.begin(), ext.end(), first); // seed output with the index values of `arr` + auto const last = std::copy(arr.extension().begin(), arr.extension().end(), first); // seed output with the index values of `arr` - std::sort( - first, last, + detail::sort_dispatch( + detail::priority_tag<1>{}, first, last, [&arr, comp](auto idx1, auto idx2) { return comp(arr[idx1], arr[idx2]); } ); @@ -39,6 +82,26 @@ auto ordering(Array1D const& arr, RandomAccessIt first) -> RandomAccessIt { return ordering(arr, first, std::less<>{}); } +// Policy-aware overloads: `policy` (e.g. `std::execution::par`, or a thrust policy) is +// forwarded to the sort. Disambiguated from the overloads above by requiring the second +// argument to be a Multi array (an execution policy is not). +template::value, int> = 0> // NOLINT(modernize-use-constraints) for C++17 +auto ordering(Policy&& policy, Array1D const& arr, RandomAccessIt first, Compare comp) -> RandomAccessIt { + auto const last = std::copy(arr.extension().begin(), arr.extension().end(), first); + + detail::sort_dispatch( + detail::priority_tag<1>{}, std::forward(policy), first, last, + [&arr, comp](auto idx1, auto idx2) { return comp(arr[idx1], arr[idx2]); } + ); + + return last; +} + +template::value, int> = 0> // NOLINT(modernize-use-constraints) for C++17 +auto ordering(Policy&& policy, Array1D const& arr, RandomAccessIt first) -> RandomAccessIt { + return ordering(std::forward(policy), arr, first, std::less<>{}); +} + // To *apply* the resulting `order` (or any index permutation) there is no need for a // dedicated algorithm here: // - out of place (gather): dst[k] = src[order[k]], i.e. diff --git a/test/ordering.cpp b/test/ordering.cpp index 953d43565..d4d8c3912 100644 --- a/test/ordering.cpp +++ b/test/ordering.cpp @@ -10,6 +10,12 @@ #include // for greater #include // for next +#if defined(__has_include) && !defined(__NVCC__) && !defined(__NVCOMPILER) +#if __has_include() +#include // for std::execution::seq // IWYU pragma: keep +#endif +#endif + namespace multi = boost::multi; auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) @@ -108,5 +114,19 @@ auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugpro BOOST_TEST( arr[buf[2]] == 6 ); } +#ifdef __cpp_lib_execution + // execution-policy overload (also exercises disambiguation from the policy-free overloads) + { + multi::array const arr = {3, 1, 2, 5, 4}; + + multi::array order(arr.extents()); + multi::ordering(std::execution::seq, arr, order.begin()); + + for(multi::index k = 0; k + 1 != order.size(); ++k) { // NOLINT(altera-unroll-loops,altera-id-dependent-backward-branch) + BOOST_TEST( arr[order[k]] <= arr[order[k + 1]] ); + } + } +#endif + return boost::report_errors(); } From f36c55135ff3032216fbcab2cd710cb91f641c39 Mon Sep 17 00:00:00 2001 From: Alfredo Correa Date: Sat, 13 Jun 2026 15:38:51 -0700 Subject: [PATCH 7/7] test: exercise policy overload via a portable user policy, not std::execution std::execution::seq broke nvcc/nvhpc (header guarded out yet __cpp_lib_execution still defined) and IWYU (demanded internal headers). Replace with a tiny user-defined policy whose ADL-found sort exercises the policy overload and the ADL-preferred dispatch path directly, with no /TBB dependency. Co-Authored-By: Claude Opus 4.8 --- test/ordering.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/test/ordering.cpp b/test/ordering.cpp index d4d8c3912..3de18cafc 100644 --- a/test/ordering.cpp +++ b/test/ordering.cpp @@ -7,17 +7,20 @@ #include +#include // for sort #include // for greater #include // for next -#if defined(__has_include) && !defined(__NVCC__) && !defined(__NVCOMPILER) -#if __has_include() -#include // for std::execution::seq // IWYU pragma: keep -#endif -#endif - namespace multi = boost::multi; +namespace { +// a minimal user-defined "execution policy" with an ADL-findable sort, used to exercise +// the policy overload and its ADL dispatch portably (no /TBB, nvcc/nvhpc-safe). +struct seq_policy {}; +template +void sort(seq_policy /*policy*/, It first, It last, Compare comp) { std::sort(first, last, comp); } +} // namespace + auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) // basic ascending order into a caller-provided output buffer { @@ -114,19 +117,17 @@ auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugpro BOOST_TEST( arr[buf[2]] == 6 ); } -#ifdef __cpp_lib_execution - // execution-policy overload (also exercises disambiguation from the policy-free overloads) + // policy overload + ADL sort dispatch (also exercises disambiguation from the policy-free overloads) { multi::array const arr = {3, 1, 2, 5, 4}; multi::array order(arr.extents()); - multi::ordering(std::execution::seq, arr, order.begin()); + multi::ordering(seq_policy{}, arr, order.begin()); for(multi::index k = 0; k + 1 != order.size(); ++k) { // NOLINT(altera-unroll-loops,altera-id-dependent-backward-branch) BOOST_TEST( arr[order[k]] <= arr[order[k + 1]] ); } } -#endif return boost::report_errors(); }