From 943ab8192d799e9c00b7f24b4dffb611b8dbf674 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Mon, 7 Sep 2026 11:33:06 +0200 Subject: [PATCH 001/148] Introduce __is_value_storable_and_comparable_v The vectorized min_element/minmax_element bricks keep copies of the values in a user-defined reduction object and compare those copies, which puts requirements on the value type and on the comparator that not every argument of the algorithms satisfies. Add a trait stating those requirements, so that the callers can check them. The trait is expressed with the concepts it stands for: in C++20 std::semiregular, std::convertible_to and std::predicate are used directly, in C++17 each of them is approximated. The approximations are named after the concepts and kept as close to them as the type traits allow; where they deviate, the comment says how. --- include/oneapi/dpl/pstl/utils.h | 94 ++++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/include/oneapi/dpl/pstl/utils.h b/include/oneapi/dpl/pstl/utils.h index 77b074ee7aa..e2912df4f26 100644 --- a/include/oneapi/dpl/pstl/utils.h +++ b/include/oneapi/dpl/pstl/utils.h @@ -49,7 +49,7 @@ #endif #if _ONEDPL_CPP20_CONCEPTS_PRESENT -# include // for std::equality_comparable_with +# include // for std::equality_comparable_with, std::semiregular, std::convertible_to, std::predicate #endif #include "functional_impl.h" @@ -1104,6 +1104,98 @@ struct __is_type_with_iterator_traits< template static constexpr bool __is_type_with_iterator_traits_v = __is_type_with_iterator_traits<_T>::value; +// The requirements below are named after the concepts they stand for: C++20 uses those concepts directly, while +// C++17 gets an approximation of each of them. +#if _ONEDPL_CPP20_CONCEPTS_PRESENT + +template +inline constexpr bool __convertible_to = std::convertible_to<_From, _To>; + +template +inline constexpr bool __semiregular = std::semiregular<_Tp>; + +template +inline constexpr bool __predicate = std::predicate<_Fp, _Args...>; + +#else + +// std::convertible_to also requires an explicit conversion, which std::is_convertible_v does not check. +template +inline constexpr bool __convertible_to = false; + +template +inline constexpr bool __convertible_to<_From, _To, std::void_t(std::declval<_From>()))>> = + std::is_convertible_v<_From, _To>; + +// std::assignable_from also requires the assignment to return _Tp&, which std::is_assignable_v does not check. +template +inline constexpr bool __assignable_from = false; + +template +inline constexpr bool __assignable_from<_Tp, _Up, std::void_t() = std::declval<_Up>())>> = + std::is_same_v() = std::declval<_Up>()), _Tp&>; + +// std::constructible_from, which includes std::destructible +template +inline constexpr bool __constructible_from = + std::is_nothrow_destructible_v<_Tp> && std::is_constructible_v<_Tp, _Args...>; + +// std::move_constructible +template +inline constexpr bool __move_constructible = __constructible_from<_Tp, _Tp> && __convertible_to<_Tp, _Tp>; + +// std::copy_constructible. Void is rejected up front because the requirement below forms _Tp&, which would be +// ill-formed rather than merely unsatisfied, and std::copy_constructible is not satisfied for void anyway. +template +inline constexpr bool __copy_constructible = false; + +template +inline constexpr bool __copy_constructible<_Tp, std::enable_if_t>> = + __move_constructible<_Tp> && __constructible_from<_Tp, _Tp&> && __convertible_to<_Tp&, _Tp> && + __constructible_from<_Tp, const _Tp&> && __convertible_to && + __constructible_from<_Tp, const _Tp> && __convertible_to; + +// std::movable, less std::swappable: the latter is implied by move construction and move assignment, since +// std::swappable falls back to a move-based implementation, while std::is_swappable_v would additionally reject a +// type with a deleted ADL swap. +template +inline constexpr bool __movable = std::is_object_v<_Tp> && __move_constructible<_Tp> && __assignable_from<_Tp, _Tp>; + +// std::copyable. Void is rejected up front for the same reason as in __copy_constructible above. +template +inline constexpr bool __copyable = false; + +template +inline constexpr bool __copyable<_Tp, std::enable_if_t>> = + __copy_constructible<_Tp> && __movable<_Tp> && __assignable_from<_Tp, _Tp&> && + __assignable_from<_Tp, const _Tp&> && __assignable_from<_Tp, const _Tp>; + +// std::semiregular. std::default_initializable also requires _Tp{} and ::new _Tp to be valid, which +// std::is_default_constructible_v does not check. +template +inline constexpr bool __semiregular = __copyable<_Tp> && std::is_default_constructible_v<_Tp>; + +// std::predicate requires the result to be boolean-testable, which is stronger than being convertible to bool, but +// the difference only shows for types with an unusable operator!. +template +inline constexpr bool __predicate = std::is_invocable_r_v; + +#endif // _ONEDPL_CPP20_CONCEPTS_PRESENT + +// An output iterator reports void as its value type: such a value cannot be stored, and forming const _ValueType& +// for it would be ill-formed rather than merely unsatisfied, so void is rejected up front. +template ::reference, + typename _ValueType = typename std::iterator_traits<_Iterator>::value_type, typename = void> +inline constexpr bool __is_value_storable_and_comparable_v = false; + +template +inline constexpr bool + __is_value_storable_and_comparable_v<_Iterator, _Compare, _ReferenceType, _ValueType, + std::enable_if_t>> = + __semiregular<_ValueType> && __convertible_to<_ReferenceType, _ValueType> && + __predicate<_Compare&, const _ValueType&, const _ValueType&>; + // Storage helper since _Tp may not have a default constructor. template union __lazy_ctor_storage From 15cdc7b12268ed17a37e48493905efdf50bca23d Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Mon, 7 Sep 2026 11:33:07 +0200 Subject: [PATCH 002/148] Add a test for __is_value_storable_and_comparable_v Check the trait and every requirement it is built from, in both directions: a type that satisfies the requirement and a type that does not. The C++17 building blocks of __semiregular are checked as well, since in C++20 they do not exist. --- .../value_storable_and_comparable.pass.cpp | 389 ++++++++++++++++++ 1 file changed, 389 insertions(+) create mode 100644 test/general/implementation_details/value_storable_and_comparable.pass.cpp diff --git a/test/general/implementation_details/value_storable_and_comparable.pass.cpp b/test/general/implementation_details/value_storable_and_comparable.pass.cpp new file mode 100644 index 00000000000..42b052a6bbe --- /dev/null +++ b/test/general/implementation_details/value_storable_and_comparable.pass.cpp @@ -0,0 +1,389 @@ +// -*- C++ -*- +//===------------------------------------------------------===// +// +// Copyright (C) 2025 UXL Foundation Contributors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===------------------------------------------------------===// + +// Compile-time checks for oneapi::dpl::__internal::__is_value_storable_and_comparable_v and for each of the +// requirements it is built from: __convertible_to, __semiregular and __predicate, plus the C++17 building blocks +// of __semiregular (__constructible_from, __move_constructible, __copy_constructible, __assignable_from, __movable, +// __copyable). Every requirement is checked both ways: a type that satisfies it and a type that does not. + +#include "support/test_config.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include "support/utils.h" + +namespace dpl_internal = oneapi::dpl::__internal; + +//----------------------------------------------------------------------------// +// Value types +//----------------------------------------------------------------------------// + +// Satisfies every requirement: default-constructible, copyable, less-than comparable. +struct Regular +{ + int val = 0; + bool + operator<(const Regular& other) const + { + return val < other.val; + } +}; + +// std::default_initializable accepts an explicit default constructor, since T{} stays valid. +struct ExplicitDefaultCtor +{ + int val; + explicit ExplicitDefaultCtor() : val(0) {} + bool + operator<(const ExplicitDefaultCtor& other) const + { + return val < other.val; + } +}; + +struct NoDefaultCtor +{ + int val; + explicit NoDefaultCtor(int v) : val(v) {} + bool + operator<(const NoDefaultCtor& other) const + { + return val < other.val; + } +}; + +struct NoCopyAssign +{ + int val = 0; + NoCopyAssign() = default; + NoCopyAssign(const NoCopyAssign&) = default; + NoCopyAssign& + operator=(const NoCopyAssign&) = delete; + bool + operator<(const NoCopyAssign& other) const + { + return val < other.val; + } +}; + +// std::is_assignable_v is satisfied, but the assignment does not return VoidAssign&. +struct VoidAssign +{ + int val = 0; + void + operator=(const VoidAssign& other) + { + val = other.val; + } + bool + operator<(const VoidAssign& other) const + { + return val < other.val; + } +}; + +// std::destructible, and hence __constructible_from, requires the destructor to be noexcept. +struct ThrowingDtor +{ + int val = 0; + ~ThrowingDtor() noexcept(false) {} + bool + operator<(const ThrowingDtor& other) const + { + return val < other.val; + } +}; + +struct MoveOnly +{ + int val = 0; + MoveOnly() = default; + MoveOnly(MoveOnly&&) = default; + MoveOnly& + operator=(MoveOnly&&) = default; + MoveOnly(const MoveOnly&) = delete; + MoveOnly& + operator=(const MoveOnly&) = delete; + bool + operator<(const MoveOnly& other) const + { + return val < other.val; + } +}; + +//----------------------------------------------------------------------------// +// __convertible_to +//----------------------------------------------------------------------------// + +struct ExplicitFromInt +{ + explicit ExplicitFromInt(int) {} +}; + +// A destination whose only constructor taking ImplicitSource is deleted and explicit: copy-initialization ignores it +// and picks the conversion operator, so std::is_convertible_v is satisfied, while static_cast selects the deleted +// constructor. This is the difference std::convertible_to catches and std::is_convertible_v does not. +struct ExplicitlyNotConvertible; + +struct ImplicitSource +{ + operator ExplicitlyNotConvertible() const; +}; + +struct ExplicitlyNotConvertible +{ + ExplicitlyNotConvertible() = default; + explicit ExplicitlyNotConvertible(ImplicitSource) = delete; +}; + +static_assert(dpl_internal::__convertible_to); +static_assert(dpl_internal::__convertible_to); +static_assert(dpl_internal::__convertible_to); +static_assert(dpl_internal::__convertible_to); +static_assert(dpl_internal::__convertible_to, std::pair>); + +static_assert(!dpl_internal::__convertible_to); +static_assert(!dpl_internal::__convertible_to); +static_assert(!dpl_internal::__convertible_to); +static_assert(!dpl_internal::__convertible_to); +static_assert(std::is_convertible_v); +static_assert(!dpl_internal::__convertible_to); + +//----------------------------------------------------------------------------// +// __semiregular +//----------------------------------------------------------------------------// + +static_assert(dpl_internal::__semiregular); +static_assert(dpl_internal::__semiregular); +static_assert(dpl_internal::__semiregular); +static_assert(dpl_internal::__semiregular); +static_assert(dpl_internal::__semiregular>); + +static_assert(!dpl_internal::__semiregular); +static_assert(!dpl_internal::__semiregular); +static_assert(!dpl_internal::__semiregular); +static_assert(!dpl_internal::__semiregular); +static_assert(!dpl_internal::__semiregular); +static_assert(!dpl_internal::__semiregular); +// Output iterators report void as their value type, so void must be rejected rather than rejecting the program. +static_assert(!dpl_internal::__semiregular); +static_assert(!dpl_internal::__semiregular); + +//----------------------------------------------------------------------------// +// __predicate +//----------------------------------------------------------------------------// + +struct NotBool +{ +}; + +struct IntResultLess +{ + int + operator()(const int& lhs, const int& rhs) const + { + return lhs < rhs; + } +}; + +struct NotBoolResultLess +{ + NotBool + operator()(const int&, const int&) const + { + return NotBool{}; + } +}; + +// Requires modifiable arguments, so it cannot be called on const values. +struct MutableRefLess +{ + bool + operator()(int& lhs, int& rhs) const + { + return lhs < rhs; + } +}; + +// Callable on an rvalue only, while the requirement is stated for _Compare&. +struct RvalueOnlyLess +{ + bool + operator()(const int&, const int&) && + { + return false; + } +}; + +struct UnaryLess +{ + bool + operator()(const int&) const + { + return false; + } +}; + +// Not copyable: the requirement is stated for _Compare&, so it must not ask for a copy. +struct MoveOnlyLess +{ + MoveOnlyLess() = default; + MoveOnlyLess(MoveOnlyLess&&) = default; + MoveOnlyLess(const MoveOnlyLess&) = delete; + bool + operator()(const int& lhs, const int& rhs) const + { + return lhs < rhs; + } +}; + +static_assert(dpl_internal::__predicate&, const int&, const int&>); +static_assert(dpl_internal::__predicate&, const int&, const int&>); +static_assert(dpl_internal::__predicate); +static_assert(dpl_internal::__predicate); +static_assert(dpl_internal::__predicate&, const Regular&, const Regular&>); + +static_assert(!dpl_internal::__predicate); +static_assert(!dpl_internal::__predicate); +static_assert(!dpl_internal::__predicate); +static_assert(!dpl_internal::__predicate); +static_assert(!dpl_internal::__predicate); +static_assert(!dpl_internal::__predicate&, const Regular&, const Regular&>); + +//----------------------------------------------------------------------------// +// C++17 building blocks of __semiregular. In C++20 the standard concepts are used directly, so these helpers only +// exist in the C++17 branch. +//----------------------------------------------------------------------------// + +#if !_ONEDPL_CPP20_CONCEPTS_PRESENT + +static_assert(dpl_internal::__constructible_from); +static_assert(dpl_internal::__constructible_from); +static_assert(dpl_internal::__constructible_from); +static_assert(!dpl_internal::__constructible_from); +static_assert(!dpl_internal::__constructible_from); +static_assert(!dpl_internal::__constructible_from); + +static_assert(dpl_internal::__assignable_from); +static_assert(dpl_internal::__assignable_from); +static_assert(dpl_internal::__assignable_from); +static_assert(!dpl_internal::__assignable_from); +static_assert(std::is_assignable_v); +static_assert(!dpl_internal::__assignable_from); +static_assert(!dpl_internal::__assignable_from); + +static_assert(dpl_internal::__move_constructible); +static_assert(dpl_internal::__move_constructible); +static_assert(!dpl_internal::__move_constructible); +static_assert(!dpl_internal::__move_constructible); + +static_assert(dpl_internal::__copy_constructible); +static_assert(dpl_internal::__copy_constructible); +static_assert(!dpl_internal::__copy_constructible); +static_assert(!dpl_internal::__copy_constructible); + +static_assert(dpl_internal::__movable); +static_assert(dpl_internal::__movable); +static_assert(!dpl_internal::__movable); +static_assert(!dpl_internal::__movable); + +static_assert(dpl_internal::__copyable); +static_assert(dpl_internal::__copyable); +static_assert(!dpl_internal::__copyable); +static_assert(!dpl_internal::__copyable); +static_assert(!dpl_internal::__copyable); + +// Each building block has to yield false for void instead of failing to compile, since forming void& is ill-formed +// rather than merely unsatisfied. +static_assert(!dpl_internal::__constructible_from); +static_assert(!dpl_internal::__assignable_from); +static_assert(!dpl_internal::__move_constructible); +static_assert(!dpl_internal::__copy_constructible); +static_assert(!dpl_internal::__movable); +static_assert(!dpl_internal::__copyable); +static_assert(!dpl_internal::__copy_constructible); +static_assert(!dpl_internal::__copyable); + +#endif // !_ONEDPL_CPP20_CONCEPTS_PRESENT + +//----------------------------------------------------------------------------// +// __is_value_storable_and_comparable_v +//----------------------------------------------------------------------------// + +// An iterator whose reference type is not convertible to its value type. +struct OpaqueRef +{ +}; + +template +struct FakeIterator +{ + using iterator_category = std::random_access_iterator_tag; + using value_type = _ValueType; + using difference_type = std::ptrdiff_t; + using pointer = void; + using reference = _ReferenceType; + + reference + operator*() const; +}; + +// Accepted: the value type is storable and the comparator is callable on const values. +static_assert(dpl_internal::__is_value_storable_and_comparable_v>); +static_assert(dpl_internal::__is_value_storable_and_comparable_v>); +static_assert(dpl_internal::__is_value_storable_and_comparable_v::iterator, std::less>); +static_assert(dpl_internal::__is_value_storable_and_comparable_v::const_iterator, std::less<>>); +static_assert(dpl_internal::__is_value_storable_and_comparable_v>); +static_assert( + dpl_internal::__is_value_storable_and_comparable_v>); +static_assert(dpl_internal::__is_value_storable_and_comparable_v); +static_assert(dpl_internal::__is_value_storable_and_comparable_v); +static_assert(dpl_internal::__is_value_storable_and_comparable_v); +// The comparator max_element passes down to the min_element brick. +static_assert( + dpl_internal::__is_value_storable_and_comparable_v>>); +// A proxy reference is fine as long as it converts to the value type. +static_assert(dpl_internal::__is_value_storable_and_comparable_v::iterator, std::less>); +static_assert(dpl_internal::__is_value_storable_and_comparable_v, std::less>); +static_assert(dpl_internal::__is_value_storable_and_comparable_v, std::pair>, + std::less>>); + +// Rejected because of the value type. +static_assert(!dpl_internal::__is_value_storable_and_comparable_v>); +static_assert(!dpl_internal::__is_value_storable_and_comparable_v>); +static_assert(!dpl_internal::__is_value_storable_and_comparable_v>); +static_assert(!dpl_internal::__is_value_storable_and_comparable_v>); +static_assert(!dpl_internal::__is_value_storable_and_comparable_v>); + +// Rejected because the reference type does not convert to the value type. +static_assert(!dpl_internal::__is_value_storable_and_comparable_v, std::less>); + +// Rejected because an output iterator reports void as its value type. +static_assert(!dpl_internal::__is_value_storable_and_comparable_v>, + std::less>); + +// Rejected because of the comparator. +static_assert(!dpl_internal::__is_value_storable_and_comparable_v); +static_assert(!dpl_internal::__is_value_storable_and_comparable_v); +static_assert(!dpl_internal::__is_value_storable_and_comparable_v); +static_assert(!dpl_internal::__is_value_storable_and_comparable_v); +static_assert(!dpl_internal::__is_value_storable_and_comparable_v>); + +int +main() +{ + return TestUtils::done(); +} From a4393b8a1088301f4622558d57312761938d0eb2 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Mon, 7 Sep 2026 11:35:45 +0200 Subject: [PATCH 003/148] Use std::addressof to take the address of the comparator in the SIMD bricks __simd_min_element and __simd_minmax_element pass a pointer to the comparator into the reduction object, and the comparator is a user-provided type that may overload the unary operator&. --- include/oneapi/dpl/pstl/unseq_backend_simd.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/oneapi/dpl/pstl/unseq_backend_simd.h b/include/oneapi/dpl/pstl/unseq_backend_simd.h index 9280c39c337..133e58beb6a 100644 --- a/include/oneapi/dpl/pstl/unseq_backend_simd.h +++ b/include/oneapi/dpl/pstl/unseq_backend_simd.h @@ -17,6 +17,7 @@ #define _ONEDPL_UNSEQ_BACKEND_SIMD_H #include +#include // for std::addressof #include "utils.h" @@ -652,7 +653,7 @@ __simd_min_element(_ForwardIterator __first, _Size __n, _Compare __comp) noexcep } }; - _ComplexType __init{*__first, &__comp}; + _ComplexType __init{*__first, std::addressof(__comp)}; _ONEDPL_PRAGMA_DECLARE_REDUCTION(__min_func, _ComplexType) @@ -730,7 +731,7 @@ __simd_minmax_element(_ForwardIterator __first, _Size __n, _Compare __comp) noex } }; - _ComplexType __init{*__first, *__first, &__comp}; + _ComplexType __init{*__first, *__first, std::addressof(__comp)}; _ONEDPL_PRAGMA_DECLARE_REDUCTION(__min_func, _ComplexType); From 8fa332448582d0508260cde66e42b860613cabad Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Mon, 7 Sep 2026 11:35:45 +0200 Subject: [PATCH 004/148] Take the vectorized min_element/minmax_element brick only when it is applicable __brick_min_element and __brick_minmax_element called __simd_min_element and __simd_minmax_element for every value type and comparator, so an argument that does not satisfy the requirements of the reduction object used there failed to compile instead of falling back to the serial implementation. Guard both calls with __is_value_storable_and_comparable_v and state the same requirement as a static_assert in the bricks themselves. --- include/oneapi/dpl/pstl/algorithm_impl.h | 14 ++++++++------ include/oneapi/dpl/pstl/unseq_backend_simd.h | 18 +++++++++++++++--- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/include/oneapi/dpl/pstl/algorithm_impl.h b/include/oneapi/dpl/pstl/algorithm_impl.h index bcd47f94f7a..477eb59afeb 100644 --- a/include/oneapi/dpl/pstl/algorithm_impl.h +++ b/include/oneapi/dpl/pstl/algorithm_impl.h @@ -4876,10 +4876,11 @@ __brick_min_element(_RandomAccessIterator __first, _RandomAccessIterator __last, /* __is_vector = */ ::std::true_type) noexcept { #if _ONEDPL_UDR_PRESENT // _PSTL_UDR_PRESENT - return __unseq_backend::__simd_min_element(__first, __last - __first, __comp); -#else - return ::std::min_element(__first, __last, __comp); + if constexpr (__is_value_storable_and_comparable_v<_RandomAccessIterator, _Compare>) + return __unseq_backend::__simd_min_element(__first, __last - __first, __comp); #endif + + return std::min_element(__first, __last, __comp); } template @@ -4943,10 +4944,11 @@ __brick_minmax_element(_RandomAccessIterator __first, _RandomAccessIterator __la /* __is_vector = */ ::std::true_type) noexcept { #if _ONEDPL_UDR_PRESENT // _PSTL_UDR_PRESENT - return __unseq_backend::__simd_minmax_element(__first, __last - __first, __comp); -#else - return ::std::minmax_element(__first, __last, __comp); + if constexpr (__is_value_storable_and_comparable_v<_RandomAccessIterator, _Compare>) + return __unseq_backend::__simd_minmax_element(__first, __last - __first, __comp); #endif + + return std::minmax_element(__first, __last, __comp); } template diff --git a/include/oneapi/dpl/pstl/unseq_backend_simd.h b/include/oneapi/dpl/pstl/unseq_backend_simd.h index 133e58beb6a..f874238deed 100644 --- a/include/oneapi/dpl/pstl/unseq_backend_simd.h +++ b/include/oneapi/dpl/pstl/unseq_backend_simd.h @@ -613,12 +613,18 @@ __simd_scan(_InputIterator __first, _Size __n, _OutputIterator __result, _UnaryO return ::std::make_pair(__result + __n, __init_.__value); } -// [restriction] - ::std::iterator_traits<_ForwardIterator>::value_type should be DefaultConstructible. +// The implementation keeps copies of the values in the reduction object and compares those copies, so the value +// type has to be usable in a user-defined reduction and the comparator has to be applicable to the copies: +// __internal::__is_value_storable_and_comparable_v is the requirement checked by the callers. // complexity [violation] - We will have at most (__n-1 + number_of_lanes) comparisons instead of at most __n-1. template _ForwardIterator __simd_min_element(_ForwardIterator __first, _Size __n, _Compare __comp) noexcept { + static_assert(__internal::__is_value_storable_and_comparable_v<_ForwardIterator, _Compare>, + "The value type of the iterator must be storable in the reduction object and __comp must be " + "a predicate over objects of that type"); + if (__n == 0) { return __first; @@ -671,12 +677,18 @@ __simd_min_element(_ForwardIterator __first, _Size __n, _Compare __comp) noexcep return __first + __init.__min_ind; } -// [restriction] - ::std::iterator_traits<_ForwardIterator>::value_type should be DefaultConstructible. +// The implementation keeps copies of the values in the reduction object and compares those copies, so the value +// type has to be usable in a user-defined reduction and the comparator has to be applicable to the copies: +// __internal::__is_value_storable_and_comparable_v is the requirement checked by the callers. // complexity [violation] - We will have at most (2*(__n-1) + 4*number_of_lanes) comparisons instead of at most [1.5*(__n-1)]. template -::std::pair<_ForwardIterator, _ForwardIterator> +std::pair<_ForwardIterator, _ForwardIterator> __simd_minmax_element(_ForwardIterator __first, _Size __n, _Compare __comp) noexcept { + static_assert(__internal::__is_value_storable_and_comparable_v<_ForwardIterator, _Compare>, + "The value type of the iterator must be storable in the reduction object and __comp must be " + "a predicate over objects of that type"); + if (__n == 0) { return ::std::make_pair(__first, __first); From 3f1dfcd470b1b666fd8785278b311630c7d5b21c Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Mon, 7 Sep 2026 11:33:19 +0200 Subject: [PATCH 005/148] Extend minmax_element test coverage with value types of the serial fallback Check the value types that decide which code path is taken: one that is default-constructible only through an explicit default constructor, which the vectorized path still has to accept, and three that it has to reject, each of them violating one of the requirements - default construction, copy assignment and copy construction. All four fail to compile in this test unless the code path is chosen by the value type. --- .../alg.min.max/minmax_element.pass.cpp | 111 +++++++++++++++++- 1 file changed, 108 insertions(+), 3 deletions(-) diff --git a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp index be6fd5e140b..05959f039db 100644 --- a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp +++ b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp @@ -20,9 +20,10 @@ #include "support/utils.h" -#include #include #include +#include +#include #if !defined(_PSTL_TEST_MIN_ELEMENT) && !defined(_PSTL_TEST_MAX_ELEMENT) &&\ !defined(_PSTL_TEST_MINMAX_ELEMENT) && !_PSTL_ICPX_TEST_MINMAX_ELEMENT_PASS_BROKEN @@ -231,6 +232,99 @@ struct OnlyLessCompare } }; +// The value type is default-constructible, but only through an explicit default constructor: +// the vector code path is still applicable for it, because the reduction object initializes its +// members with direct-list-initialization. +struct ExplicitDefaultCtorCompare +{ + std::int32_t val; + explicit ExplicitDefaultCtorCompare() : val(0) {} + ExplicitDefaultCtorCompare(std::int32_t val_) : val(val_) {} + bool + operator<(const ExplicitDefaultCtorCompare& other) const + { + return val < other.val; + } +}; + +// The value type is not default-constructible, so it cannot be used in a user-defined reduction +// and the vector code path must not be selected for it. The same holds for NoCopyAssignCompare and +// MoveOnlyCompare below: each of them violates one of the requirements the vector code path puts on the +// value type, so each of them fails to compile once that path is selected. +struct NoDefaultCtorCompare +{ + std::int32_t val; + explicit NoDefaultCtorCompare(std::int32_t val_) : val(val_) {} + bool + operator<(const NoDefaultCtorCompare& other) const + { + return val < other.val; + } +}; + +// The value type is not copy-assignable, so it cannot be used in a user-defined reduction +// and the vector code path must not be selected for it. +struct NoCopyAssignCompare +{ + std::int32_t val; + NoCopyAssignCompare() : val(0) {} + NoCopyAssignCompare(std::int32_t val_) : val(val_) {} + NoCopyAssignCompare(const NoCopyAssignCompare&) = default; + NoCopyAssignCompare& + operator=(const NoCopyAssignCompare&) = delete; + bool + operator<(const NoCopyAssignCompare& other) const + { + return val < other.val; + } +}; + +// The value type is not copy-constructible, so it cannot be used in a user-defined reduction +// and the vector code path must not be selected for it. +struct MoveOnlyCompare +{ + std::int32_t val; + MoveOnlyCompare() : val(0) {} + MoveOnlyCompare(std::int32_t val_) : val(val_) {} + MoveOnlyCompare(MoveOnlyCompare&&) = default; + MoveOnlyCompare& + operator=(MoveOnlyCompare&&) = default; + MoveOnlyCompare(const MoveOnlyCompare&) = delete; + MoveOnlyCompare& + operator=(const MoveOnlyCompare&) = delete; + bool + operator<(const MoveOnlyCompare& other) const + { + return val < other.val; + } +}; + +// The sequence is built in place because the value types checked here either do not satisfy the requirements of +// TestUtils::Sequence (which default-constructs and assigns its elements) or are not trivially copyable, and thus +// cannot be checked with device policies. +template +static void +test_by_type_host_policies(::std::size_t n) +{ + ::std::vector data; + data.reserve(n); + for (::std::size_t i = 0; i < n; ++i) + data.emplace_back(std::int32_t(TestUtils::HashBits(i, 30))); + +#ifdef _PSTL_TEST_MIN_ELEMENT + invoke_on_all_host_policies()(check_minelement(), data.begin(), data.end()); + invoke_on_all_host_policies()(check_minelement_predicate(), data.begin(), data.end()); +#endif +#ifdef _PSTL_TEST_MAX_ELEMENT + invoke_on_all_host_policies()(check_maxelement(), data.begin(), data.end()); + invoke_on_all_host_policies()(check_maxelement_predicate(), data.begin(), data.end()); +#endif +#ifdef _PSTL_TEST_MINMAX_ELEMENT + invoke_on_all_host_policies()(check_minmaxelement(), data.begin(), data.end()); + invoke_on_all_host_policies()(check_minmaxelement_predicate(), data.begin(), data.end()); +#endif +} + template struct test_non_const_max_element { @@ -268,9 +362,10 @@ int main() { using TestUtils::float64_t; - const ::std::size_t N = 100000; + const std::size_t N = 100000; + const std::size_t NSmall = 10; - for (::std::size_t n = 0; n < N; n = n < 16 ? n + 1 : size_t(3.14159 * n)) + for (std::size_t n = 0; n < N; n = n < 16 ? n + 1 : size_t(3.14159 * n)) { #if !ONEDPL_FPGA_DEVICE test_by_type(n); @@ -279,6 +374,16 @@ main() test_by_type(n); } + // This value type is accepted by the vector code path, exactly like OnlyLessCompare above: it differs from it only + // by an explicit default constructor, which the path has to accept at compile time, so one size is enough. + test_by_type(NSmall); + + // These value types are rejected by the vector code path, so the point of checking them is that the call compiles + // and falls back to the serial implementation. That does not depend on the sequence size, so one size is enough. + test_by_type_host_policies(NSmall); + test_by_type_host_policies(NSmall); + test_by_type_host_policies(NSmall); + #ifdef _PSTL_TEST_MIN_ELEMENT test_algo_basic_single(run_for_rnd_fw>()); #endif From e45255f631eb68d7737472db83f9577e2a5a8e77 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Mon, 7 Sep 2026 11:44:24 +0200 Subject: [PATCH 006/148] Apply GitHUB clang format --- include/oneapi/dpl/pstl/utils.h | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/include/oneapi/dpl/pstl/utils.h b/include/oneapi/dpl/pstl/utils.h index e2912df4f26..84748414060 100644 --- a/include/oneapi/dpl/pstl/utils.h +++ b/include/oneapi/dpl/pstl/utils.h @@ -1167,8 +1167,8 @@ inline constexpr bool __copyable = false; template inline constexpr bool __copyable<_Tp, std::enable_if_t>> = - __copy_constructible<_Tp> && __movable<_Tp> && __assignable_from<_Tp, _Tp&> && - __assignable_from<_Tp, const _Tp&> && __assignable_from<_Tp, const _Tp>; + __copy_constructible<_Tp> && __movable<_Tp> && __assignable_from<_Tp, _Tp&> && __assignable_from<_Tp, const _Tp&> && + __assignable_from<_Tp, const _Tp>; // std::semiregular. std::default_initializable also requires _Tp{} and ::new _Tp to be valid, which // std::is_default_constructible_v does not check. @@ -1190,11 +1190,10 @@ template -inline constexpr bool - __is_value_storable_and_comparable_v<_Iterator, _Compare, _ReferenceType, _ValueType, - std::enable_if_t>> = - __semiregular<_ValueType> && __convertible_to<_ReferenceType, _ValueType> && - __predicate<_Compare&, const _ValueType&, const _ValueType&>; +inline constexpr bool __is_value_storable_and_comparable_v<_Iterator, _Compare, _ReferenceType, _ValueType, + std::enable_if_t>> = + __semiregular<_ValueType> && __convertible_to<_ReferenceType, _ValueType> && + __predicate<_Compare&, const _ValueType&, const _ValueType&>; // Storage helper since _Tp may not have a default constructor. template From 1c9cd7a3dda8f424ea564efd69da0357bc6366e9 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 8 Sep 2026 10:22:27 +0200 Subject: [PATCH 007/148] Renames inline constexpr bool __predicate -> __predicate_v --- include/oneapi/dpl/pstl/utils.h | 6 ++-- .../value_storable_and_comparable.pass.cpp | 28 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/include/oneapi/dpl/pstl/utils.h b/include/oneapi/dpl/pstl/utils.h index 84748414060..51a568ab28a 100644 --- a/include/oneapi/dpl/pstl/utils.h +++ b/include/oneapi/dpl/pstl/utils.h @@ -1115,7 +1115,7 @@ template inline constexpr bool __semiregular = std::semiregular<_Tp>; template -inline constexpr bool __predicate = std::predicate<_Fp, _Args...>; +inline constexpr bool __predicate_v = std::predicate<_Fp, _Args...>; #else @@ -1178,7 +1178,7 @@ inline constexpr bool __semiregular = __copyable<_Tp> && std::is_default_constru // std::predicate requires the result to be boolean-testable, which is stronger than being convertible to bool, but // the difference only shows for types with an unusable operator!. template -inline constexpr bool __predicate = std::is_invocable_r_v; +inline constexpr bool __predicate_v = std::is_invocable_r_v; #endif // _ONEDPL_CPP20_CONCEPTS_PRESENT @@ -1193,7 +1193,7 @@ template >> = __semiregular<_ValueType> && __convertible_to<_ReferenceType, _ValueType> && - __predicate<_Compare&, const _ValueType&, const _ValueType&>; + __predicate_v<_Compare&, const _ValueType&, const _ValueType&>; // Storage helper since _Tp may not have a default constructor. template diff --git a/test/general/implementation_details/value_storable_and_comparable.pass.cpp b/test/general/implementation_details/value_storable_and_comparable.pass.cpp index 42b052a6bbe..77743dd6758 100644 --- a/test/general/implementation_details/value_storable_and_comparable.pass.cpp +++ b/test/general/implementation_details/value_storable_and_comparable.pass.cpp @@ -8,7 +8,7 @@ //===------------------------------------------------------===// // Compile-time checks for oneapi::dpl::__internal::__is_value_storable_and_comparable_v and for each of the -// requirements it is built from: __convertible_to, __semiregular and __predicate, plus the C++17 building blocks +// requirements it is built from: __convertible_to, __semiregular and __predicate_v, plus the C++17 building blocks // of __semiregular (__constructible_from, __move_constructible, __copy_constructible, __assignable_from, __movable, // __copyable). Every requirement is checked both ways: a type that satisfies it and a type that does not. @@ -183,7 +183,7 @@ static_assert(!dpl_internal::__semiregular); static_assert(!dpl_internal::__semiregular); //----------------------------------------------------------------------------// -// __predicate +// __predicate_v //----------------------------------------------------------------------------// struct NotBool @@ -250,18 +250,18 @@ struct MoveOnlyLess } }; -static_assert(dpl_internal::__predicate&, const int&, const int&>); -static_assert(dpl_internal::__predicate&, const int&, const int&>); -static_assert(dpl_internal::__predicate); -static_assert(dpl_internal::__predicate); -static_assert(dpl_internal::__predicate&, const Regular&, const Regular&>); - -static_assert(!dpl_internal::__predicate); -static_assert(!dpl_internal::__predicate); -static_assert(!dpl_internal::__predicate); -static_assert(!dpl_internal::__predicate); -static_assert(!dpl_internal::__predicate); -static_assert(!dpl_internal::__predicate&, const Regular&, const Regular&>); +static_assert(dpl_internal::__predicate_v&, const int&, const int&>); +static_assert(dpl_internal::__predicate_v&, const int&, const int&>); +static_assert(dpl_internal::__predicate_v); +static_assert(dpl_internal::__predicate_v); +static_assert(dpl_internal::__predicate_v&, const Regular&, const Regular&>); + +static_assert(!dpl_internal::__predicate_v); +static_assert(!dpl_internal::__predicate_v); +static_assert(!dpl_internal::__predicate_v); +static_assert(!dpl_internal::__predicate_v); +static_assert(!dpl_internal::__predicate_v); +static_assert(!dpl_internal::__predicate_v&, const Regular&, const Regular&>); //----------------------------------------------------------------------------// // C++17 building blocks of __semiregular. In C++20 the standard concepts are used directly, so these helpers only From 77856020b52cf869b5082e7cb0aa59e9abcc673b Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 8 Sep 2026 10:24:01 +0200 Subject: [PATCH 008/148] Renames inline constexpr bool __convertible_to -> __convertible_to_v --- include/oneapi/dpl/pstl/utils.h | 16 ++++++------- .../value_storable_and_comparable.pass.cpp | 24 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/include/oneapi/dpl/pstl/utils.h b/include/oneapi/dpl/pstl/utils.h index 51a568ab28a..0333430f1cd 100644 --- a/include/oneapi/dpl/pstl/utils.h +++ b/include/oneapi/dpl/pstl/utils.h @@ -1109,7 +1109,7 @@ static constexpr bool __is_type_with_iterator_traits_v = __is_type_with_iterator #if _ONEDPL_CPP20_CONCEPTS_PRESENT template -inline constexpr bool __convertible_to = std::convertible_to<_From, _To>; +inline constexpr bool __convertible_to_v = std::convertible_to<_From, _To>; template inline constexpr bool __semiregular = std::semiregular<_Tp>; @@ -1121,10 +1121,10 @@ inline constexpr bool __predicate_v = std::predicate<_Fp, _Args...>; // std::convertible_to also requires an explicit conversion, which std::is_convertible_v does not check. template -inline constexpr bool __convertible_to = false; +inline constexpr bool __convertible_to_v = false; template -inline constexpr bool __convertible_to<_From, _To, std::void_t(std::declval<_From>()))>> = +inline constexpr bool __convertible_to_v<_From, _To, std::void_t(std::declval<_From>()))>> = std::is_convertible_v<_From, _To>; // std::assignable_from also requires the assignment to return _Tp&, which std::is_assignable_v does not check. @@ -1142,7 +1142,7 @@ inline constexpr bool __constructible_from = // std::move_constructible template -inline constexpr bool __move_constructible = __constructible_from<_Tp, _Tp> && __convertible_to<_Tp, _Tp>; +inline constexpr bool __move_constructible = __constructible_from<_Tp, _Tp> && __convertible_to_v<_Tp, _Tp>; // std::copy_constructible. Void is rejected up front because the requirement below forms _Tp&, which would be // ill-formed rather than merely unsatisfied, and std::copy_constructible is not satisfied for void anyway. @@ -1151,9 +1151,9 @@ inline constexpr bool __copy_constructible = false; template inline constexpr bool __copy_constructible<_Tp, std::enable_if_t>> = - __move_constructible<_Tp> && __constructible_from<_Tp, _Tp&> && __convertible_to<_Tp&, _Tp> && - __constructible_from<_Tp, const _Tp&> && __convertible_to && - __constructible_from<_Tp, const _Tp> && __convertible_to; + __move_constructible<_Tp> && __constructible_from<_Tp, _Tp&> && __convertible_to_v<_Tp&, _Tp> && + __constructible_from<_Tp, const _Tp&> && __convertible_to_v && + __constructible_from<_Tp, const _Tp> && __convertible_to_v; // std::movable, less std::swappable: the latter is implied by move construction and move assignment, since // std::swappable falls back to a move-based implementation, while std::is_swappable_v would additionally reject a @@ -1192,7 +1192,7 @@ inline constexpr bool __is_value_storable_and_comparable_v = false; template inline constexpr bool __is_value_storable_and_comparable_v<_Iterator, _Compare, _ReferenceType, _ValueType, std::enable_if_t>> = - __semiregular<_ValueType> && __convertible_to<_ReferenceType, _ValueType> && + __semiregular<_ValueType> && __convertible_to_v<_ReferenceType, _ValueType> && __predicate_v<_Compare&, const _ValueType&, const _ValueType&>; // Storage helper since _Tp may not have a default constructor. diff --git a/test/general/implementation_details/value_storable_and_comparable.pass.cpp b/test/general/implementation_details/value_storable_and_comparable.pass.cpp index 77743dd6758..1dca001871d 100644 --- a/test/general/implementation_details/value_storable_and_comparable.pass.cpp +++ b/test/general/implementation_details/value_storable_and_comparable.pass.cpp @@ -8,7 +8,7 @@ //===------------------------------------------------------===// // Compile-time checks for oneapi::dpl::__internal::__is_value_storable_and_comparable_v and for each of the -// requirements it is built from: __convertible_to, __semiregular and __predicate_v, plus the C++17 building blocks +// requirements it is built from: __convertible_to_v, __semiregular and __predicate_v, plus the C++17 building blocks // of __semiregular (__constructible_from, __move_constructible, __copy_constructible, __assignable_from, __movable, // __copyable). Every requirement is checked both ways: a type that satisfies it and a type that does not. @@ -125,7 +125,7 @@ struct MoveOnly }; //----------------------------------------------------------------------------// -// __convertible_to +// __convertible_to_v //----------------------------------------------------------------------------// struct ExplicitFromInt @@ -149,18 +149,18 @@ struct ExplicitlyNotConvertible explicit ExplicitlyNotConvertible(ImplicitSource) = delete; }; -static_assert(dpl_internal::__convertible_to); -static_assert(dpl_internal::__convertible_to); -static_assert(dpl_internal::__convertible_to); -static_assert(dpl_internal::__convertible_to); -static_assert(dpl_internal::__convertible_to, std::pair>); +static_assert(dpl_internal::__convertible_to_v); +static_assert(dpl_internal::__convertible_to_v); +static_assert(dpl_internal::__convertible_to_v); +static_assert(dpl_internal::__convertible_to_v); +static_assert(dpl_internal::__convertible_to_v, std::pair>); -static_assert(!dpl_internal::__convertible_to); -static_assert(!dpl_internal::__convertible_to); -static_assert(!dpl_internal::__convertible_to); -static_assert(!dpl_internal::__convertible_to); +static_assert(!dpl_internal::__convertible_to_v); +static_assert(!dpl_internal::__convertible_to_v); +static_assert(!dpl_internal::__convertible_to_v); +static_assert(!dpl_internal::__convertible_to_v); static_assert(std::is_convertible_v); -static_assert(!dpl_internal::__convertible_to); +static_assert(!dpl_internal::__convertible_to_v); //----------------------------------------------------------------------------// // __semiregular From e5765eedcf0a035027d9f4dcce4ea455dbeea567 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 8 Sep 2026 10:25:39 +0200 Subject: [PATCH 009/148] Renames inline constexpr bool __semiregular -> __semiregular_v --- include/oneapi/dpl/pstl/utils.h | 6 ++-- .../value_storable_and_comparable.pass.cpp | 36 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/include/oneapi/dpl/pstl/utils.h b/include/oneapi/dpl/pstl/utils.h index 0333430f1cd..8b96d940e6c 100644 --- a/include/oneapi/dpl/pstl/utils.h +++ b/include/oneapi/dpl/pstl/utils.h @@ -1112,7 +1112,7 @@ template inline constexpr bool __convertible_to_v = std::convertible_to<_From, _To>; template -inline constexpr bool __semiregular = std::semiregular<_Tp>; +inline constexpr bool __semiregular_v = std::semiregular<_Tp>; template inline constexpr bool __predicate_v = std::predicate<_Fp, _Args...>; @@ -1173,7 +1173,7 @@ inline constexpr bool __copyable<_Tp, std::enable_if_t>> = // std::semiregular. std::default_initializable also requires _Tp{} and ::new _Tp to be valid, which // std::is_default_constructible_v does not check. template -inline constexpr bool __semiregular = __copyable<_Tp> && std::is_default_constructible_v<_Tp>; +inline constexpr bool __semiregular_v = __copyable<_Tp> && std::is_default_constructible_v<_Tp>; // std::predicate requires the result to be boolean-testable, which is stronger than being convertible to bool, but // the difference only shows for types with an unusable operator!. @@ -1192,7 +1192,7 @@ inline constexpr bool __is_value_storable_and_comparable_v = false; template inline constexpr bool __is_value_storable_and_comparable_v<_Iterator, _Compare, _ReferenceType, _ValueType, std::enable_if_t>> = - __semiregular<_ValueType> && __convertible_to_v<_ReferenceType, _ValueType> && + __semiregular_v<_ValueType> && __convertible_to_v<_ReferenceType, _ValueType> && __predicate_v<_Compare&, const _ValueType&, const _ValueType&>; // Storage helper since _Tp may not have a default constructor. diff --git a/test/general/implementation_details/value_storable_and_comparable.pass.cpp b/test/general/implementation_details/value_storable_and_comparable.pass.cpp index 1dca001871d..f08178f902a 100644 --- a/test/general/implementation_details/value_storable_and_comparable.pass.cpp +++ b/test/general/implementation_details/value_storable_and_comparable.pass.cpp @@ -8,8 +8,8 @@ //===------------------------------------------------------===// // Compile-time checks for oneapi::dpl::__internal::__is_value_storable_and_comparable_v and for each of the -// requirements it is built from: __convertible_to_v, __semiregular and __predicate_v, plus the C++17 building blocks -// of __semiregular (__constructible_from, __move_constructible, __copy_constructible, __assignable_from, __movable, +// requirements it is built from: __convertible_to_v, __semiregular_v and __predicate_v, plus the C++17 building blocks +// of __semiregular_v (__constructible_from, __move_constructible, __copy_constructible, __assignable_from, __movable, // __copyable). Every requirement is checked both ways: a type that satisfies it and a type that does not. #include "support/test_config.h" @@ -163,24 +163,24 @@ static_assert(std::is_convertible_v); static_assert(!dpl_internal::__convertible_to_v); //----------------------------------------------------------------------------// -// __semiregular +// __semiregular_v //----------------------------------------------------------------------------// -static_assert(dpl_internal::__semiregular); -static_assert(dpl_internal::__semiregular); -static_assert(dpl_internal::__semiregular); -static_assert(dpl_internal::__semiregular); -static_assert(dpl_internal::__semiregular>); - -static_assert(!dpl_internal::__semiregular); -static_assert(!dpl_internal::__semiregular); -static_assert(!dpl_internal::__semiregular); -static_assert(!dpl_internal::__semiregular); -static_assert(!dpl_internal::__semiregular); -static_assert(!dpl_internal::__semiregular); +static_assert(dpl_internal::__semiregular_v); +static_assert(dpl_internal::__semiregular_v); +static_assert(dpl_internal::__semiregular_v); +static_assert(dpl_internal::__semiregular_v); +static_assert(dpl_internal::__semiregular_v>); + +static_assert(!dpl_internal::__semiregular_v); +static_assert(!dpl_internal::__semiregular_v); +static_assert(!dpl_internal::__semiregular_v); +static_assert(!dpl_internal::__semiregular_v); +static_assert(!dpl_internal::__semiregular_v); +static_assert(!dpl_internal::__semiregular_v); // Output iterators report void as their value type, so void must be rejected rather than rejecting the program. -static_assert(!dpl_internal::__semiregular); -static_assert(!dpl_internal::__semiregular); +static_assert(!dpl_internal::__semiregular_v); +static_assert(!dpl_internal::__semiregular_v); //----------------------------------------------------------------------------// // __predicate_v @@ -264,7 +264,7 @@ static_assert(!dpl_internal::__predicate_v); static_assert(!dpl_internal::__predicate_v&, const Regular&, const Regular&>); //----------------------------------------------------------------------------// -// C++17 building blocks of __semiregular. In C++20 the standard concepts are used directly, so these helpers only +// C++17 building blocks of __semiregular_v. In C++20 the standard concepts are used directly, so these helpers only // exist in the C++17 branch. //----------------------------------------------------------------------------// From 9bb553745bd762ec96db630b530a81579c817a15 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 8 Sep 2026 10:27:50 +0200 Subject: [PATCH 010/148] Renames inline constexpr bool __assignable_from -> __assignable_from_v --- include/oneapi/dpl/pstl/utils.h | 10 +++++----- .../value_storable_and_comparable.pass.cpp | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/include/oneapi/dpl/pstl/utils.h b/include/oneapi/dpl/pstl/utils.h index 8b96d940e6c..b6b35a154d8 100644 --- a/include/oneapi/dpl/pstl/utils.h +++ b/include/oneapi/dpl/pstl/utils.h @@ -1129,10 +1129,10 @@ inline constexpr bool __convertible_to_v<_From, _To, std::void_t -inline constexpr bool __assignable_from = false; +inline constexpr bool __assignable_from_v = false; template -inline constexpr bool __assignable_from<_Tp, _Up, std::void_t() = std::declval<_Up>())>> = +inline constexpr bool __assignable_from_v<_Tp, _Up, std::void_t() = std::declval<_Up>())>> = std::is_same_v() = std::declval<_Up>()), _Tp&>; // std::constructible_from, which includes std::destructible @@ -1159,7 +1159,7 @@ inline constexpr bool __copy_constructible<_Tp, std::enable_if_t -inline constexpr bool __movable = std::is_object_v<_Tp> && __move_constructible<_Tp> && __assignable_from<_Tp, _Tp>; +inline constexpr bool __movable = std::is_object_v<_Tp> && __move_constructible<_Tp> && __assignable_from_v<_Tp, _Tp>; // std::copyable. Void is rejected up front for the same reason as in __copy_constructible above. template @@ -1167,8 +1167,8 @@ inline constexpr bool __copyable = false; template inline constexpr bool __copyable<_Tp, std::enable_if_t>> = - __copy_constructible<_Tp> && __movable<_Tp> && __assignable_from<_Tp, _Tp&> && __assignable_from<_Tp, const _Tp&> && - __assignable_from<_Tp, const _Tp>; + __copy_constructible<_Tp> && __movable<_Tp> && __assignable_from_v<_Tp, _Tp&> && + __assignable_from_v<_Tp, const _Tp&> && __assignable_from_v<_Tp, const _Tp>; // std::semiregular. std::default_initializable also requires _Tp{} and ::new _Tp to be valid, which // std::is_default_constructible_v does not check. diff --git a/test/general/implementation_details/value_storable_and_comparable.pass.cpp b/test/general/implementation_details/value_storable_and_comparable.pass.cpp index f08178f902a..a3eda9d8ab3 100644 --- a/test/general/implementation_details/value_storable_and_comparable.pass.cpp +++ b/test/general/implementation_details/value_storable_and_comparable.pass.cpp @@ -9,7 +9,7 @@ // Compile-time checks for oneapi::dpl::__internal::__is_value_storable_and_comparable_v and for each of the // requirements it is built from: __convertible_to_v, __semiregular_v and __predicate_v, plus the C++17 building blocks -// of __semiregular_v (__constructible_from, __move_constructible, __copy_constructible, __assignable_from, __movable, +// of __semiregular_v (__constructible_from, __move_constructible, __copy_constructible, __assignable_from_v, __movable, // __copyable). Every requirement is checked both ways: a type that satisfies it and a type that does not. #include "support/test_config.h" @@ -277,13 +277,13 @@ static_assert(!dpl_internal::__constructible_from); static_assert(!dpl_internal::__constructible_from); static_assert(!dpl_internal::__constructible_from); -static_assert(dpl_internal::__assignable_from); -static_assert(dpl_internal::__assignable_from); -static_assert(dpl_internal::__assignable_from); -static_assert(!dpl_internal::__assignable_from); +static_assert(dpl_internal::__assignable_from_v); +static_assert(dpl_internal::__assignable_from_v); +static_assert(dpl_internal::__assignable_from_v); +static_assert(!dpl_internal::__assignable_from_v); static_assert(std::is_assignable_v); -static_assert(!dpl_internal::__assignable_from); -static_assert(!dpl_internal::__assignable_from); +static_assert(!dpl_internal::__assignable_from_v); +static_assert(!dpl_internal::__assignable_from_v); static_assert(dpl_internal::__move_constructible); static_assert(dpl_internal::__move_constructible); @@ -309,7 +309,7 @@ static_assert(!dpl_internal::__copyable); // Each building block has to yield false for void instead of failing to compile, since forming void& is ill-formed // rather than merely unsatisfied. static_assert(!dpl_internal::__constructible_from); -static_assert(!dpl_internal::__assignable_from); +static_assert(!dpl_internal::__assignable_from_v); static_assert(!dpl_internal::__move_constructible); static_assert(!dpl_internal::__copy_constructible); static_assert(!dpl_internal::__movable); From 8aff2f752e4a3015315bdf2a1337c39116730b4a Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 8 Sep 2026 10:29:28 +0200 Subject: [PATCH 011/148] Renames inline constexpr bool __constructible_from -> __constructible_from_v --- include/oneapi/dpl/pstl/utils.h | 10 +++++----- .../value_storable_and_comparable.pass.cpp | 18 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/include/oneapi/dpl/pstl/utils.h b/include/oneapi/dpl/pstl/utils.h index b6b35a154d8..f7c8198d374 100644 --- a/include/oneapi/dpl/pstl/utils.h +++ b/include/oneapi/dpl/pstl/utils.h @@ -1137,12 +1137,12 @@ inline constexpr bool __assignable_from_v<_Tp, _Up, std::void_t -inline constexpr bool __constructible_from = +inline constexpr bool __constructible_from_v = std::is_nothrow_destructible_v<_Tp> && std::is_constructible_v<_Tp, _Args...>; // std::move_constructible template -inline constexpr bool __move_constructible = __constructible_from<_Tp, _Tp> && __convertible_to_v<_Tp, _Tp>; +inline constexpr bool __move_constructible_v = __constructible_from_v<_Tp, _Tp> && __convertible_to_v<_Tp, _Tp>; // std::copy_constructible. Void is rejected up front because the requirement below forms _Tp&, which would be // ill-formed rather than merely unsatisfied, and std::copy_constructible is not satisfied for void anyway. @@ -1151,9 +1151,9 @@ inline constexpr bool __copy_constructible = false; template inline constexpr bool __copy_constructible<_Tp, std::enable_if_t>> = - __move_constructible<_Tp> && __constructible_from<_Tp, _Tp&> && __convertible_to_v<_Tp&, _Tp> && - __constructible_from<_Tp, const _Tp&> && __convertible_to_v && - __constructible_from<_Tp, const _Tp> && __convertible_to_v; + __move_constructible_v<_Tp> && __constructible_from_v<_Tp, _Tp&> && __convertible_to_v<_Tp&, _Tp> && + __constructible_from_v<_Tp, const _Tp&> && __convertible_to_v && + __constructible_from_v<_Tp, const _Tp> && __convertible_to_v; // std::movable, less std::swappable: the latter is implied by move construction and move assignment, since // std::swappable falls back to a move-based implementation, while std::is_swappable_v would additionally reject a diff --git a/test/general/implementation_details/value_storable_and_comparable.pass.cpp b/test/general/implementation_details/value_storable_and_comparable.pass.cpp index a3eda9d8ab3..e95c4e06e0c 100644 --- a/test/general/implementation_details/value_storable_and_comparable.pass.cpp +++ b/test/general/implementation_details/value_storable_and_comparable.pass.cpp @@ -9,7 +9,7 @@ // Compile-time checks for oneapi::dpl::__internal::__is_value_storable_and_comparable_v and for each of the // requirements it is built from: __convertible_to_v, __semiregular_v and __predicate_v, plus the C++17 building blocks -// of __semiregular_v (__constructible_from, __move_constructible, __copy_constructible, __assignable_from_v, __movable, +// of __semiregular_v (__constructible_from_v, __move_constructible, __copy_constructible, __assignable_from_v, __movable, // __copyable). Every requirement is checked both ways: a type that satisfies it and a type that does not. #include "support/test_config.h" @@ -95,7 +95,7 @@ struct VoidAssign } }; -// std::destructible, and hence __constructible_from, requires the destructor to be noexcept. +// std::destructible, and hence __constructible_from_v, requires the destructor to be noexcept. struct ThrowingDtor { int val = 0; @@ -270,12 +270,12 @@ static_assert(!dpl_internal::__predicate_v&, const Regular&, cons #if !_ONEDPL_CPP20_CONCEPTS_PRESENT -static_assert(dpl_internal::__constructible_from); -static_assert(dpl_internal::__constructible_from); -static_assert(dpl_internal::__constructible_from); -static_assert(!dpl_internal::__constructible_from); -static_assert(!dpl_internal::__constructible_from); -static_assert(!dpl_internal::__constructible_from); +static_assert(dpl_internal::__constructible_from_v); +static_assert(dpl_internal::__constructible_from_v); +static_assert(dpl_internal::__constructible_from_v); +static_assert(!dpl_internal::__constructible_from_v); +static_assert(!dpl_internal::__constructible_from_v); +static_assert(!dpl_internal::__constructible_from_v); static_assert(dpl_internal::__assignable_from_v); static_assert(dpl_internal::__assignable_from_v); @@ -308,7 +308,7 @@ static_assert(!dpl_internal::__copyable); // Each building block has to yield false for void instead of failing to compile, since forming void& is ill-formed // rather than merely unsatisfied. -static_assert(!dpl_internal::__constructible_from); +static_assert(!dpl_internal::__constructible_from_v); static_assert(!dpl_internal::__assignable_from_v); static_assert(!dpl_internal::__move_constructible); static_assert(!dpl_internal::__copy_constructible); From 6b1bbfe3cc0e33e7a109a6451871e0065204dc0b Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 8 Sep 2026 10:30:35 +0200 Subject: [PATCH 012/148] Renames inline constexpr bool __move_constructible -> __move_constructible_v --- include/oneapi/dpl/pstl/utils.h | 2 +- .../value_storable_and_comparable.pass.cpp | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/oneapi/dpl/pstl/utils.h b/include/oneapi/dpl/pstl/utils.h index f7c8198d374..f18dce2d342 100644 --- a/include/oneapi/dpl/pstl/utils.h +++ b/include/oneapi/dpl/pstl/utils.h @@ -1159,7 +1159,7 @@ inline constexpr bool __copy_constructible<_Tp, std::enable_if_t -inline constexpr bool __movable = std::is_object_v<_Tp> && __move_constructible<_Tp> && __assignable_from_v<_Tp, _Tp>; +inline constexpr bool __movable = std::is_object_v<_Tp> && __move_constructible_v<_Tp> && __assignable_from_v<_Tp, _Tp>; // std::copyable. Void is rejected up front for the same reason as in __copy_constructible above. template diff --git a/test/general/implementation_details/value_storable_and_comparable.pass.cpp b/test/general/implementation_details/value_storable_and_comparable.pass.cpp index e95c4e06e0c..ddce849f60c 100644 --- a/test/general/implementation_details/value_storable_and_comparable.pass.cpp +++ b/test/general/implementation_details/value_storable_and_comparable.pass.cpp @@ -9,7 +9,7 @@ // Compile-time checks for oneapi::dpl::__internal::__is_value_storable_and_comparable_v and for each of the // requirements it is built from: __convertible_to_v, __semiregular_v and __predicate_v, plus the C++17 building blocks -// of __semiregular_v (__constructible_from_v, __move_constructible, __copy_constructible, __assignable_from_v, __movable, +// of __semiregular_v (__constructible_from_v, __move_constructible_v, __copy_constructible, __assignable_from_v, __movable, // __copyable). Every requirement is checked both ways: a type that satisfies it and a type that does not. #include "support/test_config.h" @@ -285,10 +285,10 @@ static_assert(std::is_assignable_v); static_assert(!dpl_internal::__assignable_from_v); static_assert(!dpl_internal::__assignable_from_v); -static_assert(dpl_internal::__move_constructible); -static_assert(dpl_internal::__move_constructible); -static_assert(!dpl_internal::__move_constructible); -static_assert(!dpl_internal::__move_constructible); +static_assert(dpl_internal::__move_constructible_v); +static_assert(dpl_internal::__move_constructible_v); +static_assert(!dpl_internal::__move_constructible_v); +static_assert(!dpl_internal::__move_constructible_v); static_assert(dpl_internal::__copy_constructible); static_assert(dpl_internal::__copy_constructible); @@ -310,7 +310,7 @@ static_assert(!dpl_internal::__copyable); // rather than merely unsatisfied. static_assert(!dpl_internal::__constructible_from_v); static_assert(!dpl_internal::__assignable_from_v); -static_assert(!dpl_internal::__move_constructible); +static_assert(!dpl_internal::__move_constructible_v); static_assert(!dpl_internal::__copy_constructible); static_assert(!dpl_internal::__movable); static_assert(!dpl_internal::__copyable); From 9de20e244ee781b5f18d44113248f34f5fe97399 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 8 Sep 2026 10:31:16 +0200 Subject: [PATCH 013/148] Renames inline constexpr bool __copy_constructible -> __copy_constructible_v --- include/oneapi/dpl/pstl/utils.h | 8 ++++---- .../value_storable_and_comparable.pass.cpp | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/include/oneapi/dpl/pstl/utils.h b/include/oneapi/dpl/pstl/utils.h index f18dce2d342..c4cfbc47f56 100644 --- a/include/oneapi/dpl/pstl/utils.h +++ b/include/oneapi/dpl/pstl/utils.h @@ -1147,10 +1147,10 @@ inline constexpr bool __move_constructible_v = __constructible_from_v<_Tp, _Tp> // std::copy_constructible. Void is rejected up front because the requirement below forms _Tp&, which would be // ill-formed rather than merely unsatisfied, and std::copy_constructible is not satisfied for void anyway. template -inline constexpr bool __copy_constructible = false; +inline constexpr bool __copy_constructible_v = false; template -inline constexpr bool __copy_constructible<_Tp, std::enable_if_t>> = +inline constexpr bool __copy_constructible_v<_Tp, std::enable_if_t>> = __move_constructible_v<_Tp> && __constructible_from_v<_Tp, _Tp&> && __convertible_to_v<_Tp&, _Tp> && __constructible_from_v<_Tp, const _Tp&> && __convertible_to_v && __constructible_from_v<_Tp, const _Tp> && __convertible_to_v; @@ -1161,13 +1161,13 @@ inline constexpr bool __copy_constructible<_Tp, std::enable_if_t inline constexpr bool __movable = std::is_object_v<_Tp> && __move_constructible_v<_Tp> && __assignable_from_v<_Tp, _Tp>; -// std::copyable. Void is rejected up front for the same reason as in __copy_constructible above. +// std::copyable. Void is rejected up front for the same reason as in __copy_constructible_v above. template inline constexpr bool __copyable = false; template inline constexpr bool __copyable<_Tp, std::enable_if_t>> = - __copy_constructible<_Tp> && __movable<_Tp> && __assignable_from_v<_Tp, _Tp&> && + __copy_constructible_v<_Tp> && __movable<_Tp> && __assignable_from_v<_Tp, _Tp&> && __assignable_from_v<_Tp, const _Tp&> && __assignable_from_v<_Tp, const _Tp>; // std::semiregular. std::default_initializable also requires _Tp{} and ::new _Tp to be valid, which diff --git a/test/general/implementation_details/value_storable_and_comparable.pass.cpp b/test/general/implementation_details/value_storable_and_comparable.pass.cpp index ddce849f60c..71471df8f8b 100644 --- a/test/general/implementation_details/value_storable_and_comparable.pass.cpp +++ b/test/general/implementation_details/value_storable_and_comparable.pass.cpp @@ -9,7 +9,7 @@ // Compile-time checks for oneapi::dpl::__internal::__is_value_storable_and_comparable_v and for each of the // requirements it is built from: __convertible_to_v, __semiregular_v and __predicate_v, plus the C++17 building blocks -// of __semiregular_v (__constructible_from_v, __move_constructible_v, __copy_constructible, __assignable_from_v, __movable, +// of __semiregular_v (__constructible_from_v, __move_constructible_v, __copy_constructible_v, __assignable_from_v, __movable, // __copyable). Every requirement is checked both ways: a type that satisfies it and a type that does not. #include "support/test_config.h" @@ -290,10 +290,10 @@ static_assert(dpl_internal::__move_constructible_v); static_assert(!dpl_internal::__move_constructible_v); static_assert(!dpl_internal::__move_constructible_v); -static_assert(dpl_internal::__copy_constructible); -static_assert(dpl_internal::__copy_constructible); -static_assert(!dpl_internal::__copy_constructible); -static_assert(!dpl_internal::__copy_constructible); +static_assert(dpl_internal::__copy_constructible_v); +static_assert(dpl_internal::__copy_constructible_v); +static_assert(!dpl_internal::__copy_constructible_v); +static_assert(!dpl_internal::__copy_constructible_v); static_assert(dpl_internal::__movable); static_assert(dpl_internal::__movable); @@ -311,10 +311,10 @@ static_assert(!dpl_internal::__copyable); static_assert(!dpl_internal::__constructible_from_v); static_assert(!dpl_internal::__assignable_from_v); static_assert(!dpl_internal::__move_constructible_v); -static_assert(!dpl_internal::__copy_constructible); +static_assert(!dpl_internal::__copy_constructible_v); static_assert(!dpl_internal::__movable); static_assert(!dpl_internal::__copyable); -static_assert(!dpl_internal::__copy_constructible); +static_assert(!dpl_internal::__copy_constructible_v); static_assert(!dpl_internal::__copyable); #endif // !_ONEDPL_CPP20_CONCEPTS_PRESENT From 0c61ad979858152979a01b145347883023dd926b Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 8 Sep 2026 10:31:53 +0200 Subject: [PATCH 014/148] Renames inline constexpr bool __movable -> __movable_v --- include/oneapi/dpl/pstl/utils.h | 4 ++-- .../value_storable_and_comparable.pass.cpp | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/oneapi/dpl/pstl/utils.h b/include/oneapi/dpl/pstl/utils.h index c4cfbc47f56..4d30899d1b6 100644 --- a/include/oneapi/dpl/pstl/utils.h +++ b/include/oneapi/dpl/pstl/utils.h @@ -1159,7 +1159,7 @@ inline constexpr bool __copy_constructible_v<_Tp, std::enable_if_t -inline constexpr bool __movable = std::is_object_v<_Tp> && __move_constructible_v<_Tp> && __assignable_from_v<_Tp, _Tp>; +inline constexpr bool __movable_v = std::is_object_v<_Tp> && __move_constructible_v<_Tp> && __assignable_from_v<_Tp, _Tp>; // std::copyable. Void is rejected up front for the same reason as in __copy_constructible_v above. template @@ -1167,7 +1167,7 @@ inline constexpr bool __copyable = false; template inline constexpr bool __copyable<_Tp, std::enable_if_t>> = - __copy_constructible_v<_Tp> && __movable<_Tp> && __assignable_from_v<_Tp, _Tp&> && + __copy_constructible_v<_Tp> && __movable_v<_Tp> && __assignable_from_v<_Tp, _Tp&> && __assignable_from_v<_Tp, const _Tp&> && __assignable_from_v<_Tp, const _Tp>; // std::semiregular. std::default_initializable also requires _Tp{} and ::new _Tp to be valid, which diff --git a/test/general/implementation_details/value_storable_and_comparable.pass.cpp b/test/general/implementation_details/value_storable_and_comparable.pass.cpp index 71471df8f8b..192bf1ff739 100644 --- a/test/general/implementation_details/value_storable_and_comparable.pass.cpp +++ b/test/general/implementation_details/value_storable_and_comparable.pass.cpp @@ -9,7 +9,7 @@ // Compile-time checks for oneapi::dpl::__internal::__is_value_storable_and_comparable_v and for each of the // requirements it is built from: __convertible_to_v, __semiregular_v and __predicate_v, plus the C++17 building blocks -// of __semiregular_v (__constructible_from_v, __move_constructible_v, __copy_constructible_v, __assignable_from_v, __movable, +// of __semiregular_v (__constructible_from_v, __move_constructible_v, __copy_constructible_v, __assignable_from_v, __movable_v, // __copyable). Every requirement is checked both ways: a type that satisfies it and a type that does not. #include "support/test_config.h" @@ -295,10 +295,10 @@ static_assert(dpl_internal::__copy_constructible_v); static_assert(!dpl_internal::__copy_constructible_v); static_assert(!dpl_internal::__copy_constructible_v); -static_assert(dpl_internal::__movable); -static_assert(dpl_internal::__movable); -static_assert(!dpl_internal::__movable); -static_assert(!dpl_internal::__movable); +static_assert(dpl_internal::__movable_v); +static_assert(dpl_internal::__movable_v); +static_assert(!dpl_internal::__movable_v); +static_assert(!dpl_internal::__movable_v); static_assert(dpl_internal::__copyable); static_assert(dpl_internal::__copyable); @@ -312,7 +312,7 @@ static_assert(!dpl_internal::__constructible_from_v); static_assert(!dpl_internal::__assignable_from_v); static_assert(!dpl_internal::__move_constructible_v); static_assert(!dpl_internal::__copy_constructible_v); -static_assert(!dpl_internal::__movable); +static_assert(!dpl_internal::__movable_v); static_assert(!dpl_internal::__copyable); static_assert(!dpl_internal::__copy_constructible_v); static_assert(!dpl_internal::__copyable); From 39a590c200d2a7cd8844a691e183efc66dff46e6 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 8 Sep 2026 10:32:29 +0200 Subject: [PATCH 015/148] Renames inline constexpr bool __copyable -> __copyable_v --- include/oneapi/dpl/pstl/utils.h | 6 +++--- .../value_storable_and_comparable.pass.cpp | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/include/oneapi/dpl/pstl/utils.h b/include/oneapi/dpl/pstl/utils.h index 4d30899d1b6..0dfaee759f7 100644 --- a/include/oneapi/dpl/pstl/utils.h +++ b/include/oneapi/dpl/pstl/utils.h @@ -1163,17 +1163,17 @@ inline constexpr bool __movable_v = std::is_object_v<_Tp> && __move_constructibl // std::copyable. Void is rejected up front for the same reason as in __copy_constructible_v above. template -inline constexpr bool __copyable = false; +inline constexpr bool __copyable_v = false; template -inline constexpr bool __copyable<_Tp, std::enable_if_t>> = +inline constexpr bool __copyable_v<_Tp, std::enable_if_t>> = __copy_constructible_v<_Tp> && __movable_v<_Tp> && __assignable_from_v<_Tp, _Tp&> && __assignable_from_v<_Tp, const _Tp&> && __assignable_from_v<_Tp, const _Tp>; // std::semiregular. std::default_initializable also requires _Tp{} and ::new _Tp to be valid, which // std::is_default_constructible_v does not check. template -inline constexpr bool __semiregular_v = __copyable<_Tp> && std::is_default_constructible_v<_Tp>; +inline constexpr bool __semiregular_v = __copyable_v<_Tp> && std::is_default_constructible_v<_Tp>; // std::predicate requires the result to be boolean-testable, which is stronger than being convertible to bool, but // the difference only shows for types with an unusable operator!. diff --git a/test/general/implementation_details/value_storable_and_comparable.pass.cpp b/test/general/implementation_details/value_storable_and_comparable.pass.cpp index 192bf1ff739..f8b5766efd2 100644 --- a/test/general/implementation_details/value_storable_and_comparable.pass.cpp +++ b/test/general/implementation_details/value_storable_and_comparable.pass.cpp @@ -10,7 +10,7 @@ // Compile-time checks for oneapi::dpl::__internal::__is_value_storable_and_comparable_v and for each of the // requirements it is built from: __convertible_to_v, __semiregular_v and __predicate_v, plus the C++17 building blocks // of __semiregular_v (__constructible_from_v, __move_constructible_v, __copy_constructible_v, __assignable_from_v, __movable_v, -// __copyable). Every requirement is checked both ways: a type that satisfies it and a type that does not. +// __copyable_v). Every requirement is checked both ways: a type that satisfies it and a type that does not. #include "support/test_config.h" @@ -300,11 +300,11 @@ static_assert(dpl_internal::__movable_v); static_assert(!dpl_internal::__movable_v); static_assert(!dpl_internal::__movable_v); -static_assert(dpl_internal::__copyable); -static_assert(dpl_internal::__copyable); -static_assert(!dpl_internal::__copyable); -static_assert(!dpl_internal::__copyable); -static_assert(!dpl_internal::__copyable); +static_assert(dpl_internal::__copyable_v); +static_assert(dpl_internal::__copyable_v); +static_assert(!dpl_internal::__copyable_v); +static_assert(!dpl_internal::__copyable_v); +static_assert(!dpl_internal::__copyable_v); // Each building block has to yield false for void instead of failing to compile, since forming void& is ill-formed // rather than merely unsatisfied. @@ -313,9 +313,9 @@ static_assert(!dpl_internal::__assignable_from_v); static_assert(!dpl_internal::__move_constructible_v); static_assert(!dpl_internal::__copy_constructible_v); static_assert(!dpl_internal::__movable_v); -static_assert(!dpl_internal::__copyable); +static_assert(!dpl_internal::__copyable_v); static_assert(!dpl_internal::__copy_constructible_v); -static_assert(!dpl_internal::__copyable); +static_assert(!dpl_internal::__copyable_v); #endif // !_ONEDPL_CPP20_CONCEPTS_PRESENT From f3771efe91b8ec61a558c0d6d61806d1adbd585c Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 8 Sep 2026 12:58:30 +0200 Subject: [PATCH 016/148] Replace `__semiregular_v` with the exact requirements of the SIMD `min` / `max` / `minmax` path (#2821) --- include/oneapi/dpl/pstl/algorithm_impl.h | 4 +- include/oneapi/dpl/pstl/unseq_backend_simd.h | 54 ++- include/oneapi/dpl/pstl/utils.h | 93 +----- .../value_storable_and_comparable.pass.cpp | 316 +++++++++--------- .../alg.min.max/minmax_element.pass.cpp | 139 +++++++- 5 files changed, 333 insertions(+), 273 deletions(-) diff --git a/include/oneapi/dpl/pstl/algorithm_impl.h b/include/oneapi/dpl/pstl/algorithm_impl.h index 477eb59afeb..87758405c68 100644 --- a/include/oneapi/dpl/pstl/algorithm_impl.h +++ b/include/oneapi/dpl/pstl/algorithm_impl.h @@ -4876,7 +4876,7 @@ __brick_min_element(_RandomAccessIterator __first, _RandomAccessIterator __last, /* __is_vector = */ ::std::true_type) noexcept { #if _ONEDPL_UDR_PRESENT // _PSTL_UDR_PRESENT - if constexpr (__is_value_storable_and_comparable_v<_RandomAccessIterator, _Compare>) + if constexpr (__unseq_backend::__is_value_storable_and_comparable_v<_RandomAccessIterator, _Compare>) return __unseq_backend::__simd_min_element(__first, __last - __first, __comp); #endif @@ -4944,7 +4944,7 @@ __brick_minmax_element(_RandomAccessIterator __first, _RandomAccessIterator __la /* __is_vector = */ ::std::true_type) noexcept { #if _ONEDPL_UDR_PRESENT // _PSTL_UDR_PRESENT - if constexpr (__is_value_storable_and_comparable_v<_RandomAccessIterator, _Compare>) + if constexpr (__unseq_backend::__is_value_storable_and_comparable_v<_RandomAccessIterator, _Compare>) return __unseq_backend::__simd_minmax_element(__first, __last - __first, __comp); #endif diff --git a/include/oneapi/dpl/pstl/unseq_backend_simd.h b/include/oneapi/dpl/pstl/unseq_backend_simd.h index f874238deed..46f03cf81a7 100644 --- a/include/oneapi/dpl/pstl/unseq_backend_simd.h +++ b/include/oneapi/dpl/pstl/unseq_backend_simd.h @@ -17,7 +17,9 @@ #define _ONEDPL_UNSEQ_BACKEND_SIMD_H #include -#include // for std::addressof +#include // for std::addressof +#include // for std::iterator_traits +#include // for std::as_const #include "utils.h" @@ -613,15 +615,44 @@ __simd_scan(_InputIterator __first, _Size __n, _OutputIterator __result, _UnaryO return ::std::make_pair(__result + __n, __init_.__value); } +// The reduction object initializes its value members with _ValueType{}, which is not what +// std::is_default_constructible_v checks: that trait stands for _ValueType v;, and the two differ both ways. An +// aggregate whose member has an explicit default constructor is default-constructible but not brace-initializable, +// while an aggregate with a const member without a default member initializer is the other way round. +template +inline constexpr bool __is_brace_constructible_v = false; + +template +inline constexpr bool __is_brace_constructible_v<_Tp, decltype(void(_Tp{}))> = true; + +// An output iterator reports void as its value type: such a value cannot be stored, and forming const _ValueType& +// for it would be ill-formed rather than merely unsatisfied, so void is rejected up front. +template ::value_type, typename = void> +inline constexpr bool __is_value_storable_and_comparable_v = false; + +// The requirement covers only what the vectorized bricks add on top of the input the algorithms already require: the +// value type has to be storable in the reduction object and the comparator has to be applicable to the stored copies. +// What the algorithms require themselves is not re-checked here and fails to compile if it is not met: that *__first is +// convertible to the value type, and that the result of the comparison can be negated. +// Every requirement is the expression the implementation uses rather than the concept it resembles: std::semiregular +// would also require moving, an assignment returning _ValueType& and a non-throwing destructor. +template +inline constexpr bool __is_value_storable_and_comparable_v<_Iterator, _Compare, _ValueType, + std::enable_if_t>> = + __is_brace_constructible_v<_ValueType> && std::is_copy_constructible_v<_ValueType> && + std::is_copy_assignable_v<_ValueType> && + std::is_invocable_r_v; + // The implementation keeps copies of the values in the reduction object and compares those copies, so the value // type has to be usable in a user-defined reduction and the comparator has to be applicable to the copies: -// __internal::__is_value_storable_and_comparable_v is the requirement checked by the callers. +// __is_value_storable_and_comparable_v is the requirement checked by the callers. // complexity [violation] - We will have at most (__n-1 + number_of_lanes) comparisons instead of at most __n-1. template _ForwardIterator __simd_min_element(_ForwardIterator __first, _Size __n, _Compare __comp) noexcept { - static_assert(__internal::__is_value_storable_and_comparable_v<_ForwardIterator, _Compare>, + static_assert(__is_value_storable_and_comparable_v<_ForwardIterator, _Compare>, "The value type of the iterator must be storable in the reduction object and __comp must be " "a predicate over objects of that type"); @@ -666,7 +697,9 @@ __simd_min_element(_ForwardIterator __first, _Size __n, _Compare __comp) noexcep _ONEDPL_PRAGMA_SIMD_REDUCTION(__min_func : __init) for (_Size __i = 1; __i < __n; ++__i) { - const _ValueType __min_val = __init.__min_val; + // The candidate is read through a const reference and copied by direct initialization, so that copying it + // requires nothing but std::is_copy_constructible_v, which is stated in terms of direct initialization too. + const _ValueType __min_val(std::as_const(__init).__min_val); const _ValueType __current = __first[__i]; if (std::invoke(__comp, __current, __min_val)) { @@ -679,13 +712,13 @@ __simd_min_element(_ForwardIterator __first, _Size __n, _Compare __comp) noexcep // The implementation keeps copies of the values in the reduction object and compares those copies, so the value // type has to be usable in a user-defined reduction and the comparator has to be applicable to the copies: -// __internal::__is_value_storable_and_comparable_v is the requirement checked by the callers. +// __is_value_storable_and_comparable_v is the requirement checked by the callers. // complexity [violation] - We will have at most (2*(__n-1) + 4*number_of_lanes) comparisons instead of at most [1.5*(__n-1)]. template std::pair<_ForwardIterator, _ForwardIterator> __simd_minmax_element(_ForwardIterator __first, _Size __n, _Compare __comp) noexcept { - static_assert(__internal::__is_value_storable_and_comparable_v<_ForwardIterator, _Compare>, + static_assert(__is_value_storable_and_comparable_v<_ForwardIterator, _Compare>, "The value type of the iterator must be storable in the reduction object and __comp must be " "a predicate over objects of that type"); @@ -750,9 +783,12 @@ __simd_minmax_element(_ForwardIterator __first, _Size __n, _Compare __comp) noex _ONEDPL_PRAGMA_SIMD_REDUCTION(__min_func : __init) for (_Size __i = 1; __i < __n; ++__i) { - auto __min_val = __init.__min_val; - auto __max_val = __init.__max_val; - auto __current = __first[__i]; + // The candidates are read through a const reference and copied by direct initialization, and the element is + // materialized as a _ValueType, so that copying and storing them requires nothing but + // std::is_copy_constructible_v and std::is_copy_assignable_v. + const _ValueType __min_val(std::as_const(__init).__min_val); + const _ValueType __max_val(std::as_const(__init).__max_val); + const _ValueType __current = __first[__i]; if (std::invoke(__comp, __current, __min_val)) { __init.__min_val = __current; diff --git a/include/oneapi/dpl/pstl/utils.h b/include/oneapi/dpl/pstl/utils.h index 0dfaee759f7..77b074ee7aa 100644 --- a/include/oneapi/dpl/pstl/utils.h +++ b/include/oneapi/dpl/pstl/utils.h @@ -49,7 +49,7 @@ #endif #if _ONEDPL_CPP20_CONCEPTS_PRESENT -# include // for std::equality_comparable_with, std::semiregular, std::convertible_to, std::predicate +# include // for std::equality_comparable_with #endif #include "functional_impl.h" @@ -1104,97 +1104,6 @@ struct __is_type_with_iterator_traits< template static constexpr bool __is_type_with_iterator_traits_v = __is_type_with_iterator_traits<_T>::value; -// The requirements below are named after the concepts they stand for: C++20 uses those concepts directly, while -// C++17 gets an approximation of each of them. -#if _ONEDPL_CPP20_CONCEPTS_PRESENT - -template -inline constexpr bool __convertible_to_v = std::convertible_to<_From, _To>; - -template -inline constexpr bool __semiregular_v = std::semiregular<_Tp>; - -template -inline constexpr bool __predicate_v = std::predicate<_Fp, _Args...>; - -#else - -// std::convertible_to also requires an explicit conversion, which std::is_convertible_v does not check. -template -inline constexpr bool __convertible_to_v = false; - -template -inline constexpr bool __convertible_to_v<_From, _To, std::void_t(std::declval<_From>()))>> = - std::is_convertible_v<_From, _To>; - -// std::assignable_from also requires the assignment to return _Tp&, which std::is_assignable_v does not check. -template -inline constexpr bool __assignable_from_v = false; - -template -inline constexpr bool __assignable_from_v<_Tp, _Up, std::void_t() = std::declval<_Up>())>> = - std::is_same_v() = std::declval<_Up>()), _Tp&>; - -// std::constructible_from, which includes std::destructible -template -inline constexpr bool __constructible_from_v = - std::is_nothrow_destructible_v<_Tp> && std::is_constructible_v<_Tp, _Args...>; - -// std::move_constructible -template -inline constexpr bool __move_constructible_v = __constructible_from_v<_Tp, _Tp> && __convertible_to_v<_Tp, _Tp>; - -// std::copy_constructible. Void is rejected up front because the requirement below forms _Tp&, which would be -// ill-formed rather than merely unsatisfied, and std::copy_constructible is not satisfied for void anyway. -template -inline constexpr bool __copy_constructible_v = false; - -template -inline constexpr bool __copy_constructible_v<_Tp, std::enable_if_t>> = - __move_constructible_v<_Tp> && __constructible_from_v<_Tp, _Tp&> && __convertible_to_v<_Tp&, _Tp> && - __constructible_from_v<_Tp, const _Tp&> && __convertible_to_v && - __constructible_from_v<_Tp, const _Tp> && __convertible_to_v; - -// std::movable, less std::swappable: the latter is implied by move construction and move assignment, since -// std::swappable falls back to a move-based implementation, while std::is_swappable_v would additionally reject a -// type with a deleted ADL swap. -template -inline constexpr bool __movable_v = std::is_object_v<_Tp> && __move_constructible_v<_Tp> && __assignable_from_v<_Tp, _Tp>; - -// std::copyable. Void is rejected up front for the same reason as in __copy_constructible_v above. -template -inline constexpr bool __copyable_v = false; - -template -inline constexpr bool __copyable_v<_Tp, std::enable_if_t>> = - __copy_constructible_v<_Tp> && __movable_v<_Tp> && __assignable_from_v<_Tp, _Tp&> && - __assignable_from_v<_Tp, const _Tp&> && __assignable_from_v<_Tp, const _Tp>; - -// std::semiregular. std::default_initializable also requires _Tp{} and ::new _Tp to be valid, which -// std::is_default_constructible_v does not check. -template -inline constexpr bool __semiregular_v = __copyable_v<_Tp> && std::is_default_constructible_v<_Tp>; - -// std::predicate requires the result to be boolean-testable, which is stronger than being convertible to bool, but -// the difference only shows for types with an unusable operator!. -template -inline constexpr bool __predicate_v = std::is_invocable_r_v; - -#endif // _ONEDPL_CPP20_CONCEPTS_PRESENT - -// An output iterator reports void as its value type: such a value cannot be stored, and forming const _ValueType& -// for it would be ill-formed rather than merely unsatisfied, so void is rejected up front. -template ::reference, - typename _ValueType = typename std::iterator_traits<_Iterator>::value_type, typename = void> -inline constexpr bool __is_value_storable_and_comparable_v = false; - -template -inline constexpr bool __is_value_storable_and_comparable_v<_Iterator, _Compare, _ReferenceType, _ValueType, - std::enable_if_t>> = - __semiregular_v<_ValueType> && __convertible_to_v<_ReferenceType, _ValueType> && - __predicate_v<_Compare&, const _ValueType&, const _ValueType&>; - // Storage helper since _Tp may not have a default constructor. template union __lazy_ctor_storage diff --git a/test/general/implementation_details/value_storable_and_comparable.pass.cpp b/test/general/implementation_details/value_storable_and_comparable.pass.cpp index f8b5766efd2..3bde33cf7a5 100644 --- a/test/general/implementation_details/value_storable_and_comparable.pass.cpp +++ b/test/general/implementation_details/value_storable_and_comparable.pass.cpp @@ -7,13 +7,14 @@ // //===------------------------------------------------------===// -// Compile-time checks for oneapi::dpl::__internal::__is_value_storable_and_comparable_v and for each of the -// requirements it is built from: __convertible_to_v, __semiregular_v and __predicate_v, plus the C++17 building blocks -// of __semiregular_v (__constructible_from_v, __move_constructible_v, __copy_constructible_v, __assignable_from_v, __movable_v, -// __copyable_v). Every requirement is checked both ways: a type that satisfies it and a type that does not. +// Compile-time checks for oneapi::dpl::__unseq_backend::__is_value_storable_and_comparable_v and for the requirements +// it is built from. +// The only one of them that is not a standard type trait, oneapi::dpl::__unseq_backend::__is_brace_constructible_v, is +// checked on its own as well. Every requirement is checked both ways: a type that satisfies it and a type that does not. #include "support/test_config.h" +#include #include #include @@ -26,12 +27,13 @@ #include "support/utils.h" namespace dpl_internal = oneapi::dpl::__internal; +namespace dpl_unseq = oneapi::dpl::__unseq_backend; //----------------------------------------------------------------------------// // Value types //----------------------------------------------------------------------------// -// Satisfies every requirement: default-constructible, copyable, less-than comparable. +// Satisfies every requirement: default-constructible, copy-constructible, copy-assignable, less-than comparable. struct Regular { int val = 0; @@ -42,7 +44,7 @@ struct Regular } }; -// std::default_initializable accepts an explicit default constructor, since T{} stays valid. +// An explicit default constructor is enough, since _ValueType{} is a direct initialization, which may use it. struct ExplicitDefaultCtor { int val; @@ -54,6 +56,24 @@ struct ExplicitDefaultCtor } }; +struct ExplicitDefaultCtorMember +{ + int val; + explicit ExplicitDefaultCtorMember() : val(0) {} +}; + +// Default-constructible, but not brace-initializable: an aggregate is initialized member by member, and the member is +// copy-initialized from an empty list, which may not use its explicit default constructor. +struct AggregateOfExplicitDefaultCtor +{ + ExplicitDefaultCtorMember member; + bool + operator<(const AggregateOfExplicitDefaultCtor& other) const + { + return member.val < other.member.val; + } +}; + struct NoDefaultCtor { int val; @@ -79,7 +99,7 @@ struct NoCopyAssign } }; -// std::is_assignable_v is satisfied, but the assignment does not return VoidAssign&. +// The assignment does not return VoidAssign&, which is enough here because the result is never used. struct VoidAssign { int val = 0; @@ -95,7 +115,7 @@ struct VoidAssign } }; -// std::destructible, and hence __constructible_from_v, requires the destructor to be noexcept. +// The destructor is not noexcept, which is enough here because storing a value never has to be non-throwing. struct ThrowingDtor { int val = 0; @@ -124,66 +144,78 @@ struct MoveOnly } }; -//----------------------------------------------------------------------------// -// __convertible_to_v -//----------------------------------------------------------------------------// - -struct ExplicitFromInt +// Deleting the move operations while keeping the copy ones is enough here, because the value is never moved. +struct CopyOnlyNoMove { - explicit ExplicitFromInt(int) {} + int val = 0; + CopyOnlyNoMove() = default; + CopyOnlyNoMove(const CopyOnlyNoMove&) = default; + CopyOnlyNoMove& + operator=(const CopyOnlyNoMove&) = default; + CopyOnlyNoMove(CopyOnlyNoMove&&) = delete; + CopyOnlyNoMove& + operator=(CopyOnlyNoMove&&) = delete; + bool + operator<(const CopyOnlyNoMove& other) const + { + return val < other.val; + } }; -// A destination whose only constructor taking ImplicitSource is deleted and explicit: copy-initialization ignores it -// and picks the conversion operator, so std::is_convertible_v is satisfied, while static_cast selects the deleted -// constructor. This is the difference std::convertible_to catches and std::is_convertible_v does not. -struct ExplicitlyNotConvertible; - -struct ImplicitSource +// Copyable and assignable from a const lvalue only, which is enough here, because the candidates are read through +// std::as_const and the element is materialized as a const _ValueType. +struct ConstCopyOnly { - operator ExplicitlyNotConvertible() const; + int val = 0; + ConstCopyOnly() = default; + ConstCopyOnly(const ConstCopyOnly&) = default; + ConstCopyOnly& + operator=(const ConstCopyOnly&) = default; + ConstCopyOnly(ConstCopyOnly&) = delete; + ConstCopyOnly& + operator=(ConstCopyOnly&) = delete; + bool + operator<(const ConstCopyOnly& other) const + { + return val < other.val; + } }; -struct ExplicitlyNotConvertible +// A value type whose copy constructor is explicit, which is enough for copying the candidates, because they are copied +// by direct initialization, and so is std::is_copy_constructible_v defined. Copy-initializing an element of such a type +// is ill-formed, so an iterator over it does not meet the requirements of a forward iterator. +struct ExplicitCopyCtor { - ExplicitlyNotConvertible() = default; - explicit ExplicitlyNotConvertible(ImplicitSource) = delete; + int val = 0; + ExplicitCopyCtor() = default; + explicit ExplicitCopyCtor(const ExplicitCopyCtor& other) : val(other.val) {} + ExplicitCopyCtor& + operator=(const ExplicitCopyCtor&) = default; + bool + operator<(const ExplicitCopyCtor&) const + { + return false; + } }; -static_assert(dpl_internal::__convertible_to_v); -static_assert(dpl_internal::__convertible_to_v); -static_assert(dpl_internal::__convertible_to_v); -static_assert(dpl_internal::__convertible_to_v); -static_assert(dpl_internal::__convertible_to_v, std::pair>); - -static_assert(!dpl_internal::__convertible_to_v); -static_assert(!dpl_internal::__convertible_to_v); -static_assert(!dpl_internal::__convertible_to_v); -static_assert(!dpl_internal::__convertible_to_v); -static_assert(std::is_convertible_v); -static_assert(!dpl_internal::__convertible_to_v); - //----------------------------------------------------------------------------// -// __semiregular_v +// __is_brace_constructible_v //----------------------------------------------------------------------------// -static_assert(dpl_internal::__semiregular_v); -static_assert(dpl_internal::__semiregular_v); -static_assert(dpl_internal::__semiregular_v); -static_assert(dpl_internal::__semiregular_v); -static_assert(dpl_internal::__semiregular_v>); - -static_assert(!dpl_internal::__semiregular_v); -static_assert(!dpl_internal::__semiregular_v); -static_assert(!dpl_internal::__semiregular_v); -static_assert(!dpl_internal::__semiregular_v); -static_assert(!dpl_internal::__semiregular_v); -static_assert(!dpl_internal::__semiregular_v); -// Output iterators report void as their value type, so void must be rejected rather than rejecting the program. -static_assert(!dpl_internal::__semiregular_v); -static_assert(!dpl_internal::__semiregular_v); +static_assert(dpl_unseq::__is_brace_constructible_v); +static_assert(dpl_unseq::__is_brace_constructible_v); +static_assert(dpl_unseq::__is_brace_constructible_v); +static_assert(dpl_unseq::__is_brace_constructible_v); +static_assert(dpl_unseq::__is_brace_constructible_v); + +static_assert(std::is_default_constructible_v); +static_assert(!dpl_unseq::__is_brace_constructible_v); +static_assert(!dpl_unseq::__is_brace_constructible_v); +// void{} is a valid expression, so this requirement does not reject void: that is done separately. +static_assert(dpl_unseq::__is_brace_constructible_v); //----------------------------------------------------------------------------// -// __predicate_v +// Comparison objects //----------------------------------------------------------------------------// struct NotBool @@ -250,80 +282,30 @@ struct MoveOnlyLess } }; -static_assert(dpl_internal::__predicate_v&, const int&, const int&>); -static_assert(dpl_internal::__predicate_v&, const int&, const int&>); -static_assert(dpl_internal::__predicate_v); -static_assert(dpl_internal::__predicate_v); -static_assert(dpl_internal::__predicate_v&, const Regular&, const Regular&>); - -static_assert(!dpl_internal::__predicate_v); -static_assert(!dpl_internal::__predicate_v); -static_assert(!dpl_internal::__predicate_v); -static_assert(!dpl_internal::__predicate_v); -static_assert(!dpl_internal::__predicate_v); -static_assert(!dpl_internal::__predicate_v&, const Regular&, const Regular&>); - -//----------------------------------------------------------------------------// -// C++17 building blocks of __semiregular_v. In C++20 the standard concepts are used directly, so these helpers only -// exist in the C++17 branch. -//----------------------------------------------------------------------------// +struct NotNegatableResult +{ + operator bool() const; + bool + operator!() const = delete; +}; -#if !_ONEDPL_CPP20_CONCEPTS_PRESENT - -static_assert(dpl_internal::__constructible_from_v); -static_assert(dpl_internal::__constructible_from_v); -static_assert(dpl_internal::__constructible_from_v); -static_assert(!dpl_internal::__constructible_from_v); -static_assert(!dpl_internal::__constructible_from_v); -static_assert(!dpl_internal::__constructible_from_v); - -static_assert(dpl_internal::__assignable_from_v); -static_assert(dpl_internal::__assignable_from_v); -static_assert(dpl_internal::__assignable_from_v); -static_assert(!dpl_internal::__assignable_from_v); -static_assert(std::is_assignable_v); -static_assert(!dpl_internal::__assignable_from_v); -static_assert(!dpl_internal::__assignable_from_v); - -static_assert(dpl_internal::__move_constructible_v); -static_assert(dpl_internal::__move_constructible_v); -static_assert(!dpl_internal::__move_constructible_v); -static_assert(!dpl_internal::__move_constructible_v); - -static_assert(dpl_internal::__copy_constructible_v); -static_assert(dpl_internal::__copy_constructible_v); -static_assert(!dpl_internal::__copy_constructible_v); -static_assert(!dpl_internal::__copy_constructible_v); - -static_assert(dpl_internal::__movable_v); -static_assert(dpl_internal::__movable_v); -static_assert(!dpl_internal::__movable_v); -static_assert(!dpl_internal::__movable_v); - -static_assert(dpl_internal::__copyable_v); -static_assert(dpl_internal::__copyable_v); -static_assert(!dpl_internal::__copyable_v); -static_assert(!dpl_internal::__copyable_v); -static_assert(!dpl_internal::__copyable_v); - -// Each building block has to yield false for void instead of failing to compile, since forming void& is ill-formed -// rather than merely unsatisfied. -static_assert(!dpl_internal::__constructible_from_v); -static_assert(!dpl_internal::__assignable_from_v); -static_assert(!dpl_internal::__move_constructible_v); -static_assert(!dpl_internal::__copy_constructible_v); -static_assert(!dpl_internal::__movable_v); -static_assert(!dpl_internal::__copyable_v); -static_assert(!dpl_internal::__copy_constructible_v); -static_assert(!dpl_internal::__copyable_v); - -#endif // !_ONEDPL_CPP20_CONCEPTS_PRESENT +// The result of the comparison is convertible to bool, but cannot be negated, while the vectorized bricks do negate it. +// Such a comparison object does not meet the Compare requirements the standard states for the algorithms, so it is not +// rejected here: the requirement accepts it in both C++17 and C++20, and instantiating the brick for it is a compile +// error rather than a fallback to the serial implementation. +struct NotNegatableResultLess +{ + NotNegatableResult + operator()(const int& lhs, const int& rhs) const; +}; //----------------------------------------------------------------------------// -// __is_value_storable_and_comparable_v +// Reference types //----------------------------------------------------------------------------// -// An iterator whose reference type is not convertible to its value type. +// A reference type that does not convert to the value type: an iterator reporting it does not meet the requirements of +// a forward iterator, which state that *__first is convertible to the value type, so the requirement does not look at +// the reference type at all. struct OpaqueRef { }; @@ -341,46 +323,66 @@ struct FakeIterator operator*() const; }; +//----------------------------------------------------------------------------// +// __is_value_storable_and_comparable_v +//----------------------------------------------------------------------------// + // Accepted: the value type is storable and the comparator is callable on const values. -static_assert(dpl_internal::__is_value_storable_and_comparable_v>); -static_assert(dpl_internal::__is_value_storable_and_comparable_v>); -static_assert(dpl_internal::__is_value_storable_and_comparable_v::iterator, std::less>); -static_assert(dpl_internal::__is_value_storable_and_comparable_v::const_iterator, std::less<>>); -static_assert(dpl_internal::__is_value_storable_and_comparable_v>); -static_assert( - dpl_internal::__is_value_storable_and_comparable_v>); -static_assert(dpl_internal::__is_value_storable_and_comparable_v); -static_assert(dpl_internal::__is_value_storable_and_comparable_v); -static_assert(dpl_internal::__is_value_storable_and_comparable_v); +static_assert(dpl_unseq::__is_value_storable_and_comparable_v>); +static_assert(dpl_unseq::__is_value_storable_and_comparable_v>); +static_assert(dpl_unseq::__is_value_storable_and_comparable_v::iterator, std::less>); +static_assert(dpl_unseq::__is_value_storable_and_comparable_v::const_iterator, std::less<>>); +static_assert(dpl_unseq::__is_value_storable_and_comparable_v>); +static_assert(dpl_unseq::__is_value_storable_and_comparable_v>); +// Accepted: the requirements are brace initialization, copy construction and copy assignment, and nothing else. +static_assert(dpl_unseq::__is_value_storable_and_comparable_v>); +static_assert(dpl_unseq::__is_value_storable_and_comparable_v>); +static_assert(dpl_unseq::__is_value_storable_and_comparable_v>); +// Accepted: copying and storing a value only ever reads it through a const reference. +static_assert(dpl_unseq::__is_value_storable_and_comparable_v>); +static_assert(dpl_unseq::__is_value_storable_and_comparable_v); +static_assert(dpl_unseq::__is_value_storable_and_comparable_v); +static_assert(dpl_unseq::__is_value_storable_and_comparable_v); +// Accepted although the bricks do not compile for it: a comparison object that does not meet the Compare requirements +// of the algorithms is not detected here, see NotNegatableResultLess above. +static_assert(dpl_unseq::__is_value_storable_and_comparable_v); // The comparator max_element passes down to the min_element brick. -static_assert( - dpl_internal::__is_value_storable_and_comparable_v>>); -// A proxy reference is fine as long as it converts to the value type. -static_assert(dpl_internal::__is_value_storable_and_comparable_v::iterator, std::less>); -static_assert(dpl_internal::__is_value_storable_and_comparable_v, std::less>); -static_assert(dpl_internal::__is_value_storable_and_comparable_v, std::pair>, - std::less>>); - -// Rejected because of the value type. -static_assert(!dpl_internal::__is_value_storable_and_comparable_v>); -static_assert(!dpl_internal::__is_value_storable_and_comparable_v>); -static_assert(!dpl_internal::__is_value_storable_and_comparable_v>); -static_assert(!dpl_internal::__is_value_storable_and_comparable_v>); -static_assert(!dpl_internal::__is_value_storable_and_comparable_v>); - -// Rejected because the reference type does not convert to the value type. -static_assert(!dpl_internal::__is_value_storable_and_comparable_v, std::less>); +static_assert(dpl_unseq::__is_value_storable_and_comparable_v>>); +// The reference type is not part of the requirement, so a proxy reference and an iterator returning the value type by +// value are accepted like any other. +static_assert(dpl_unseq::__is_value_storable_and_comparable_v::iterator, std::less>); +static_assert(dpl_unseq::__is_value_storable_and_comparable_v, std::less>); +static_assert(dpl_unseq::__is_value_storable_and_comparable_v, std::pair>, + std::less>>); +static_assert(dpl_unseq::__is_value_storable_and_comparable_v, + std::less>); +// Accepted although the bricks do not compile for them: an element of these iterators cannot be copy-initialized into +// the value type, so they do not meet the requirements of a forward iterator, which is not detected here either. The +// value types themselves are copy-constructible, which is stated in terms of direct initialization. +static_assert(std::is_copy_constructible_v); +static_assert(dpl_unseq::__is_value_storable_and_comparable_v>); +static_assert(dpl_unseq::__is_value_storable_and_comparable_v>); +static_assert(dpl_unseq::__is_value_storable_and_comparable_v, std::less>); + +// Rejected because of the value type: the first two fail brace initialization, the third copy assignment, and the +// move-only one copy construction, and with it every other requirement that copies a value. +static_assert(!dpl_unseq::__is_value_storable_and_comparable_v>); +static_assert(!dpl_unseq::__is_value_storable_and_comparable_v>); +static_assert(!dpl_unseq::__is_value_storable_and_comparable_v>); +static_assert(!dpl_unseq::__is_value_storable_and_comparable_v>); // Rejected because an output iterator reports void as its value type. -static_assert(!dpl_internal::__is_value_storable_and_comparable_v>, - std::less>); +static_assert(!dpl_unseq::__is_value_storable_and_comparable_v>, + std::less>); // Rejected because of the comparator. -static_assert(!dpl_internal::__is_value_storable_and_comparable_v); -static_assert(!dpl_internal::__is_value_storable_and_comparable_v); -static_assert(!dpl_internal::__is_value_storable_and_comparable_v); -static_assert(!dpl_internal::__is_value_storable_and_comparable_v); -static_assert(!dpl_internal::__is_value_storable_and_comparable_v>); +static_assert(!dpl_unseq::__is_value_storable_and_comparable_v); +static_assert(!dpl_unseq::__is_value_storable_and_comparable_v); +static_assert(!dpl_unseq::__is_value_storable_and_comparable_v); +static_assert(!dpl_unseq::__is_value_storable_and_comparable_v); +static_assert(!dpl_unseq::__is_value_storable_and_comparable_v>); +static_assert(!dpl_unseq::__is_value_storable_and_comparable_v); int main() diff --git a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp index 05959f039db..24bf1a85404 100644 --- a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp +++ b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #if !defined(_PSTL_TEST_MIN_ELEMENT) && !defined(_PSTL_TEST_MAX_ELEMENT) &&\ @@ -247,6 +248,81 @@ struct ExplicitDefaultCtorCompare } }; +// The move operations of the value type are deleted: the vector code path is still applicable for it, because the +// vector code never moves a value. +struct CopyOnlyNoMoveCompare +{ + std::int32_t val; + CopyOnlyNoMoveCompare() : val(0) {} + CopyOnlyNoMoveCompare(std::int32_t val_) : val(val_) {} + CopyOnlyNoMoveCompare(const CopyOnlyNoMoveCompare&) = default; + CopyOnlyNoMoveCompare& + operator=(const CopyOnlyNoMoveCompare&) = default; + CopyOnlyNoMoveCompare(CopyOnlyNoMoveCompare&&) = delete; + CopyOnlyNoMoveCompare& + operator=(CopyOnlyNoMoveCompare&&) = delete; + bool + operator<(const CopyOnlyNoMoveCompare& other) const + { + return val < other.val; + } +}; + +// The assignment of the value type does not return VoidAssignCompare&: the vector code path is still applicable for it, +// because the vector code never uses the result of an assignment. +struct VoidAssignCompare +{ + std::int32_t val; + VoidAssignCompare() : val(0) {} + VoidAssignCompare(std::int32_t val_) : val(val_) {} + void + operator=(const VoidAssignCompare& other) + { + val = other.val; + } + bool + operator<(const VoidAssignCompare& other) const + { + return val < other.val; + } +}; + +// The destructor of the value type is not noexcept: the vector code path is still applicable for it, because storing +// a value never has to be non-throwing. +struct ThrowingDtorCompare +{ + std::int32_t val; + ThrowingDtorCompare() : val(0) {} + ThrowingDtorCompare(std::int32_t val_) : val(val_) {} + ~ThrowingDtorCompare() noexcept(false) {} + bool + operator<(const ThrowingDtorCompare& other) const + { + return val < other.val; + } +}; + +// The value type can be copied and assigned from a const lvalue only. The vector code path is still applicable for it, +// because the vector code reads both the elements and the stored candidates through const references, but it requires +// const iterators here: the reference type of a non-const iterator does not convert to such a value type. +struct ConstCopyOnlyCompare +{ + std::int32_t val; + ConstCopyOnlyCompare() : val(0) {} + ConstCopyOnlyCompare(std::int32_t val_) : val(val_) {} + ConstCopyOnlyCompare(const ConstCopyOnlyCompare&) = default; + ConstCopyOnlyCompare(ConstCopyOnlyCompare&) = delete; + ConstCopyOnlyCompare& + operator=(const ConstCopyOnlyCompare&) = default; + ConstCopyOnlyCompare& + operator=(ConstCopyOnlyCompare&) = delete; + bool + operator<(const ConstCopyOnlyCompare& other) const + { + return val < other.val; + } +}; + // The value type is not default-constructible, so it cannot be used in a user-defined reduction // and the vector code path must not be selected for it. The same holds for NoCopyAssignCompare and // MoveOnlyCompare below: each of them violates one of the requirements the vector code path puts on the @@ -299,10 +375,28 @@ struct MoveOnlyCompare } }; +template +static void +check_by_type_host_policies(Iterator first, Iterator last) +{ +#ifdef _PSTL_TEST_MIN_ELEMENT + invoke_on_all_host_policies()(check_minelement(), first, last); + invoke_on_all_host_policies()(check_minelement_predicate(), first, last); +#endif +#ifdef _PSTL_TEST_MAX_ELEMENT + invoke_on_all_host_policies()(check_maxelement(), first, last); + invoke_on_all_host_policies()(check_maxelement_predicate(), first, last); +#endif +#ifdef _PSTL_TEST_MINMAX_ELEMENT + invoke_on_all_host_policies()(check_minmaxelement(), first, last); + invoke_on_all_host_policies()(check_minmaxelement_predicate(), first, last); +#endif +} + // The sequence is built in place because the value types checked here either do not satisfy the requirements of // TestUtils::Sequence (which default-constructs and assigns its elements) or are not trivially copyable, and thus // cannot be checked with device policies. -template +template static void test_by_type_host_policies(::std::size_t n) { @@ -311,18 +405,25 @@ test_by_type_host_policies(::std::size_t n) for (::std::size_t i = 0; i < n; ++i) data.emplace_back(std::int32_t(TestUtils::HashBits(i, 30))); -#ifdef _PSTL_TEST_MIN_ELEMENT - invoke_on_all_host_policies()(check_minelement(), data.begin(), data.end()); - invoke_on_all_host_policies()(check_minelement_predicate(), data.begin(), data.end()); -#endif -#ifdef _PSTL_TEST_MAX_ELEMENT - invoke_on_all_host_policies()(check_maxelement(), data.begin(), data.end()); - invoke_on_all_host_policies()(check_maxelement_predicate(), data.begin(), data.end()); -#endif -#ifdef _PSTL_TEST_MINMAX_ELEMENT - invoke_on_all_host_policies()(check_minmaxelement(), data.begin(), data.end()); - invoke_on_all_host_policies()(check_minmaxelement_predicate(), data.begin(), data.end()); -#endif + using Iterator = ::std::conditional_t::const_iterator, + typename ::std::vector::iterator>; + check_by_type_host_policies(Iterator(data.begin()), Iterator(data.end())); +} + +// A value type with deleted move operations cannot be stored in a std::vector, because the growth path of the container +// moves its elements, so the sequence is a plain array here and its elements are assigned from const lvalues. +template +static void +test_by_type_host_policies_array() +{ + T data[N]; + for (::std::size_t i = 0; i < N; ++i) + { + const T value(std::int32_t(TestUtils::HashBits(i, 30))); + data[i] = value; + } + + check_by_type_host_policies(data, data + N); } template @@ -378,6 +479,18 @@ main() // by an explicit default constructor, which the path has to accept at compile time, so one size is enough. test_by_type(NSmall); + // These value types are accepted by the vector code path as well, and the point of checking them is that the vector + // code is instantiated for them: each of them violates one of the requirements of std::semiregular that the vector + // code does not have. That does not depend on the sequence size either. + test_by_type_host_policies_array(); + test_by_type_host_policies(NSmall); + test_by_type_host_policies(NSmall); + + // This value type is accepted by the vector code path through const iterators, so the point of checking it is that + // the vector code copies the elements and the stored candidates from const lvalues only. That does not depend on + // the sequence size either. + test_by_type_host_policies(NSmall); + // These value types are rejected by the vector code path, so the point of checking them is that the call compiles // and falls back to the serial implementation. That does not depend on the sequence size, so one size is enough. test_by_type_host_policies(NSmall); From 46973aedbbbad613586cb94cf472b05d438a8644 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 8 Sep 2026 13:47:19 +0200 Subject: [PATCH 017/148] value_storable_and_comparable.pass.cpp - fix compile error --- .../value_storable_and_comparable.pass.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/general/implementation_details/value_storable_and_comparable.pass.cpp b/test/general/implementation_details/value_storable_and_comparable.pass.cpp index 3bde33cf7a5..dee80af647d 100644 --- a/test/general/implementation_details/value_storable_and_comparable.pass.cpp +++ b/test/general/implementation_details/value_storable_and_comparable.pass.cpp @@ -211,8 +211,6 @@ static_assert(dpl_unseq::__is_brace_constructible_v); static_assert(std::is_default_constructible_v); static_assert(!dpl_unseq::__is_brace_constructible_v); static_assert(!dpl_unseq::__is_brace_constructible_v); -// void{} is a valid expression, so this requirement does not reject void: that is done separately. -static_assert(dpl_unseq::__is_brace_constructible_v); //----------------------------------------------------------------------------// // Comparison objects From b5465a83ad5ef60edb9df649609d8c7bd76e9f78 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 8 Sep 2026 13:47:37 +0200 Subject: [PATCH 018/148] minmax_element.pass.cpp - expand test coverage --- .../alg.min.max/minmax_element.pass.cpp | 100 ++++++++++++++++-- 1 file changed, 92 insertions(+), 8 deletions(-) diff --git a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp index 24bf1a85404..f49f6e608bb 100644 --- a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp +++ b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp @@ -117,6 +117,64 @@ struct check_minmaxelement_predicate } }; +// The comparison object overloads unary operator&, which a user-defined functor is allowed to do, so the vector code +// path has to take its address with std::addressof. Both overloads are deleted, therefore taking the address with & +// does not compile. +struct OverloadedAddressOfLess +{ + void operator&() = delete; + void operator&() const = delete; + + template + bool + operator()(const T& lhs, const T& rhs) const + { + return lhs < rhs; + } +}; + +template +struct check_minelement_overloaded_address_of +{ + template + void + operator()(Policy&& exec, Iterator begin, Iterator end) + { + const Iterator expect = ::std::min_element(begin, end); + const Iterator result = + std::min_element(std::forward(exec), begin, end, OverloadedAddressOfLess()); + EXPECT_EQ(expect, result, "wrong return result from min_element with a comparator overloading operator&"); + } +}; + +template +struct check_maxelement_overloaded_address_of +{ + template + void + operator()(Policy&& exec, Iterator begin, Iterator end) + { + const Iterator expect = ::std::max_element(begin, end); + const Iterator result = + std::max_element(std::forward(exec), begin, end, OverloadedAddressOfLess()); + EXPECT_EQ(expect, result, "wrong return result from max_element with a comparator overloading operator&"); + } +}; + +template +struct check_minmaxelement_overloaded_address_of +{ + template + void + operator()(Policy&& exec, Iterator begin, Iterator end) + { + const ::std::pair expect = ::std::minmax_element(begin, end); + const std::pair got = + std::minmax_element(std::forward(exec), begin, end, OverloadedAddressOfLess()); + EXPECT_EQ(expect, got, "wrong return result from minmax_element with a comparator overloading operator&"); + } +}; + template struct sequence_wrapper { @@ -410,20 +468,41 @@ test_by_type_host_policies(::std::size_t n) check_by_type_host_policies(Iterator(data.begin()), Iterator(data.end())); } -// A value type with deleted move operations cannot be stored in a std::vector, because the growth path of the container -// moves its elements, so the sequence is a plain array here and its elements are assigned from const lvalues. -template +// A value type with deleted move operations cannot be added to a std::vector, because the growth path of the container +// moves its elements, so the sequence is sized up front here and its elements are assigned from const lvalues. A plain +// array is deliberately not used: with the bounds of the storage known at compile time, GCC reports a false +// out-of-bounds subscript in the parallel reduction, which never dereferences its identity iterator, the end of the +// sequence. +template static void -test_by_type_host_policies_array() +test_by_type_host_policies_no_move(::std::size_t n) { - T data[N]; - for (::std::size_t i = 0; i < N; ++i) + ::std::vector data(n); + for (::std::size_t i = 0; i < n; ++i) { const T value(std::int32_t(TestUtils::HashBits(i, 30))); data[i] = value; } - check_by_type_host_policies(data, data + N); + check_by_type_host_policies(data.begin(), data.end()); +} + +// The comparison object is passed to min_element and minmax_element as is, so the vector code path takes its address +// there, and to max_element wrapped into an internal predicate which reorders the arguments. +static void +test_comparator_with_overloaded_address_of(::std::size_t n) +{ + Sequence in(n, [](::std::size_t i) { return std::int32_t(TestUtils::HashBits(i, 30)); }); + +#ifdef _PSTL_TEST_MIN_ELEMENT + invoke_on_all_host_policies()(check_minelement_overloaded_address_of(), in.begin(), in.end()); +#endif +#ifdef _PSTL_TEST_MAX_ELEMENT + invoke_on_all_host_policies()(check_maxelement_overloaded_address_of(), in.begin(), in.end()); +#endif +#ifdef _PSTL_TEST_MINMAX_ELEMENT + invoke_on_all_host_policies()(check_minmaxelement_overloaded_address_of(), in.begin(), in.end()); +#endif } template @@ -482,7 +561,7 @@ main() // These value types are accepted by the vector code path as well, and the point of checking them is that the vector // code is instantiated for them: each of them violates one of the requirements of std::semiregular that the vector // code does not have. That does not depend on the sequence size either. - test_by_type_host_policies_array(); + test_by_type_host_policies_no_move(NSmall); test_by_type_host_policies(NSmall); test_by_type_host_policies(NSmall); @@ -497,6 +576,11 @@ main() test_by_type_host_policies(NSmall); test_by_type_host_policies(NSmall); + // The comparison object of this check overloads unary operator&, so the point of it is that the vector code path + // takes the address of the comparison object with std::addressof: with & it does not compile. The sequence is long + // enough for the vector code to process several blocks and to combine their results. + test_comparator_with_overloaded_address_of(1000); + #ifdef _PSTL_TEST_MIN_ELEMENT test_algo_basic_single(run_for_rnd_fw>()); #endif From 3c7f1054d2a03ecf06957fa77c3f9b6b73aa6ba8 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 8 Sep 2026 13:54:07 +0200 Subject: [PATCH 019/148] minmax_element.pass.cpp - expand test coverage --- .../alg.sorting/alg.min.max/minmax_element.pass.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp index f49f6e608bb..567e93b3188 100644 --- a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp +++ b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp @@ -122,8 +122,10 @@ struct check_minmaxelement_predicate // does not compile. struct OverloadedAddressOfLess { - void operator&() = delete; - void operator&() const = delete; + void + operator&() = delete; + void + operator&() const = delete; template bool @@ -141,8 +143,7 @@ struct check_minelement_overloaded_address_of operator()(Policy&& exec, Iterator begin, Iterator end) { const Iterator expect = ::std::min_element(begin, end); - const Iterator result = - std::min_element(std::forward(exec), begin, end, OverloadedAddressOfLess()); + const Iterator result = std::min_element(std::forward(exec), begin, end, OverloadedAddressOfLess()); EXPECT_EQ(expect, result, "wrong return result from min_element with a comparator overloading operator&"); } }; @@ -155,8 +156,7 @@ struct check_maxelement_overloaded_address_of operator()(Policy&& exec, Iterator begin, Iterator end) { const Iterator expect = ::std::max_element(begin, end); - const Iterator result = - std::max_element(std::forward(exec), begin, end, OverloadedAddressOfLess()); + const Iterator result = std::max_element(std::forward(exec), begin, end, OverloadedAddressOfLess()); EXPECT_EQ(expect, result, "wrong return result from max_element with a comparator overloading operator&"); } }; From 95e55cf884a9dd332786d0890846ac840ce414ad Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 8 Sep 2026 14:01:29 +0200 Subject: [PATCH 020/148] expand test coverage --- .../value_storable_and_comparable.pass.cpp | 3 ++- .../algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/general/implementation_details/value_storable_and_comparable.pass.cpp b/test/general/implementation_details/value_storable_and_comparable.pass.cpp index dee80af647d..16eab2280f5 100644 --- a/test/general/implementation_details/value_storable_and_comparable.pass.cpp +++ b/test/general/implementation_details/value_storable_and_comparable.pass.cpp @@ -10,7 +10,8 @@ // Compile-time checks for oneapi::dpl::__unseq_backend::__is_value_storable_and_comparable_v and for the requirements // it is built from. // The only one of them that is not a standard type trait, oneapi::dpl::__unseq_backend::__is_brace_constructible_v, is -// checked on its own as well. Every requirement is checked both ways: a type that satisfies it and a type that does not. +// checked on its own as well. Every requirement is checked both ways: a type that satisfies it and a type that does +// not. #include "support/test_config.h" diff --git a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp index 567e93b3188..811178c53af 100644 --- a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp +++ b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp @@ -127,9 +127,8 @@ struct OverloadedAddressOfLess void operator&() const = delete; - template bool - operator()(const T& lhs, const T& rhs) const + operator()(const std::int32_t& lhs, const std::int32_t& rhs) const { return lhs < rhs; } From e6602d4cf273e73c7e0bde7aac7f79cbc6678c87 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 8 Sep 2026 14:12:28 +0200 Subject: [PATCH 021/148] Fix review comment: not reasons no check comparator compatibility In C++17 this is stricter than the algorithm and the SIMD body: is_invocable_r_v requires an implicit conversion to bool, while Compare and these if/! expressions only require contextual conversion. A valid result type with explicit operator bool() therefore compiles in the SIMD expressions but makes this gate select the serial fallback. Detect invocability plus the actual contextual/negation expressions instead so conforming C++17 comparators are not unnecessarily de-vectorized, and add a compile-time case for that result type. --- include/oneapi/dpl/pstl/algorithm_impl.h | 4 +- include/oneapi/dpl/pstl/unseq_backend_simd.h | 43 +- .../value_storable.pass.cpp | 227 ++++++++++ .../value_storable_and_comparable.pass.cpp | 390 ------------------ 4 files changed, 247 insertions(+), 417 deletions(-) create mode 100644 test/general/implementation_details/value_storable.pass.cpp delete mode 100644 test/general/implementation_details/value_storable_and_comparable.pass.cpp diff --git a/include/oneapi/dpl/pstl/algorithm_impl.h b/include/oneapi/dpl/pstl/algorithm_impl.h index 87758405c68..c6b9d142369 100644 --- a/include/oneapi/dpl/pstl/algorithm_impl.h +++ b/include/oneapi/dpl/pstl/algorithm_impl.h @@ -4876,7 +4876,7 @@ __brick_min_element(_RandomAccessIterator __first, _RandomAccessIterator __last, /* __is_vector = */ ::std::true_type) noexcept { #if _ONEDPL_UDR_PRESENT // _PSTL_UDR_PRESENT - if constexpr (__unseq_backend::__is_value_storable_and_comparable_v<_RandomAccessIterator, _Compare>) + if constexpr (__unseq_backend::__is_value_storable_v<_RandomAccessIterator>) return __unseq_backend::__simd_min_element(__first, __last - __first, __comp); #endif @@ -4944,7 +4944,7 @@ __brick_minmax_element(_RandomAccessIterator __first, _RandomAccessIterator __la /* __is_vector = */ ::std::true_type) noexcept { #if _ONEDPL_UDR_PRESENT // _PSTL_UDR_PRESENT - if constexpr (__unseq_backend::__is_value_storable_and_comparable_v<_RandomAccessIterator, _Compare>) + if constexpr (__unseq_backend::__is_value_storable_v<_RandomAccessIterator>) return __unseq_backend::__simd_minmax_element(__first, __last - __first, __comp); #endif diff --git a/include/oneapi/dpl/pstl/unseq_backend_simd.h b/include/oneapi/dpl/pstl/unseq_backend_simd.h index 46f03cf81a7..e0badc64027 100644 --- a/include/oneapi/dpl/pstl/unseq_backend_simd.h +++ b/include/oneapi/dpl/pstl/unseq_backend_simd.h @@ -627,34 +627,29 @@ inline constexpr bool __is_brace_constructible_v<_Tp, decltype(void(_Tp{}))> = t // An output iterator reports void as its value type: such a value cannot be stored, and forming const _ValueType& // for it would be ill-formed rather than merely unsatisfied, so void is rejected up front. -template ::value_type, typename = void> -inline constexpr bool __is_value_storable_and_comparable_v = false; - -// The requirement covers only what the vectorized bricks add on top of the input the algorithms already require: the -// value type has to be storable in the reduction object and the comparator has to be applicable to the stored copies. -// What the algorithms require themselves is not re-checked here and fails to compile if it is not met: that *__first is -// convertible to the value type, and that the result of the comparison can be negated. +template ::value_type, + typename = void> +inline constexpr bool __is_value_storable_v = false; + +// The requirement covers only what the vectorized bricks add on top of what the algorithms already require: the value +// type has to be storable in the reduction object. Anything else, the comparison object included, is not looked at and +// fails to compile if it is not met, exactly as it does without this requirement. // Every requirement is the expression the implementation uses rather than the concept it resembles: std::semiregular // would also require moving, an assignment returning _ValueType& and a non-throwing destructor. -template -inline constexpr bool __is_value_storable_and_comparable_v<_Iterator, _Compare, _ValueType, - std::enable_if_t>> = +template +inline constexpr bool __is_value_storable_v<_Iterator, _ValueType, std::enable_if_t>> = __is_brace_constructible_v<_ValueType> && std::is_copy_constructible_v<_ValueType> && - std::is_copy_assignable_v<_ValueType> && - std::is_invocable_r_v; + std::is_copy_assignable_v<_ValueType>; -// The implementation keeps copies of the values in the reduction object and compares those copies, so the value -// type has to be usable in a user-defined reduction and the comparator has to be applicable to the copies: -// __is_value_storable_and_comparable_v is the requirement checked by the callers. +// The implementation keeps copies of the values in the reduction object, so the value type has to be usable in a +// user-defined reduction: __is_value_storable_v is the requirement checked by the callers. // complexity [violation] - We will have at most (__n-1 + number_of_lanes) comparisons instead of at most __n-1. template _ForwardIterator __simd_min_element(_ForwardIterator __first, _Size __n, _Compare __comp) noexcept { - static_assert(__is_value_storable_and_comparable_v<_ForwardIterator, _Compare>, - "The value type of the iterator must be storable in the reduction object and __comp must be " - "a predicate over objects of that type"); + static_assert(__is_value_storable_v<_ForwardIterator>, + "The value type of the iterator must be storable in the reduction object"); if (__n == 0) { @@ -710,17 +705,15 @@ __simd_min_element(_ForwardIterator __first, _Size __n, _Compare __comp) noexcep return __first + __init.__min_ind; } -// The implementation keeps copies of the values in the reduction object and compares those copies, so the value -// type has to be usable in a user-defined reduction and the comparator has to be applicable to the copies: -// __is_value_storable_and_comparable_v is the requirement checked by the callers. +// The implementation keeps copies of the values in the reduction object, so the value type has to be usable in a +// user-defined reduction: __is_value_storable_v is the requirement checked by the callers. // complexity [violation] - We will have at most (2*(__n-1) + 4*number_of_lanes) comparisons instead of at most [1.5*(__n-1)]. template std::pair<_ForwardIterator, _ForwardIterator> __simd_minmax_element(_ForwardIterator __first, _Size __n, _Compare __comp) noexcept { - static_assert(__is_value_storable_and_comparable_v<_ForwardIterator, _Compare>, - "The value type of the iterator must be storable in the reduction object and __comp must be " - "a predicate over objects of that type"); + static_assert(__is_value_storable_v<_ForwardIterator>, + "The value type of the iterator must be storable in the reduction object"); if (__n == 0) { diff --git a/test/general/implementation_details/value_storable.pass.cpp b/test/general/implementation_details/value_storable.pass.cpp new file mode 100644 index 00000000000..aad8ed21d83 --- /dev/null +++ b/test/general/implementation_details/value_storable.pass.cpp @@ -0,0 +1,227 @@ +// -*- C++ -*- +//===------------------------------------------------------===// +// +// Copyright (C) 2025 UXL Foundation Contributors +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===------------------------------------------------------===// + +// Compile-time checks for oneapi::dpl::__unseq_backend::__is_value_storable_v and for the requirements it is built +// from. +// The only one of them that is not a standard type trait, oneapi::dpl::__unseq_backend::__is_brace_constructible_v, is +// checked on its own as well. Every requirement is checked both ways: a type that satisfies it and a type that does +// not. + +#include "support/test_config.h" + +#include + +#include +#include +#include +#include +#include + +#include "support/utils.h" + +namespace dpl_unseq = oneapi::dpl::__unseq_backend; + +//----------------------------------------------------------------------------// +// Value types +//----------------------------------------------------------------------------// + +// Satisfies every requirement: default-constructible, copy-constructible, copy-assignable. +struct Regular +{ + int val = 0; +}; + +// An explicit default constructor is enough, since _ValueType{} is a direct initialization, which may use it. +struct ExplicitDefaultCtor +{ + int val; + explicit ExplicitDefaultCtor() : val(0) {} +}; + +struct ExplicitDefaultCtorMember +{ + int val; + explicit ExplicitDefaultCtorMember() : val(0) {} +}; + +// Default-constructible, but not brace-initializable: an aggregate is initialized member by member, and the member is +// copy-initialized from an empty list, which may not use its explicit default constructor. +struct AggregateOfExplicitDefaultCtor +{ + ExplicitDefaultCtorMember member; +}; + +struct NoDefaultCtor +{ + int val; + explicit NoDefaultCtor(int v) : val(v) {} +}; + +struct NoCopyAssign +{ + int val = 0; + NoCopyAssign() = default; + NoCopyAssign(const NoCopyAssign&) = default; + NoCopyAssign& + operator=(const NoCopyAssign&) = delete; +}; + +// The assignment does not return VoidAssign&, which is enough here because the result is never used. +struct VoidAssign +{ + int val = 0; + void + operator=(const VoidAssign& other) + { + val = other.val; + } +}; + +// The destructor is not noexcept, which is enough here because storing a value never has to be non-throwing. +struct ThrowingDtor +{ + int val = 0; + ~ThrowingDtor() noexcept(false) {} +}; + +struct MoveOnly +{ + int val = 0; + MoveOnly() = default; + MoveOnly(MoveOnly&&) = default; + MoveOnly& + operator=(MoveOnly&&) = default; + MoveOnly(const MoveOnly&) = delete; + MoveOnly& + operator=(const MoveOnly&) = delete; +}; + +// Deleting the move operations while keeping the copy ones is enough here, because the value is never moved. +struct CopyOnlyNoMove +{ + int val = 0; + CopyOnlyNoMove() = default; + CopyOnlyNoMove(const CopyOnlyNoMove&) = default; + CopyOnlyNoMove& + operator=(const CopyOnlyNoMove&) = default; + CopyOnlyNoMove(CopyOnlyNoMove&&) = delete; + CopyOnlyNoMove& + operator=(CopyOnlyNoMove&&) = delete; +}; + +// Copyable and assignable from a const lvalue only, which is enough here, because the candidates are read through +// std::as_const and the element is materialized as a const _ValueType. +struct ConstCopyOnly +{ + int val = 0; + ConstCopyOnly() = default; + ConstCopyOnly(const ConstCopyOnly&) = default; + ConstCopyOnly& + operator=(const ConstCopyOnly&) = default; + ConstCopyOnly(ConstCopyOnly&) = delete; + ConstCopyOnly& + operator=(ConstCopyOnly&) = delete; +}; + +// A value type whose copy constructor is explicit, which is enough for copying the candidates, because they are copied +// by direct initialization, and so is std::is_copy_constructible_v defined. Copy-initializing an element of such a type +// is ill-formed, so an iterator over it does not meet the requirements of a forward iterator. +struct ExplicitCopyCtor +{ + int val = 0; + ExplicitCopyCtor() = default; + explicit ExplicitCopyCtor(const ExplicitCopyCtor& other) : val(other.val) {} + ExplicitCopyCtor& + operator=(const ExplicitCopyCtor&) = default; +}; + +//----------------------------------------------------------------------------// +// __is_brace_constructible_v +//----------------------------------------------------------------------------// + +static_assert(dpl_unseq::__is_brace_constructible_v); +static_assert(dpl_unseq::__is_brace_constructible_v); +static_assert(dpl_unseq::__is_brace_constructible_v); +static_assert(dpl_unseq::__is_brace_constructible_v); +static_assert(dpl_unseq::__is_brace_constructible_v); + +static_assert(std::is_default_constructible_v); +static_assert(!dpl_unseq::__is_brace_constructible_v); +static_assert(!dpl_unseq::__is_brace_constructible_v); + +//----------------------------------------------------------------------------// +// Reference types +//----------------------------------------------------------------------------// + +// A reference type that does not convert to the value type: an iterator reporting it does not meet the requirements of +// a forward iterator, which state that *__first is convertible to the value type, so the requirement does not look at +// the reference type at all. +struct OpaqueRef +{ +}; + +template +struct FakeIterator +{ + using iterator_category = std::random_access_iterator_tag; + using value_type = _ValueType; + using difference_type = std::ptrdiff_t; + using pointer = void; + using reference = _ReferenceType; + + reference + operator*() const; +}; + +//----------------------------------------------------------------------------// +// __is_value_storable_v +//----------------------------------------------------------------------------// + +// Accepted: the value type is storable in the reduction object. +static_assert(dpl_unseq::__is_value_storable_v); +static_assert(dpl_unseq::__is_value_storable_v); +static_assert(dpl_unseq::__is_value_storable_v::iterator>); +static_assert(dpl_unseq::__is_value_storable_v::const_iterator>); +static_assert(dpl_unseq::__is_value_storable_v); +static_assert(dpl_unseq::__is_value_storable_v); +// Accepted: the requirements are brace initialization, copy construction and copy assignment, and nothing else. +static_assert(dpl_unseq::__is_value_storable_v); +static_assert(dpl_unseq::__is_value_storable_v); +static_assert(dpl_unseq::__is_value_storable_v); +// Accepted: copying and storing a value only ever reads it through a const reference. +static_assert(dpl_unseq::__is_value_storable_v); +// The reference type is not part of the requirement, so a proxy reference and an iterator returning the value type by +// value are accepted like any other. +static_assert(dpl_unseq::__is_value_storable_v::iterator>); +static_assert(dpl_unseq::__is_value_storable_v>); +static_assert(dpl_unseq::__is_value_storable_v, std::pair>>); +static_assert(dpl_unseq::__is_value_storable_v>); +// Accepted although the bricks do not compile for them: an element of these iterators cannot be copy-initialized into +// the value type, so they do not meet the requirements of a forward iterator, which is not detected here. The value +// types themselves are copy-constructible, which is stated in terms of direct initialization. +static_assert(std::is_copy_constructible_v); +static_assert(dpl_unseq::__is_value_storable_v); +static_assert(dpl_unseq::__is_value_storable_v); +static_assert(dpl_unseq::__is_value_storable_v>); + +// Rejected because of the value type: the first two fail brace initialization, the third copy assignment, and the +// move-only one copy construction, and with it every other requirement that copies a value. +static_assert(!dpl_unseq::__is_value_storable_v); +static_assert(!dpl_unseq::__is_value_storable_v); +static_assert(!dpl_unseq::__is_value_storable_v); +static_assert(!dpl_unseq::__is_value_storable_v); + +// Rejected because an output iterator reports void as its value type. +static_assert(!dpl_unseq::__is_value_storable_v>>); + +int +main() +{ + return TestUtils::done(); +} diff --git a/test/general/implementation_details/value_storable_and_comparable.pass.cpp b/test/general/implementation_details/value_storable_and_comparable.pass.cpp deleted file mode 100644 index 16eab2280f5..00000000000 --- a/test/general/implementation_details/value_storable_and_comparable.pass.cpp +++ /dev/null @@ -1,390 +0,0 @@ -// -*- C++ -*- -//===------------------------------------------------------===// -// -// Copyright (C) 2025 UXL Foundation Contributors -// -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===------------------------------------------------------===// - -// Compile-time checks for oneapi::dpl::__unseq_backend::__is_value_storable_and_comparable_v and for the requirements -// it is built from. -// The only one of them that is not a standard type trait, oneapi::dpl::__unseq_backend::__is_brace_constructible_v, is -// checked on its own as well. Every requirement is checked both ways: a type that satisfies it and a type that does -// not. - -#include "support/test_config.h" - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "support/utils.h" - -namespace dpl_internal = oneapi::dpl::__internal; -namespace dpl_unseq = oneapi::dpl::__unseq_backend; - -//----------------------------------------------------------------------------// -// Value types -//----------------------------------------------------------------------------// - -// Satisfies every requirement: default-constructible, copy-constructible, copy-assignable, less-than comparable. -struct Regular -{ - int val = 0; - bool - operator<(const Regular& other) const - { - return val < other.val; - } -}; - -// An explicit default constructor is enough, since _ValueType{} is a direct initialization, which may use it. -struct ExplicitDefaultCtor -{ - int val; - explicit ExplicitDefaultCtor() : val(0) {} - bool - operator<(const ExplicitDefaultCtor& other) const - { - return val < other.val; - } -}; - -struct ExplicitDefaultCtorMember -{ - int val; - explicit ExplicitDefaultCtorMember() : val(0) {} -}; - -// Default-constructible, but not brace-initializable: an aggregate is initialized member by member, and the member is -// copy-initialized from an empty list, which may not use its explicit default constructor. -struct AggregateOfExplicitDefaultCtor -{ - ExplicitDefaultCtorMember member; - bool - operator<(const AggregateOfExplicitDefaultCtor& other) const - { - return member.val < other.member.val; - } -}; - -struct NoDefaultCtor -{ - int val; - explicit NoDefaultCtor(int v) : val(v) {} - bool - operator<(const NoDefaultCtor& other) const - { - return val < other.val; - } -}; - -struct NoCopyAssign -{ - int val = 0; - NoCopyAssign() = default; - NoCopyAssign(const NoCopyAssign&) = default; - NoCopyAssign& - operator=(const NoCopyAssign&) = delete; - bool - operator<(const NoCopyAssign& other) const - { - return val < other.val; - } -}; - -// The assignment does not return VoidAssign&, which is enough here because the result is never used. -struct VoidAssign -{ - int val = 0; - void - operator=(const VoidAssign& other) - { - val = other.val; - } - bool - operator<(const VoidAssign& other) const - { - return val < other.val; - } -}; - -// The destructor is not noexcept, which is enough here because storing a value never has to be non-throwing. -struct ThrowingDtor -{ - int val = 0; - ~ThrowingDtor() noexcept(false) {} - bool - operator<(const ThrowingDtor& other) const - { - return val < other.val; - } -}; - -struct MoveOnly -{ - int val = 0; - MoveOnly() = default; - MoveOnly(MoveOnly&&) = default; - MoveOnly& - operator=(MoveOnly&&) = default; - MoveOnly(const MoveOnly&) = delete; - MoveOnly& - operator=(const MoveOnly&) = delete; - bool - operator<(const MoveOnly& other) const - { - return val < other.val; - } -}; - -// Deleting the move operations while keeping the copy ones is enough here, because the value is never moved. -struct CopyOnlyNoMove -{ - int val = 0; - CopyOnlyNoMove() = default; - CopyOnlyNoMove(const CopyOnlyNoMove&) = default; - CopyOnlyNoMove& - operator=(const CopyOnlyNoMove&) = default; - CopyOnlyNoMove(CopyOnlyNoMove&&) = delete; - CopyOnlyNoMove& - operator=(CopyOnlyNoMove&&) = delete; - bool - operator<(const CopyOnlyNoMove& other) const - { - return val < other.val; - } -}; - -// Copyable and assignable from a const lvalue only, which is enough here, because the candidates are read through -// std::as_const and the element is materialized as a const _ValueType. -struct ConstCopyOnly -{ - int val = 0; - ConstCopyOnly() = default; - ConstCopyOnly(const ConstCopyOnly&) = default; - ConstCopyOnly& - operator=(const ConstCopyOnly&) = default; - ConstCopyOnly(ConstCopyOnly&) = delete; - ConstCopyOnly& - operator=(ConstCopyOnly&) = delete; - bool - operator<(const ConstCopyOnly& other) const - { - return val < other.val; - } -}; - -// A value type whose copy constructor is explicit, which is enough for copying the candidates, because they are copied -// by direct initialization, and so is std::is_copy_constructible_v defined. Copy-initializing an element of such a type -// is ill-formed, so an iterator over it does not meet the requirements of a forward iterator. -struct ExplicitCopyCtor -{ - int val = 0; - ExplicitCopyCtor() = default; - explicit ExplicitCopyCtor(const ExplicitCopyCtor& other) : val(other.val) {} - ExplicitCopyCtor& - operator=(const ExplicitCopyCtor&) = default; - bool - operator<(const ExplicitCopyCtor&) const - { - return false; - } -}; - -//----------------------------------------------------------------------------// -// __is_brace_constructible_v -//----------------------------------------------------------------------------// - -static_assert(dpl_unseq::__is_brace_constructible_v); -static_assert(dpl_unseq::__is_brace_constructible_v); -static_assert(dpl_unseq::__is_brace_constructible_v); -static_assert(dpl_unseq::__is_brace_constructible_v); -static_assert(dpl_unseq::__is_brace_constructible_v); - -static_assert(std::is_default_constructible_v); -static_assert(!dpl_unseq::__is_brace_constructible_v); -static_assert(!dpl_unseq::__is_brace_constructible_v); - -//----------------------------------------------------------------------------// -// Comparison objects -//----------------------------------------------------------------------------// - -struct NotBool -{ -}; - -struct IntResultLess -{ - int - operator()(const int& lhs, const int& rhs) const - { - return lhs < rhs; - } -}; - -struct NotBoolResultLess -{ - NotBool - operator()(const int&, const int&) const - { - return NotBool{}; - } -}; - -// Requires modifiable arguments, so it cannot be called on const values. -struct MutableRefLess -{ - bool - operator()(int& lhs, int& rhs) const - { - return lhs < rhs; - } -}; - -// Callable on an rvalue only, while the requirement is stated for _Compare&. -struct RvalueOnlyLess -{ - bool - operator()(const int&, const int&) && - { - return false; - } -}; - -struct UnaryLess -{ - bool - operator()(const int&) const - { - return false; - } -}; - -// Not copyable: the requirement is stated for _Compare&, so it must not ask for a copy. -struct MoveOnlyLess -{ - MoveOnlyLess() = default; - MoveOnlyLess(MoveOnlyLess&&) = default; - MoveOnlyLess(const MoveOnlyLess&) = delete; - bool - operator()(const int& lhs, const int& rhs) const - { - return lhs < rhs; - } -}; - -struct NotNegatableResult -{ - operator bool() const; - bool - operator!() const = delete; -}; - -// The result of the comparison is convertible to bool, but cannot be negated, while the vectorized bricks do negate it. -// Such a comparison object does not meet the Compare requirements the standard states for the algorithms, so it is not -// rejected here: the requirement accepts it in both C++17 and C++20, and instantiating the brick for it is a compile -// error rather than a fallback to the serial implementation. -struct NotNegatableResultLess -{ - NotNegatableResult - operator()(const int& lhs, const int& rhs) const; -}; - -//----------------------------------------------------------------------------// -// Reference types -//----------------------------------------------------------------------------// - -// A reference type that does not convert to the value type: an iterator reporting it does not meet the requirements of -// a forward iterator, which state that *__first is convertible to the value type, so the requirement does not look at -// the reference type at all. -struct OpaqueRef -{ -}; - -template -struct FakeIterator -{ - using iterator_category = std::random_access_iterator_tag; - using value_type = _ValueType; - using difference_type = std::ptrdiff_t; - using pointer = void; - using reference = _ReferenceType; - - reference - operator*() const; -}; - -//----------------------------------------------------------------------------// -// __is_value_storable_and_comparable_v -//----------------------------------------------------------------------------// - -// Accepted: the value type is storable and the comparator is callable on const values. -static_assert(dpl_unseq::__is_value_storable_and_comparable_v>); -static_assert(dpl_unseq::__is_value_storable_and_comparable_v>); -static_assert(dpl_unseq::__is_value_storable_and_comparable_v::iterator, std::less>); -static_assert(dpl_unseq::__is_value_storable_and_comparable_v::const_iterator, std::less<>>); -static_assert(dpl_unseq::__is_value_storable_and_comparable_v>); -static_assert(dpl_unseq::__is_value_storable_and_comparable_v>); -// Accepted: the requirements are brace initialization, copy construction and copy assignment, and nothing else. -static_assert(dpl_unseq::__is_value_storable_and_comparable_v>); -static_assert(dpl_unseq::__is_value_storable_and_comparable_v>); -static_assert(dpl_unseq::__is_value_storable_and_comparable_v>); -// Accepted: copying and storing a value only ever reads it through a const reference. -static_assert(dpl_unseq::__is_value_storable_and_comparable_v>); -static_assert(dpl_unseq::__is_value_storable_and_comparable_v); -static_assert(dpl_unseq::__is_value_storable_and_comparable_v); -static_assert(dpl_unseq::__is_value_storable_and_comparable_v); -// Accepted although the bricks do not compile for it: a comparison object that does not meet the Compare requirements -// of the algorithms is not detected here, see NotNegatableResultLess above. -static_assert(dpl_unseq::__is_value_storable_and_comparable_v); -// The comparator max_element passes down to the min_element brick. -static_assert(dpl_unseq::__is_value_storable_and_comparable_v>>); -// The reference type is not part of the requirement, so a proxy reference and an iterator returning the value type by -// value are accepted like any other. -static_assert(dpl_unseq::__is_value_storable_and_comparable_v::iterator, std::less>); -static_assert(dpl_unseq::__is_value_storable_and_comparable_v, std::less>); -static_assert(dpl_unseq::__is_value_storable_and_comparable_v, std::pair>, - std::less>>); -static_assert(dpl_unseq::__is_value_storable_and_comparable_v, - std::less>); -// Accepted although the bricks do not compile for them: an element of these iterators cannot be copy-initialized into -// the value type, so they do not meet the requirements of a forward iterator, which is not detected here either. The -// value types themselves are copy-constructible, which is stated in terms of direct initialization. -static_assert(std::is_copy_constructible_v); -static_assert(dpl_unseq::__is_value_storable_and_comparable_v>); -static_assert(dpl_unseq::__is_value_storable_and_comparable_v>); -static_assert(dpl_unseq::__is_value_storable_and_comparable_v, std::less>); - -// Rejected because of the value type: the first two fail brace initialization, the third copy assignment, and the -// move-only one copy construction, and with it every other requirement that copies a value. -static_assert(!dpl_unseq::__is_value_storable_and_comparable_v>); -static_assert(!dpl_unseq::__is_value_storable_and_comparable_v>); -static_assert(!dpl_unseq::__is_value_storable_and_comparable_v>); -static_assert(!dpl_unseq::__is_value_storable_and_comparable_v>); - -// Rejected because an output iterator reports void as its value type. -static_assert(!dpl_unseq::__is_value_storable_and_comparable_v>, - std::less>); - -// Rejected because of the comparator. -static_assert(!dpl_unseq::__is_value_storable_and_comparable_v); -static_assert(!dpl_unseq::__is_value_storable_and_comparable_v); -static_assert(!dpl_unseq::__is_value_storable_and_comparable_v); -static_assert(!dpl_unseq::__is_value_storable_and_comparable_v); -static_assert(!dpl_unseq::__is_value_storable_and_comparable_v>); -static_assert(!dpl_unseq::__is_value_storable_and_comparable_v); - -int -main() -{ - return TestUtils::done(); -} From fa6adf6e6172a7a91d19ac74c47590160ef885e7 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 8 Sep 2026 15:37:58 +0200 Subject: [PATCH 022/148] include/oneapi/dpl/pstl/unseq_backend_simd.h - fix review comment: remove extra comment from file --- include/oneapi/dpl/pstl/unseq_backend_simd.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/include/oneapi/dpl/pstl/unseq_backend_simd.h b/include/oneapi/dpl/pstl/unseq_backend_simd.h index e0badc64027..a645d117211 100644 --- a/include/oneapi/dpl/pstl/unseq_backend_simd.h +++ b/include/oneapi/dpl/pstl/unseq_backend_simd.h @@ -615,10 +615,6 @@ __simd_scan(_InputIterator __first, _Size __n, _OutputIterator __result, _UnaryO return ::std::make_pair(__result + __n, __init_.__value); } -// The reduction object initializes its value members with _ValueType{}, which is not what -// std::is_default_constructible_v checks: that trait stands for _ValueType v;, and the two differ both ways. An -// aggregate whose member has an explicit default constructor is default-constructible but not brace-initializable, -// while an aggregate with a const member without a default member initializer is the other way round. template inline constexpr bool __is_brace_constructible_v = false; From d001a0ea3befd2094d9d9a3ccb51eca944d00cf1 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 8 Sep 2026 16:11:25 +0200 Subject: [PATCH 023/148] Extra test coverage for the value type which is not default-constructible, but it is brace-initializable --- .../value_storable.pass.cpp | 17 +++++++++++++++ .../alg.min.max/minmax_element.pass.cpp | 21 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/test/general/implementation_details/value_storable.pass.cpp b/test/general/implementation_details/value_storable.pass.cpp index aad8ed21d83..ffc27d6fef1 100644 --- a/test/general/implementation_details/value_storable.pass.cpp +++ b/test/general/implementation_details/value_storable.pass.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -57,6 +58,15 @@ struct AggregateOfExplicitDefaultCtor ExplicitDefaultCtorMember member; }; +// Brace-initializable, but not default-constructible: with no default constructor declared, empty braces select the +// initializer-list constructor with an empty list, while _ValueType() is ill-formed. The reduction object initializes +// its members with _ValueType{}, so this is enough for it. +struct BraceInitOnly +{ + int val; + BraceInitOnly(std::initializer_list init) : val(init.size() == 0 ? 0 : *init.begin()) {} +}; + struct NoDefaultCtor { int val; @@ -151,8 +161,14 @@ static_assert(dpl_unseq::__is_brace_constructible_v); static_assert(dpl_unseq::__is_brace_constructible_v); static_assert(dpl_unseq::__is_brace_constructible_v); +// The requirement is brace initialization, and the two directions in which it differs from default construction are +// both checked: a type which is default-constructible but not brace-initializable, and one which is the other way +// round. static_assert(std::is_default_constructible_v); static_assert(!dpl_unseq::__is_brace_constructible_v); +static_assert(!std::is_default_constructible_v); +static_assert(dpl_unseq::__is_brace_constructible_v); + static_assert(!dpl_unseq::__is_brace_constructible_v); //----------------------------------------------------------------------------// @@ -190,6 +206,7 @@ static_assert(dpl_unseq::__is_value_storable_v::iterator>); static_assert(dpl_unseq::__is_value_storable_v::const_iterator>); static_assert(dpl_unseq::__is_value_storable_v); static_assert(dpl_unseq::__is_value_storable_v); +static_assert(dpl_unseq::__is_value_storable_v); // Accepted: the requirements are brace initialization, copy construction and copy assignment, and nothing else. static_assert(dpl_unseq::__is_value_storable_v); static_assert(dpl_unseq::__is_value_storable_v); diff --git a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp index 811178c53af..6dd78edb40f 100644 --- a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp +++ b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -305,6 +306,21 @@ struct ExplicitDefaultCtorCompare } }; +// The value type is not default-constructible, but it is brace-initializable: with no default constructor declared, +// empty braces select the initializer-list constructor with an empty list. The vector code path is still applicable for +// it, because the reduction object initializes its members with _ValueType{} and never writes _ValueType(). +struct BraceInitOnlyCompare +{ + std::int32_t val; + BraceInitOnlyCompare(std::initializer_list init) : val(init.size() == 0 ? 0 : *init.begin()) {} + BraceInitOnlyCompare(std::int32_t val_) : val(val_) {} + bool + operator<(const BraceInitOnlyCompare& other) const + { + return val < other.val; + } +}; + // The move operations of the value type are deleted: the vector code path is still applicable for it, because the // vector code never moves a value. struct CopyOnlyNoMoveCompare @@ -557,6 +573,11 @@ main() // by an explicit default constructor, which the path has to accept at compile time, so one size is enough. test_by_type(NSmall); + // This value type is accepted by the vector code path although it is not default-constructible, which is the other + // direction in which brace initialization, the requirement of the vector code path, differs from default + // construction. That does not depend on the sequence size either. + test_by_type_host_policies(NSmall); + // These value types are accepted by the vector code path as well, and the point of checking them is that the vector // code is instantiated for them: each of them violates one of the requirements of std::semiregular that the vector // code does not have. That does not depend on the sequence size either. From 6af2da51b9190f3853ab29774a43b13ad0ac663e Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 8 Sep 2026 17:33:32 +0200 Subject: [PATCH 024/148] unseq_backend_simd.h - fix review comment: remove extra definition of bool __is_value_storable_v --- include/oneapi/dpl/pstl/unseq_backend_simd.h | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/include/oneapi/dpl/pstl/unseq_backend_simd.h b/include/oneapi/dpl/pstl/unseq_backend_simd.h index a645d117211..27e497bf2cb 100644 --- a/include/oneapi/dpl/pstl/unseq_backend_simd.h +++ b/include/oneapi/dpl/pstl/unseq_backend_simd.h @@ -621,19 +621,15 @@ inline constexpr bool __is_brace_constructible_v = false; template inline constexpr bool __is_brace_constructible_v<_Tp, decltype(void(_Tp{}))> = true; -// An output iterator reports void as its value type: such a value cannot be stored, and forming const _ValueType& -// for it would be ill-formed rather than merely unsatisfied, so void is rejected up front. -template ::value_type, - typename = void> -inline constexpr bool __is_value_storable_v = false; - // The requirement covers only what the vectorized bricks add on top of what the algorithms already require: the value // type has to be storable in the reduction object. Anything else, the comparison object included, is not looked at and // fails to compile if it is not met, exactly as it does without this requirement. // Every requirement is the expression the implementation uses rather than the concept it resembles: std::semiregular // would also require moving, an assignment returning _ValueType& and a non-throwing destructor. -template -inline constexpr bool __is_value_storable_v<_Iterator, _ValueType, std::enable_if_t>> = +// void, which an output iterator reports as its value type, needs no separate handling: it is neither copy +// constructible nor copy assignable. +template ::value_type> +inline constexpr bool __is_value_storable_v = __is_brace_constructible_v<_ValueType> && std::is_copy_constructible_v<_ValueType> && std::is_copy_assignable_v<_ValueType>; From 58d57125c220bce671b6f2495dffddcd925952e6 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 8 Sep 2026 17:42:35 +0200 Subject: [PATCH 025/148] Update include/oneapi/dpl/pstl/unseq_backend_simd.h Co-authored-by: Dmitriy Sobolev --- include/oneapi/dpl/pstl/unseq_backend_simd.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/include/oneapi/dpl/pstl/unseq_backend_simd.h b/include/oneapi/dpl/pstl/unseq_backend_simd.h index 27e497bf2cb..05f97f48d0e 100644 --- a/include/oneapi/dpl/pstl/unseq_backend_simd.h +++ b/include/oneapi/dpl/pstl/unseq_backend_simd.h @@ -621,11 +621,10 @@ inline constexpr bool __is_brace_constructible_v = false; template inline constexpr bool __is_brace_constructible_v<_Tp, decltype(void(_Tp{}))> = true; -// The requirement covers only what the vectorized bricks add on top of what the algorithms already require: the value -// type has to be storable in the reduction object. Anything else, the comparison object included, is not looked at and -// fails to compile if it is not met, exactly as it does without this requirement. -// Every requirement is the expression the implementation uses rather than the concept it resembles: std::semiregular -// would also require moving, an assignment returning _ValueType& and a non-throwing destructor. +// Requirements needed by __simd_min_element and __simd_minmax_element implementations: +// - __is_brace_constructible_v: the _ComplexType default constructor needs _ValueType{} to be well-formed. +// - std::is_copy_constructible_v: _ComplexType copy constructor is deleted if _ValueType is not copy constructible. +// - std::is_copy_assignable_v: the _ONEDPL_PRAGMA_SIMD_REDUCTION loop assigns _ValueType. // void, which an output iterator reports as its value type, needs no separate handling: it is neither copy // constructible nor copy assignable. template ::value_type> From d87fb7631cc434955f2be6f9e34ef505b6f995fd Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 8 Sep 2026 17:43:34 +0200 Subject: [PATCH 026/148] Update include/oneapi/dpl/pstl/unseq_backend_simd.h Co-authored-by: Dmitriy Sobolev --- include/oneapi/dpl/pstl/unseq_backend_simd.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/oneapi/dpl/pstl/unseq_backend_simd.h b/include/oneapi/dpl/pstl/unseq_backend_simd.h index 05f97f48d0e..bbccd4088f6 100644 --- a/include/oneapi/dpl/pstl/unseq_backend_simd.h +++ b/include/oneapi/dpl/pstl/unseq_backend_simd.h @@ -625,8 +625,6 @@ inline constexpr bool __is_brace_constructible_v<_Tp, decltype(void(_Tp{}))> = t // - __is_brace_constructible_v: the _ComplexType default constructor needs _ValueType{} to be well-formed. // - std::is_copy_constructible_v: _ComplexType copy constructor is deleted if _ValueType is not copy constructible. // - std::is_copy_assignable_v: the _ONEDPL_PRAGMA_SIMD_REDUCTION loop assigns _ValueType. -// void, which an output iterator reports as its value type, needs no separate handling: it is neither copy -// constructible nor copy assignable. template ::value_type> inline constexpr bool __is_value_storable_v = __is_brace_constructible_v<_ValueType> && std::is_copy_constructible_v<_ValueType> && From 2f91d9e8f5653963162ed4c7e0f72f7c3e4c6cd7 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 8 Sep 2026 17:53:26 +0200 Subject: [PATCH 027/148] Update include/oneapi/dpl/pstl/unseq_backend_simd.h Co-authored-by: Dmitriy Sobolev --- include/oneapi/dpl/pstl/unseq_backend_simd.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/oneapi/dpl/pstl/unseq_backend_simd.h b/include/oneapi/dpl/pstl/unseq_backend_simd.h index bbccd4088f6..5a0b0b940be 100644 --- a/include/oneapi/dpl/pstl/unseq_backend_simd.h +++ b/include/oneapi/dpl/pstl/unseq_backend_simd.h @@ -630,8 +630,6 @@ inline constexpr bool __is_value_storable_v = __is_brace_constructible_v<_ValueType> && std::is_copy_constructible_v<_ValueType> && std::is_copy_assignable_v<_ValueType>; -// The implementation keeps copies of the values in the reduction object, so the value type has to be usable in a -// user-defined reduction: __is_value_storable_v is the requirement checked by the callers. // complexity [violation] - We will have at most (__n-1 + number_of_lanes) comparisons instead of at most __n-1. template _ForwardIterator From 5daf9ff9657142b714322176d2c5e28cca5db4e2 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Wed, 9 Sep 2026 09:35:49 +0200 Subject: [PATCH 028/148] minmax_element.pass.cpp - fix review comment: replace ::std:: -> std:: --- .../alg.min.max/minmax_element.pass.cpp | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp index 6dd78edb40f..b2c95cca9ae 100644 --- a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp +++ b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp @@ -43,7 +43,7 @@ struct check_minelement void operator()(Policy&& exec, Iterator begin, Iterator end) { - const Iterator expect = ::std::min_element(begin, end); + const Iterator expect = std::min_element(begin, end); const Iterator result = std::min_element(std::forward(exec), begin, end); EXPECT_EQ(expect, result, "wrong return result from min_element"); } @@ -57,7 +57,7 @@ struct check_minelement_predicate operator()(Policy&& exec, Iterator begin, Iterator end) { using T = typename std::iterator_traits::value_type; - const Iterator expect = ::std::min_element(begin, end); + const Iterator expect = std::min_element(begin, end); const Iterator result_pred = std::min_element(std::forward(exec), begin, end, std::less()); EXPECT_EQ(expect, result_pred, "wrong return result from min_element with predicate"); } @@ -70,7 +70,7 @@ struct check_maxelement void operator()(Policy&& exec, Iterator begin, Iterator end) { - const Iterator expect = ::std::max_element(begin, end); + const Iterator expect = std::max_element(begin, end); const Iterator result = std::max_element(std::forward(exec), begin, end); EXPECT_EQ(expect, result, "wrong return result from max_element"); } @@ -84,7 +84,7 @@ struct check_maxelement_predicate operator()(Policy&& exec, Iterator begin, Iterator end) { using T = typename std::iterator_traits::value_type; - const Iterator expect = ::std::max_element(begin, end); + const Iterator expect = std::max_element(begin, end); const Iterator result_pred = std::max_element(std::forward(exec), begin, end, std::less()); EXPECT_EQ(expect, result_pred, "wrong return result from max_element with predicate"); } @@ -97,7 +97,7 @@ struct check_minmaxelement void operator()(Policy&& exec, Iterator begin, Iterator end) { - const ::std::pair expect = ::std::minmax_element(begin, end); + const std::pair expect = std::minmax_element(begin, end); const std::pair got = std::minmax_element(std::forward(exec), begin, end); EXPECT_EQ(expect.first, got.first, "wrong return result from minmax_element (min part)"); EXPECT_EQ(expect.second, got.second, "wrong return result from minmax_element (max part)"); @@ -112,7 +112,7 @@ struct check_minmaxelement_predicate operator()(Policy&& exec, Iterator begin, Iterator end) { using T = typename std::iterator_traits::value_type; - const ::std::pair expect = ::std::minmax_element(begin, end); + const std::pair expect = std::minmax_element(begin, end); const std::pair got_pred = std::minmax_element(std::forward(exec), begin, end, std::less()); EXPECT_EQ(expect, got_pred, "wrong return result from minmax_element with predicate"); } @@ -142,7 +142,7 @@ struct check_minelement_overloaded_address_of void operator()(Policy&& exec, Iterator begin, Iterator end) { - const Iterator expect = ::std::min_element(begin, end); + const Iterator expect = std::min_element(begin, end); const Iterator result = std::min_element(std::forward(exec), begin, end, OverloadedAddressOfLess()); EXPECT_EQ(expect, result, "wrong return result from min_element with a comparator overloading operator&"); } @@ -155,7 +155,7 @@ struct check_maxelement_overloaded_address_of void operator()(Policy&& exec, Iterator begin, Iterator end) { - const Iterator expect = ::std::max_element(begin, end); + const Iterator expect = std::max_element(begin, end); const Iterator result = std::max_element(std::forward(exec), begin, end, OverloadedAddressOfLess()); EXPECT_EQ(expect, result, "wrong return result from max_element with a comparator overloading operator&"); } @@ -168,7 +168,7 @@ struct check_minmaxelement_overloaded_address_of void operator()(Policy&& exec, Iterator begin, Iterator end) { - const ::std::pair expect = ::std::minmax_element(begin, end); + const std::pair expect = std::minmax_element(begin, end); const std::pair got = std::minmax_element(std::forward(exec), begin, end, OverloadedAddressOfLess()); EXPECT_EQ(expect, got, "wrong return result from minmax_element with a comparator overloading operator&"); @@ -181,40 +181,40 @@ struct sequence_wrapper TestUtils::Sequence seq; const T min_value; const T max_value; - static const ::std::size_t bits = 30; // We assume that T can handle signed 2^bits+1 value + static const std::size_t bits = 30; // We assume that T can handle signed 2^bits+1 value // TestUtils::HashBits returns value between 0 and (1< T { return T(TestUtils::HashBits(i, bits)); }); + seq.fill([](std::size_t i) -> T { return T(TestUtils::HashBits(i, bits)); }); } // sets first one at position `at` and bunch of them farther void - set_desired_value(::std::size_t at, T value) + set_desired_value(std::size_t at, T value) { if (seq.size() == 0) return; seq[at] = value; //Producing several red herrings - for (::std::size_t i = at + 1; i < seq.size(); i += 1 + TestUtils::HashBits(i, 5)) + for (std::size_t i = at + 1; i < seq.size(); i += 1 + TestUtils::HashBits(i, 5)) seq[i] = value; } }; template void -test_by_type(::std::size_t n) +test_by_type(std::size_t n) { sequence_wrapper wseq(n); - // to avoid overtesing we use ::std::set to leave only unique indexes - ::std::set<::std::size_t> targets{0}; + // to avoid overtesing we use std::set to leave only unique indexes + std::set targets{0}; if (n > 1) { targets.insert(1); @@ -224,7 +224,7 @@ test_by_type(::std::size_t n) targets.insert(n - 1); // last } - for (::std::set<::std::size_t>::iterator it = targets.begin(); it != targets.end(); ++it) + for (std::set::iterator it = targets.begin(); it != targets.end(); ++it) { wseq.pattern_fill(); #ifdef _PSTL_TEST_MIN_ELEMENT @@ -250,7 +250,7 @@ test_by_type(::std::size_t n) #ifdef _PSTL_TEST_MINMAX_ELEMENT if (targets.size() > 1) { - for (::std::set<::std::size_t>::reverse_iterator rit = targets.rbegin(); rit != targets.rend(); ++rit) + for (std::set::reverse_iterator rit = targets.rbegin(); rit != targets.rend(); ++rit) { if (*rit == *it) // we requires at least 2 unique indexes in targets break; @@ -471,15 +471,15 @@ check_by_type_host_policies(Iterator first, Iterator last) // cannot be checked with device policies. template static void -test_by_type_host_policies(::std::size_t n) +test_by_type_host_policies(std::size_t n) { - ::std::vector data; + std::vector data; data.reserve(n); - for (::std::size_t i = 0; i < n; ++i) + for (std::size_t i = 0; i < n; ++i) data.emplace_back(std::int32_t(TestUtils::HashBits(i, 30))); - using Iterator = ::std::conditional_t::const_iterator, - typename ::std::vector::iterator>; + using Iterator = std::conditional_t::const_iterator, + typename std::vector::iterator>; check_by_type_host_policies(Iterator(data.begin()), Iterator(data.end())); } @@ -490,10 +490,10 @@ test_by_type_host_policies(::std::size_t n) // sequence. template static void -test_by_type_host_policies_no_move(::std::size_t n) +test_by_type_host_policies_no_move(std::size_t n) { - ::std::vector data(n); - for (::std::size_t i = 0; i < n; ++i) + std::vector data(n); + for (std::size_t i = 0; i < n; ++i) { const T value(std::int32_t(TestUtils::HashBits(i, 30))); data[i] = value; @@ -505,9 +505,9 @@ test_by_type_host_policies_no_move(::std::size_t n) // The comparison object is passed to min_element and minmax_element as is, so the vector code path takes its address // there, and to max_element wrapped into an internal predicate which reorders the arguments. static void -test_comparator_with_overloaded_address_of(::std::size_t n) +test_comparator_with_overloaded_address_of(std::size_t n) { - Sequence in(n, [](::std::size_t i) { return std::int32_t(TestUtils::HashBits(i, 30)); }); + Sequence in(n, [](std::size_t i) { return std::int32_t(TestUtils::HashBits(i, 30)); }); #ifdef _PSTL_TEST_MIN_ELEMENT invoke_on_all_host_policies()(check_minelement_overloaded_address_of(), in.begin(), in.end()); From 2ae97ae36ea3502ff408ff2d195353dff66b52d4 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Wed, 9 Sep 2026 09:37:03 +0200 Subject: [PATCH 029/148] minmax_element.pass.cpp - fix review comment: remove extra comments --- .../alg.sorting/alg.min.max/minmax_element.pass.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp index b2c95cca9ae..66ea963f9c0 100644 --- a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp +++ b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp @@ -573,9 +573,6 @@ main() // by an explicit default constructor, which the path has to accept at compile time, so one size is enough. test_by_type(NSmall); - // This value type is accepted by the vector code path although it is not default-constructible, which is the other - // direction in which brace initialization, the requirement of the vector code path, differs from default - // construction. That does not depend on the sequence size either. test_by_type_host_policies(NSmall); // These value types are accepted by the vector code path as well, and the point of checking them is that the vector @@ -596,9 +593,6 @@ main() test_by_type_host_policies(NSmall); test_by_type_host_policies(NSmall); - // The comparison object of this check overloads unary operator&, so the point of it is that the vector code path - // takes the address of the comparison object with std::addressof: with & it does not compile. The sequence is long - // enough for the vector code to process several blocks and to combine their results. test_comparator_with_overloaded_address_of(1000); #ifdef _PSTL_TEST_MIN_ELEMENT From 95333a730b397c69463f11e71b9a2a8d1913a614 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Wed, 9 Sep 2026 09:37:53 +0200 Subject: [PATCH 030/148] Apply suggestion from @dmitriy-sobolev Co-authored-by: Dmitriy Sobolev --- include/oneapi/dpl/pstl/unseq_backend_simd.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/oneapi/dpl/pstl/unseq_backend_simd.h b/include/oneapi/dpl/pstl/unseq_backend_simd.h index 5a0b0b940be..808bd331a62 100644 --- a/include/oneapi/dpl/pstl/unseq_backend_simd.h +++ b/include/oneapi/dpl/pstl/unseq_backend_simd.h @@ -679,8 +679,7 @@ __simd_min_element(_ForwardIterator __first, _Size __n, _Compare __comp) noexcep _ONEDPL_PRAGMA_SIMD_REDUCTION(__min_func : __init) for (_Size __i = 1; __i < __n; ++__i) { - // The candidate is read through a const reference and copied by direct initialization, so that copying it - // requires nothing but std::is_copy_constructible_v, which is stated in terms of direct initialization too. + // std::as_const соответствует std::is_copy_constructible_v, создавая его из const _ValueType&. const _ValueType __min_val(std::as_const(__init).__min_val); const _ValueType __current = __first[__i]; if (std::invoke(__comp, __current, __min_val)) From 2fd7f85bd8e5f1a60a0c5f9066978f820b26e2d9 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Wed, 9 Sep 2026 09:38:07 +0200 Subject: [PATCH 031/148] Apply suggestion from @dmitriy-sobolev Co-authored-by: Dmitriy Sobolev --- include/oneapi/dpl/pstl/unseq_backend_simd.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/oneapi/dpl/pstl/unseq_backend_simd.h b/include/oneapi/dpl/pstl/unseq_backend_simd.h index 808bd331a62..6ac8810e0e8 100644 --- a/include/oneapi/dpl/pstl/unseq_backend_simd.h +++ b/include/oneapi/dpl/pstl/unseq_backend_simd.h @@ -691,8 +691,6 @@ __simd_min_element(_ForwardIterator __first, _Size __n, _Compare __comp) noexcep return __first + __init.__min_ind; } -// The implementation keeps copies of the values in the reduction object, so the value type has to be usable in a -// user-defined reduction: __is_value_storable_v is the requirement checked by the callers. // complexity [violation] - We will have at most (2*(__n-1) + 4*number_of_lanes) comparisons instead of at most [1.5*(__n-1)]. template std::pair<_ForwardIterator, _ForwardIterator> From 0acac49cca5a876f82a750a47f4b6f474f38def1 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Wed, 9 Sep 2026 09:38:25 +0200 Subject: [PATCH 032/148] Apply suggestion from @dmitriy-sobolev Co-authored-by: Dmitriy Sobolev --- include/oneapi/dpl/pstl/unseq_backend_simd.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/include/oneapi/dpl/pstl/unseq_backend_simd.h b/include/oneapi/dpl/pstl/unseq_backend_simd.h index 6ac8810e0e8..44b79e1893f 100644 --- a/include/oneapi/dpl/pstl/unseq_backend_simd.h +++ b/include/oneapi/dpl/pstl/unseq_backend_simd.h @@ -760,9 +760,7 @@ __simd_minmax_element(_ForwardIterator __first, _Size __n, _Compare __comp) noex _ONEDPL_PRAGMA_SIMD_REDUCTION(__min_func : __init) for (_Size __i = 1; __i < __n; ++__i) { - // The candidates are read through a const reference and copied by direct initialization, and the element is - // materialized as a _ValueType, so that copying and storing them requires nothing but - // std::is_copy_constructible_v and std::is_copy_assignable_v. + // std::as_const matches the std::is_copy_constructible_v requirement by constructing from const _ValueType& const _ValueType __min_val(std::as_const(__init).__min_val); const _ValueType __max_val(std::as_const(__init).__max_val); const _ValueType __current = __first[__i]; From e3e15662cb336d75c4241215b6bd4c8bcbeb9f53 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Wed, 9 Sep 2026 09:52:04 +0200 Subject: [PATCH 033/148] Revert "minmax_element.pass.cpp - fix review comment: remove extra comments" This reverts commit 2ae97ae36ea3502ff408ff2d195353dff66b52d4. --- .../alg.sorting/alg.min.max/minmax_element.pass.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp index 66ea963f9c0..b2c95cca9ae 100644 --- a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp +++ b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp @@ -573,6 +573,9 @@ main() // by an explicit default constructor, which the path has to accept at compile time, so one size is enough. test_by_type(NSmall); + // This value type is accepted by the vector code path although it is not default-constructible, which is the other + // direction in which brace initialization, the requirement of the vector code path, differs from default + // construction. That does not depend on the sequence size either. test_by_type_host_policies(NSmall); // These value types are accepted by the vector code path as well, and the point of checking them is that the vector @@ -593,6 +596,9 @@ main() test_by_type_host_policies(NSmall); test_by_type_host_policies(NSmall); + // The comparison object of this check overloads unary operator&, so the point of it is that the vector code path + // takes the address of the comparison object with std::addressof: with & it does not compile. The sequence is long + // enough for the vector code to process several blocks and to combine their results. test_comparator_with_overloaded_address_of(1000); #ifdef _PSTL_TEST_MIN_ELEMENT From 1a0e3d5cb4a2bdb45076a93054b95a0328dc5788 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Wed, 9 Sep 2026 15:14:03 +0200 Subject: [PATCH 034/148] Pack comments --- .../value_storable.pass.cpp | 53 +++++--------- .../alg.min.max/minmax_element.pass.cpp | 73 +++++-------------- 2 files changed, 36 insertions(+), 90 deletions(-) diff --git a/test/general/implementation_details/value_storable.pass.cpp b/test/general/implementation_details/value_storable.pass.cpp index ffc27d6fef1..d6d11f3a68a 100644 --- a/test/general/implementation_details/value_storable.pass.cpp +++ b/test/general/implementation_details/value_storable.pass.cpp @@ -7,11 +7,7 @@ // //===------------------------------------------------------===// -// Compile-time checks for oneapi::dpl::__unseq_backend::__is_value_storable_v and for the requirements it is built -// from. -// The only one of them that is not a standard type trait, oneapi::dpl::__unseq_backend::__is_brace_constructible_v, is -// checked on its own as well. Every requirement is checked both ways: a type that satisfies it and a type that does -// not. +// Compile-time checks for oneapi::dpl::__unseq_backend::__is_value_storable_v and __is_brace_constructible_v. #include "support/test_config.h" @@ -38,7 +34,6 @@ struct Regular int val = 0; }; -// An explicit default constructor is enough, since _ValueType{} is a direct initialization, which may use it. struct ExplicitDefaultCtor { int val; @@ -51,16 +46,14 @@ struct ExplicitDefaultCtorMember explicit ExplicitDefaultCtorMember() : val(0) {} }; -// Default-constructible, but not brace-initializable: an aggregate is initialized member by member, and the member is -// copy-initialized from an empty list, which may not use its explicit default constructor. +// Default-constructible, but not brace-initializable: the member is copy-initialized from an empty list, which may not +// use its explicit default constructor. struct AggregateOfExplicitDefaultCtor { ExplicitDefaultCtorMember member; }; -// Brace-initializable, but not default-constructible: with no default constructor declared, empty braces select the -// initializer-list constructor with an empty list, while _ValueType() is ill-formed. The reduction object initializes -// its members with _ValueType{}, so this is enough for it. +// Brace-initializable, but not default-constructible: empty braces select the initializer-list constructor. struct BraceInitOnly { int val; @@ -82,7 +75,7 @@ struct NoCopyAssign operator=(const NoCopyAssign&) = delete; }; -// The assignment does not return VoidAssign&, which is enough here because the result is never used. +// The copy assignment returns void instead of VoidAssign&. struct VoidAssign { int val = 0; @@ -93,7 +86,7 @@ struct VoidAssign } }; -// The destructor is not noexcept, which is enough here because storing a value never has to be non-throwing. +// The destructor is not noexcept. struct ThrowingDtor { int val = 0; @@ -112,7 +105,7 @@ struct MoveOnly operator=(const MoveOnly&) = delete; }; -// Deleting the move operations while keeping the copy ones is enough here, because the value is never moved. +// Copyable, but with deleted move operations. struct CopyOnlyNoMove { int val = 0; @@ -125,8 +118,7 @@ struct CopyOnlyNoMove operator=(CopyOnlyNoMove&&) = delete; }; -// Copyable and assignable from a const lvalue only, which is enough here, because the candidates are read through -// std::as_const and the element is materialized as a const _ValueType. +// Copyable and assignable from a const lvalue only. struct ConstCopyOnly { int val = 0; @@ -139,9 +131,7 @@ struct ConstCopyOnly operator=(ConstCopyOnly&) = delete; }; -// A value type whose copy constructor is explicit, which is enough for copying the candidates, because they are copied -// by direct initialization, and so is std::is_copy_constructible_v defined. Copy-initializing an element of such a type -// is ill-formed, so an iterator over it does not meet the requirements of a forward iterator. +// The copy constructor is explicit, so the type is copy-constructible, but its elements cannot be copy-initialized. struct ExplicitCopyCtor { int val = 0; @@ -161,9 +151,7 @@ static_assert(dpl_unseq::__is_brace_constructible_v); static_assert(dpl_unseq::__is_brace_constructible_v); static_assert(dpl_unseq::__is_brace_constructible_v); -// The requirement is brace initialization, and the two directions in which it differs from default construction are -// both checked: a type which is default-constructible but not brace-initializable, and one which is the other way -// round. +// Brace initialization differs from default construction in both directions. static_assert(std::is_default_constructible_v); static_assert(!dpl_unseq::__is_brace_constructible_v); static_assert(!std::is_default_constructible_v); @@ -175,9 +163,7 @@ static_assert(!dpl_unseq::__is_brace_constructible_v); // Reference types //----------------------------------------------------------------------------// -// A reference type that does not convert to the value type: an iterator reporting it does not meet the requirements of -// a forward iterator, which state that *__first is convertible to the value type, so the requirement does not look at -// the reference type at all. +// A reference type that does not convert to the value type. struct OpaqueRef { }; @@ -199,7 +185,7 @@ struct FakeIterator // __is_value_storable_v //----------------------------------------------------------------------------// -// Accepted: the value type is storable in the reduction object. +// Accepted value types. static_assert(dpl_unseq::__is_value_storable_v); static_assert(dpl_unseq::__is_value_storable_v); static_assert(dpl_unseq::__is_value_storable_v::iterator>); @@ -207,28 +193,25 @@ static_assert(dpl_unseq::__is_value_storable_v::const_iterator> static_assert(dpl_unseq::__is_value_storable_v); static_assert(dpl_unseq::__is_value_storable_v); static_assert(dpl_unseq::__is_value_storable_v); -// Accepted: the requirements are brace initialization, copy construction and copy assignment, and nothing else. +// The requirements are brace initialization, copy construction and copy assignment, and nothing else. static_assert(dpl_unseq::__is_value_storable_v); static_assert(dpl_unseq::__is_value_storable_v); static_assert(dpl_unseq::__is_value_storable_v); -// Accepted: copying and storing a value only ever reads it through a const reference. static_assert(dpl_unseq::__is_value_storable_v); -// The reference type is not part of the requirement, so a proxy reference and an iterator returning the value type by -// value are accepted like any other. +// The reference type is not part of the requirement. static_assert(dpl_unseq::__is_value_storable_v::iterator>); static_assert(dpl_unseq::__is_value_storable_v>); static_assert(dpl_unseq::__is_value_storable_v, std::pair>>); static_assert(dpl_unseq::__is_value_storable_v>); -// Accepted although the bricks do not compile for them: an element of these iterators cannot be copy-initialized into -// the value type, so they do not meet the requirements of a forward iterator, which is not detected here. The value -// types themselves are copy-constructible, which is stated in terms of direct initialization. +// Accepted although the bricks do not compile for them: these iterators do not meet the requirements of a forward +// iterator, which is not detected here. static_assert(std::is_copy_constructible_v); static_assert(dpl_unseq::__is_value_storable_v); static_assert(dpl_unseq::__is_value_storable_v); static_assert(dpl_unseq::__is_value_storable_v>); -// Rejected because of the value type: the first two fail brace initialization, the third copy assignment, and the -// move-only one copy construction, and with it every other requirement that copies a value. +// Rejected because of the value type: the first two fail brace initialization, the third copy assignment, and the last +// one copy construction. static_assert(!dpl_unseq::__is_value_storable_v); static_assert(!dpl_unseq::__is_value_storable_v); static_assert(!dpl_unseq::__is_value_storable_v); diff --git a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp index b2c95cca9ae..d3555bb3d09 100644 --- a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp +++ b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp @@ -118,9 +118,7 @@ struct check_minmaxelement_predicate } }; -// The comparison object overloads unary operator&, which a user-defined functor is allowed to do, so the vector code -// path has to take its address with std::addressof. Both overloads are deleted, therefore taking the address with & -// does not compile. +// Unary operator& is deleted, so the address of the comparator may only be taken with std::addressof. struct OverloadedAddressOfLess { void @@ -291,9 +289,7 @@ struct OnlyLessCompare } }; -// The value type is default-constructible, but only through an explicit default constructor: -// the vector code path is still applicable for it, because the reduction object initializes its -// members with direct-list-initialization. +// Default-constructible through an explicit default constructor only. struct ExplicitDefaultCtorCompare { std::int32_t val; @@ -306,9 +302,7 @@ struct ExplicitDefaultCtorCompare } }; -// The value type is not default-constructible, but it is brace-initializable: with no default constructor declared, -// empty braces select the initializer-list constructor with an empty list. The vector code path is still applicable for -// it, because the reduction object initializes its members with _ValueType{} and never writes _ValueType(). +// Not default-constructible, but brace-initializable: empty braces select the initializer-list constructor. struct BraceInitOnlyCompare { std::int32_t val; @@ -321,8 +315,7 @@ struct BraceInitOnlyCompare } }; -// The move operations of the value type are deleted: the vector code path is still applicable for it, because the -// vector code never moves a value. +// Copyable, but with deleted move operations. struct CopyOnlyNoMoveCompare { std::int32_t val; @@ -341,8 +334,7 @@ struct CopyOnlyNoMoveCompare } }; -// The assignment of the value type does not return VoidAssignCompare&: the vector code path is still applicable for it, -// because the vector code never uses the result of an assignment. +// The copy assignment returns void instead of VoidAssignCompare&. struct VoidAssignCompare { std::int32_t val; @@ -360,8 +352,7 @@ struct VoidAssignCompare } }; -// The destructor of the value type is not noexcept: the vector code path is still applicable for it, because storing -// a value never has to be non-throwing. +// The destructor is not noexcept. struct ThrowingDtorCompare { std::int32_t val; @@ -375,9 +366,7 @@ struct ThrowingDtorCompare } }; -// The value type can be copied and assigned from a const lvalue only. The vector code path is still applicable for it, -// because the vector code reads both the elements and the stored candidates through const references, but it requires -// const iterators here: the reference type of a non-const iterator does not convert to such a value type. +// Copyable and assignable from a const lvalue only, so it requires const iterators. struct ConstCopyOnlyCompare { std::int32_t val; @@ -396,10 +385,7 @@ struct ConstCopyOnlyCompare } }; -// The value type is not default-constructible, so it cannot be used in a user-defined reduction -// and the vector code path must not be selected for it. The same holds for NoCopyAssignCompare and -// MoveOnlyCompare below: each of them violates one of the requirements the vector code path puts on the -// value type, so each of them fails to compile once that path is selected. +// Not default-constructible. struct NoDefaultCtorCompare { std::int32_t val; @@ -411,8 +397,7 @@ struct NoDefaultCtorCompare } }; -// The value type is not copy-assignable, so it cannot be used in a user-defined reduction -// and the vector code path must not be selected for it. +// Not copy-assignable. struct NoCopyAssignCompare { std::int32_t val; @@ -428,8 +413,7 @@ struct NoCopyAssignCompare } }; -// The value type is not copy-constructible, so it cannot be used in a user-defined reduction -// and the vector code path must not be selected for it. +// Not copy-constructible. struct MoveOnlyCompare { std::int32_t val; @@ -466,9 +450,7 @@ check_by_type_host_policies(Iterator first, Iterator last) #endif } -// The sequence is built in place because the value types checked here either do not satisfy the requirements of -// TestUtils::Sequence (which default-constructs and assigns its elements) or are not trivially copyable, and thus -// cannot be checked with device policies. +// The value types checked here do not satisfy the requirements of TestUtils::Sequence, so the data is built in place. template static void test_by_type_host_policies(std::size_t n) @@ -483,11 +465,9 @@ test_by_type_host_policies(std::size_t n) check_by_type_host_policies(Iterator(data.begin()), Iterator(data.end())); } -// A value type with deleted move operations cannot be added to a std::vector, because the growth path of the container -// moves its elements, so the sequence is sized up front here and its elements are assigned from const lvalues. A plain -// array is deliberately not used: with the bounds of the storage known at compile time, GCC reports a false -// out-of-bounds subscript in the parallel reduction, which never dereferences its identity iterator, the end of the -// sequence. +// A type with deleted move operations cannot be pushed into a std::vector, so the vector is sized up front and its +// elements are assigned. A plain array is not used on purpose: with the bounds known at compile time, GCC reports a +// false out-of-bounds subscript in the parallel reduction. template static void test_by_type_host_policies_no_move(std::size_t n) @@ -502,8 +482,6 @@ test_by_type_host_policies_no_move(std::size_t n) check_by_type_host_policies(data.begin(), data.end()); } -// The comparison object is passed to min_element and minmax_element as is, so the vector code path takes its address -// there, and to max_element wrapped into an internal predicate which reorders the arguments. static void test_comparator_with_overloaded_address_of(std::size_t n) { @@ -569,36 +547,21 @@ main() test_by_type(n); } - // This value type is accepted by the vector code path, exactly like OnlyLessCompare above: it differs from it only - // by an explicit default constructor, which the path has to accept at compile time, so one size is enough. + // These value types are accepted by the vector code path: it must be instantiated for them. Whether it compiles + // does not depend on the sequence size, so a single small size is enough for all the checks below. test_by_type(NSmall); - - // This value type is accepted by the vector code path although it is not default-constructible, which is the other - // direction in which brace initialization, the requirement of the vector code path, differs from default - // construction. That does not depend on the sequence size either. test_by_type_host_policies(NSmall); - - // These value types are accepted by the vector code path as well, and the point of checking them is that the vector - // code is instantiated for them: each of them violates one of the requirements of std::semiregular that the vector - // code does not have. That does not depend on the sequence size either. test_by_type_host_policies_no_move(NSmall); test_by_type_host_policies(NSmall); test_by_type_host_policies(NSmall); - - // This value type is accepted by the vector code path through const iterators, so the point of checking it is that - // the vector code copies the elements and the stored candidates from const lvalues only. That does not depend on - // the sequence size either. test_by_type_host_policies(NSmall); - // These value types are rejected by the vector code path, so the point of checking them is that the call compiles - // and falls back to the serial implementation. That does not depend on the sequence size, so one size is enough. + // These value types are rejected by the vector code path: the call must compile and fall back to the serial one. test_by_type_host_policies(NSmall); test_by_type_host_policies(NSmall); test_by_type_host_policies(NSmall); - // The comparison object of this check overloads unary operator&, so the point of it is that the vector code path - // takes the address of the comparison object with std::addressof: with & it does not compile. The sequence is long - // enough for the vector code to process several blocks and to combine their results. + // The sequence is long enough for the vector code to process several blocks and to combine their results. test_comparator_with_overloaded_address_of(1000); #ifdef _PSTL_TEST_MIN_ELEMENT From dac74a0ec5d554a8854a3f352b9fab858a00d9e5 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Wed, 9 Sep 2026 16:04:14 +0200 Subject: [PATCH 035/148] unseq_backend_simd.h - remove redundand comment --- include/oneapi/dpl/pstl/unseq_backend_simd.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/oneapi/dpl/pstl/unseq_backend_simd.h b/include/oneapi/dpl/pstl/unseq_backend_simd.h index 44b79e1893f..373a4b7ee8d 100644 --- a/include/oneapi/dpl/pstl/unseq_backend_simd.h +++ b/include/oneapi/dpl/pstl/unseq_backend_simd.h @@ -679,7 +679,6 @@ __simd_min_element(_ForwardIterator __first, _Size __n, _Compare __comp) noexcep _ONEDPL_PRAGMA_SIMD_REDUCTION(__min_func : __init) for (_Size __i = 1; __i < __n; ++__i) { - // std::as_const соответствует std::is_copy_constructible_v, создавая его из const _ValueType&. const _ValueType __min_val(std::as_const(__init).__min_val); const _ValueType __current = __first[__i]; if (std::invoke(__comp, __current, __min_val)) @@ -760,7 +759,6 @@ __simd_minmax_element(_ForwardIterator __first, _Size __n, _Compare __comp) noex _ONEDPL_PRAGMA_SIMD_REDUCTION(__min_func : __init) for (_Size __i = 1; __i < __n; ++__i) { - // std::as_const matches the std::is_copy_constructible_v requirement by constructing from const _ValueType& const _ValueType __min_val(std::as_const(__init).__min_val); const _ValueType __max_val(std::as_const(__init).__max_val); const _ValueType __current = __first[__i]; From 43a62000635c8da80fdea340e8d89ee20810462d Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Thu, 10 Sep 2026 09:48:18 +0200 Subject: [PATCH 036/148] test/general/implementation_details/value_storable.pass.cpp - fix review comment: remove ExplicitDefaultCtorMember --- .../implementation_details/value_storable.pass.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/test/general/implementation_details/value_storable.pass.cpp b/test/general/implementation_details/value_storable.pass.cpp index d6d11f3a68a..116b7f17bbe 100644 --- a/test/general/implementation_details/value_storable.pass.cpp +++ b/test/general/implementation_details/value_storable.pass.cpp @@ -40,17 +40,11 @@ struct ExplicitDefaultCtor explicit ExplicitDefaultCtor() : val(0) {} }; -struct ExplicitDefaultCtorMember -{ - int val; - explicit ExplicitDefaultCtorMember() : val(0) {} -}; - // Default-constructible, but not brace-initializable: the member is copy-initialized from an empty list, which may not // use its explicit default constructor. struct AggregateOfExplicitDefaultCtor { - ExplicitDefaultCtorMember member; + ExplicitDefaultCtor member; }; // Brace-initializable, but not default-constructible: empty braces select the initializer-list constructor. From ed6b94ce1fa0b93913914701f8e6f2a12e42802a Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Thu, 10 Sep 2026 09:51:43 +0200 Subject: [PATCH 037/148] test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp - fix review comment: remove ThrowingDtorCompare --- .../alg.min.max/minmax_element.pass.cpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp index d3555bb3d09..b44c8c483b3 100644 --- a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp +++ b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp @@ -352,20 +352,6 @@ struct VoidAssignCompare } }; -// The destructor is not noexcept. -struct ThrowingDtorCompare -{ - std::int32_t val; - ThrowingDtorCompare() : val(0) {} - ThrowingDtorCompare(std::int32_t val_) : val(val_) {} - ~ThrowingDtorCompare() noexcept(false) {} - bool - operator<(const ThrowingDtorCompare& other) const - { - return val < other.val; - } -}; - // Copyable and assignable from a const lvalue only, so it requires const iterators. struct ConstCopyOnlyCompare { @@ -553,7 +539,6 @@ main() test_by_type_host_policies(NSmall); test_by_type_host_policies_no_move(NSmall); test_by_type_host_policies(NSmall); - test_by_type_host_policies(NSmall); test_by_type_host_policies(NSmall); // These value types are rejected by the vector code path: the call must compile and fall back to the serial one. From b0ef6b127f3df3d0c1db2e8d18a4ba8e300e03be Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Thu, 10 Sep 2026 09:53:25 +0200 Subject: [PATCH 038/148] test/general/implementation_details/value_storable.pass.cpp - fix review comment: remove ThrowingDtor --- .../implementation_details/value_storable.pass.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/test/general/implementation_details/value_storable.pass.cpp b/test/general/implementation_details/value_storable.pass.cpp index 116b7f17bbe..53875b174e1 100644 --- a/test/general/implementation_details/value_storable.pass.cpp +++ b/test/general/implementation_details/value_storable.pass.cpp @@ -80,13 +80,6 @@ struct VoidAssign } }; -// The destructor is not noexcept. -struct ThrowingDtor -{ - int val = 0; - ~ThrowingDtor() noexcept(false) {} -}; - struct MoveOnly { int val = 0; @@ -190,7 +183,6 @@ static_assert(dpl_unseq::__is_value_storable_v); // The requirements are brace initialization, copy construction and copy assignment, and nothing else. static_assert(dpl_unseq::__is_value_storable_v); static_assert(dpl_unseq::__is_value_storable_v); -static_assert(dpl_unseq::__is_value_storable_v); static_assert(dpl_unseq::__is_value_storable_v); // The reference type is not part of the requirement. static_assert(dpl_unseq::__is_value_storable_v::iterator>); From 26feddae9f667de4d5d8b06fae9ad45116f876c9 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Thu, 10 Sep 2026 10:19:57 +0200 Subject: [PATCH 039/148] Fix review comment: let's remove NoDefaultCtor and use TestUtils::NoDefaultCtorWrapper instead --- .../value_storable.pass.cpp | 10 +++------- .../alg.min.max/minmax_element.pass.cpp | 15 +++------------ 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/test/general/implementation_details/value_storable.pass.cpp b/test/general/implementation_details/value_storable.pass.cpp index 53875b174e1..f568646f598 100644 --- a/test/general/implementation_details/value_storable.pass.cpp +++ b/test/general/implementation_details/value_storable.pass.cpp @@ -54,11 +54,7 @@ struct BraceInitOnly BraceInitOnly(std::initializer_list init) : val(init.size() == 0 ? 0 : *init.begin()) {} }; -struct NoDefaultCtor -{ - int val; - explicit NoDefaultCtor(int v) : val(v) {} -}; +// A type that is not default-constructible is taken from the test utilities: TestUtils::NoDefaultCtorWrapper. struct NoCopyAssign { @@ -144,7 +140,7 @@ static_assert(!dpl_unseq::__is_brace_constructible_v); static_assert(dpl_unseq::__is_brace_constructible_v); -static_assert(!dpl_unseq::__is_brace_constructible_v); +static_assert(!dpl_unseq::__is_brace_constructible_v>); //----------------------------------------------------------------------------// // Reference types @@ -198,7 +194,7 @@ static_assert(dpl_unseq::__is_value_storable_v>); // Rejected because of the value type: the first two fail brace initialization, the third copy assignment, and the last // one copy construction. -static_assert(!dpl_unseq::__is_value_storable_v); +static_assert(!dpl_unseq::__is_value_storable_v*>); static_assert(!dpl_unseq::__is_value_storable_v); static_assert(!dpl_unseq::__is_value_storable_v); static_assert(!dpl_unseq::__is_value_storable_v); diff --git a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp index b44c8c483b3..5dffca8ca8d 100644 --- a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp +++ b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp @@ -371,17 +371,8 @@ struct ConstCopyOnlyCompare } }; -// Not default-constructible. -struct NoDefaultCtorCompare -{ - std::int32_t val; - explicit NoDefaultCtorCompare(std::int32_t val_) : val(val_) {} - bool - operator<(const NoDefaultCtorCompare& other) const - { - return val < other.val; - } -}; +// A type that is not default-constructible is taken from the test utilities: +// TestUtils::NoDefaultCtorWrapper. It compares through its conversion to the underlying type. // Not copy-assignable. struct NoCopyAssignCompare @@ -542,7 +533,7 @@ main() test_by_type_host_policies(NSmall); // These value types are rejected by the vector code path: the call must compile and fall back to the serial one. - test_by_type_host_policies(NSmall); + test_by_type_host_policies>(NSmall); test_by_type_host_policies(NSmall); test_by_type_host_policies(NSmall); From 029eaf45cdc322e36d5636fd7d3085539d22c095 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 15 Sep 2026 09:26:17 +0200 Subject: [PATCH 040/148] Fix `SIMD` `min_element` / `minmax_element` for value types unusable in a user-defined reduction - simplifications (#2825) --- include/oneapi/dpl/pstl/algorithm_impl.h | 24 +- include/oneapi/dpl/pstl/unseq_backend_simd.h | 65 ++--- .../value_storable.pass.cpp | 236 +++++++----------- .../alg.min.max/minmax_element.pass.cpp | 171 ++----------- test/support/utils.h | 172 +++++++++++++ 5 files changed, 328 insertions(+), 340 deletions(-) diff --git a/include/oneapi/dpl/pstl/algorithm_impl.h b/include/oneapi/dpl/pstl/algorithm_impl.h index c6b9d142369..d373e0d611a 100644 --- a/include/oneapi/dpl/pstl/algorithm_impl.h +++ b/include/oneapi/dpl/pstl/algorithm_impl.h @@ -4875,12 +4875,16 @@ _RandomAccessIterator __brick_min_element(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp, /* __is_vector = */ ::std::true_type) noexcept { -#if _ONEDPL_UDR_PRESENT // _PSTL_UDR_PRESENT +#if _ONEDPL_UDR_PRESENT if constexpr (__unseq_backend::__is_value_storable_v<_RandomAccessIterator>) + { return __unseq_backend::__simd_min_element(__first, __last - __first, __comp); -#endif - - return std::min_element(__first, __last, __comp); + } + else +#endif // _ONEDPL_UDR_PRESENT + { + return std::min_element(__first, __last, __comp); + } } template @@ -4943,12 +4947,16 @@ ::std::pair<_RandomAccessIterator, _RandomAccessIterator> __brick_minmax_element(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp, /* __is_vector = */ ::std::true_type) noexcept { -#if _ONEDPL_UDR_PRESENT // _PSTL_UDR_PRESENT +#if _ONEDPL_UDR_PRESENT if constexpr (__unseq_backend::__is_value_storable_v<_RandomAccessIterator>) + { return __unseq_backend::__simd_minmax_element(__first, __last - __first, __comp); -#endif - - return std::minmax_element(__first, __last, __comp); + } + else +#endif // _ONEDPL_UDR_PRESENT + { + return std::minmax_element(__first, __last, __comp); + } } template diff --git a/include/oneapi/dpl/pstl/unseq_backend_simd.h b/include/oneapi/dpl/pstl/unseq_backend_simd.h index 373a4b7ee8d..11afb591782 100644 --- a/include/oneapi/dpl/pstl/unseq_backend_simd.h +++ b/include/oneapi/dpl/pstl/unseq_backend_simd.h @@ -16,10 +16,11 @@ #ifndef _ONEDPL_UNSEQ_BACKEND_SIMD_H #define _ONEDPL_UNSEQ_BACKEND_SIMD_H -#include -#include // for std::addressof -#include // for std::iterator_traits -#include // for std::as_const +#include // for std::invoke +#include // for std::iterator_traits +#include // for std::addressof +#include // for std::true_type, std::is_copy_constructible_v +#include // for std::pair, std::make_pair #include "utils.h" @@ -615,20 +616,15 @@ __simd_scan(_InputIterator __first, _Size __n, _OutputIterator __result, _UnaryO return ::std::make_pair(__result + __n, __init_.__value); } -template -inline constexpr bool __is_brace_constructible_v = false; - -template -inline constexpr bool __is_brace_constructible_v<_Tp, decltype(void(_Tp{}))> = true; - -// Requirements needed by __simd_min_element and __simd_minmax_element implementations: -// - __is_brace_constructible_v: the _ComplexType default constructor needs _ValueType{} to be well-formed. -// - std::is_copy_constructible_v: _ComplexType copy constructor is deleted if _ValueType is not copy constructible. -// - std::is_copy_assignable_v: the _ONEDPL_PRAGMA_SIMD_REDUCTION loop assigns _ValueType. -template ::value_type> +// Implementation detail of __simd_min_element / __simd_minmax_element, not a contract of the algorithms. Copy +// construction is needed because the OpenMP clause initializer(omp_priv = omp_orig) copy-initializes the whole +// reduction object, hence its members. Convertibility of the reference type is not implied by copy-constructibility - +// it also holds for an explicit copy constructor and for one deleted for non-const lvalues. +template ::value_type, + typename _ReferenceType = typename std::iterator_traits<_Iterator>::reference> inline constexpr bool __is_value_storable_v = - __is_brace_constructible_v<_ValueType> && std::is_copy_constructible_v<_ValueType> && - std::is_copy_assignable_v<_ValueType>; + std::is_copy_constructible_v<_ValueType> && std::is_copy_assignable_v<_ValueType> && + std::is_convertible_v<_ReferenceType, _ValueType>; // complexity [violation] - We will have at most (__n-1 + number_of_lanes) comparisons instead of at most __n-1. template @@ -636,7 +632,8 @@ _ForwardIterator __simd_min_element(_ForwardIterator __first, _Size __n, _Compare __comp) noexcept { static_assert(__is_value_storable_v<_ForwardIterator>, - "The value type of the iterator must be storable in the reduction object"); + "The value type of the iterator must be copy-constructible, copy-assignable and copy-initializable " + "from the iterator's reference type"); if (__n == 0) { @@ -649,14 +646,8 @@ __simd_min_element(_ForwardIterator __first, _Size __n, _Compare __comp) noexcep _ValueType __min_val; _Size __min_ind; _Compare* __min_comp; - // The default constructor is not used during the algorithm, so it is not required for it. - // However, some compilers may require it. - _ComplexType() : __min_val{}, __min_ind{}, __min_comp(nullptr) {} - _ComplexType(const _ValueType& val, const _Compare* comp) - : __min_val(val), __min_ind(0), __min_comp(const_cast<_Compare*>(comp)) - { - } + _ComplexType(const _ValueType& val, _Compare* comp) : __min_val(val), __min_ind(0), __min_comp(comp) {} _ComplexType(const _ComplexType& __obj) = default; _ONEDPL_PRAGMA_DECLARE_SIMD @@ -672,16 +663,15 @@ __simd_min_element(_ForwardIterator __first, _Size __n, _Compare __comp) noexcep } }; - _ComplexType __init{*__first, std::addressof(__comp)}; + _ComplexType __init(*__first, std::addressof(__comp)); _ONEDPL_PRAGMA_DECLARE_REDUCTION(__min_func, _ComplexType) _ONEDPL_PRAGMA_SIMD_REDUCTION(__min_func : __init) for (_Size __i = 1; __i < __n; ++__i) { - const _ValueType __min_val(std::as_const(__init).__min_val); const _ValueType __current = __first[__i]; - if (std::invoke(__comp, __current, __min_val)) + if (std::invoke(__comp, __current, __init.__min_val)) { __init.__min_val = __current; __init.__min_ind = __i; @@ -696,7 +686,8 @@ std::pair<_ForwardIterator, _ForwardIterator> __simd_minmax_element(_ForwardIterator __first, _Size __n, _Compare __comp) noexcept { static_assert(__is_value_storable_v<_ForwardIterator>, - "The value type of the iterator must be storable in the reduction object"); + "The value type of the iterator must be copy-constructible, copy-assignable and copy-initializable " + "from the iterator's reference type"); if (__n == 0) { @@ -711,13 +702,9 @@ __simd_minmax_element(_ForwardIterator __first, _Size __n, _Compare __comp) noex _Size __min_ind; _Size __max_ind; _Compare* __minmax_comp; - // The default constructor is not used during the algorithm, so it is not required for it. - // However, some compilers may require it. - _ComplexType() : __min_val{}, __max_val{}, __min_ind{}, __max_ind{}, __minmax_comp(nullptr) {} - _ComplexType(const _ValueType& min_val, const _ValueType& max_val, const _Compare* comp) - : __min_val(min_val), __max_val(max_val), __min_ind(0), __max_ind(0), - __minmax_comp(const_cast<_Compare*>(comp)) + _ComplexType(const _ValueType& min_val, const _ValueType& max_val, _Compare* comp) + : __min_val(min_val), __max_val(max_val), __min_ind(0), __max_ind(0), __minmax_comp(comp) { } _ComplexType(const _ComplexType& __obj) = default; @@ -752,22 +739,20 @@ __simd_minmax_element(_ForwardIterator __first, _Size __n, _Compare __comp) noex } }; - _ComplexType __init{*__first, *__first, std::addressof(__comp)}; + _ComplexType __init(*__first, *__first, std::addressof(__comp)); _ONEDPL_PRAGMA_DECLARE_REDUCTION(__min_func, _ComplexType); _ONEDPL_PRAGMA_SIMD_REDUCTION(__min_func : __init) for (_Size __i = 1; __i < __n; ++__i) { - const _ValueType __min_val(std::as_const(__init).__min_val); - const _ValueType __max_val(std::as_const(__init).__max_val); const _ValueType __current = __first[__i]; - if (std::invoke(__comp, __current, __min_val)) + if (std::invoke(__comp, __current, __init.__min_val)) { __init.__min_val = __current; __init.__min_ind = __i; } - else if (!std::invoke(__comp, __current, __max_val)) + else if (!std::invoke(__comp, __current, __init.__max_val)) { __init.__max_val = __current; __init.__max_ind = __i; diff --git a/test/general/implementation_details/value_storable.pass.cpp b/test/general/implementation_details/value_storable.pass.cpp index f568646f598..93ec7eb0e06 100644 --- a/test/general/implementation_details/value_storable.pass.cpp +++ b/test/general/implementation_details/value_storable.pass.cpp @@ -7,141 +7,25 @@ // //===------------------------------------------------------===// -// Compile-time checks for oneapi::dpl::__unseq_backend::__is_value_storable_v and __is_brace_constructible_v. +// Compile-time checks for oneapi::dpl::__unseq_backend::__is_value_storable_v, the condition that selects the vector +// code path of min_element and minmax_element. #include "support/test_config.h" #include -#include -#include -#include -#include -#include -#include +#include // for std::ptrdiff_t +#include // for std::int32_t +#include // for std::less +#include // for std::random_access_iterator_tag, std::back_insert_iterator +#include // for std::is_default_constructible_v, std::is_copy_constructible_v +#include // for std::pair +#include // for std::vector #include "support/utils.h" namespace dpl_unseq = oneapi::dpl::__unseq_backend; -//----------------------------------------------------------------------------// -// Value types -//----------------------------------------------------------------------------// - -// Satisfies every requirement: default-constructible, copy-constructible, copy-assignable. -struct Regular -{ - int val = 0; -}; - -struct ExplicitDefaultCtor -{ - int val; - explicit ExplicitDefaultCtor() : val(0) {} -}; - -// Default-constructible, but not brace-initializable: the member is copy-initialized from an empty list, which may not -// use its explicit default constructor. -struct AggregateOfExplicitDefaultCtor -{ - ExplicitDefaultCtor member; -}; - -// Brace-initializable, but not default-constructible: empty braces select the initializer-list constructor. -struct BraceInitOnly -{ - int val; - BraceInitOnly(std::initializer_list init) : val(init.size() == 0 ? 0 : *init.begin()) {} -}; - -// A type that is not default-constructible is taken from the test utilities: TestUtils::NoDefaultCtorWrapper. - -struct NoCopyAssign -{ - int val = 0; - NoCopyAssign() = default; - NoCopyAssign(const NoCopyAssign&) = default; - NoCopyAssign& - operator=(const NoCopyAssign&) = delete; -}; - -// The copy assignment returns void instead of VoidAssign&. -struct VoidAssign -{ - int val = 0; - void - operator=(const VoidAssign& other) - { - val = other.val; - } -}; - -struct MoveOnly -{ - int val = 0; - MoveOnly() = default; - MoveOnly(MoveOnly&&) = default; - MoveOnly& - operator=(MoveOnly&&) = default; - MoveOnly(const MoveOnly&) = delete; - MoveOnly& - operator=(const MoveOnly&) = delete; -}; - -// Copyable, but with deleted move operations. -struct CopyOnlyNoMove -{ - int val = 0; - CopyOnlyNoMove() = default; - CopyOnlyNoMove(const CopyOnlyNoMove&) = default; - CopyOnlyNoMove& - operator=(const CopyOnlyNoMove&) = default; - CopyOnlyNoMove(CopyOnlyNoMove&&) = delete; - CopyOnlyNoMove& - operator=(CopyOnlyNoMove&&) = delete; -}; - -// Copyable and assignable from a const lvalue only. -struct ConstCopyOnly -{ - int val = 0; - ConstCopyOnly() = default; - ConstCopyOnly(const ConstCopyOnly&) = default; - ConstCopyOnly& - operator=(const ConstCopyOnly&) = default; - ConstCopyOnly(ConstCopyOnly&) = delete; - ConstCopyOnly& - operator=(ConstCopyOnly&) = delete; -}; - -// The copy constructor is explicit, so the type is copy-constructible, but its elements cannot be copy-initialized. -struct ExplicitCopyCtor -{ - int val = 0; - ExplicitCopyCtor() = default; - explicit ExplicitCopyCtor(const ExplicitCopyCtor& other) : val(other.val) {} - ExplicitCopyCtor& - operator=(const ExplicitCopyCtor&) = default; -}; - -//----------------------------------------------------------------------------// -// __is_brace_constructible_v -//----------------------------------------------------------------------------// - -static_assert(dpl_unseq::__is_brace_constructible_v); -static_assert(dpl_unseq::__is_brace_constructible_v); -static_assert(dpl_unseq::__is_brace_constructible_v); -static_assert(dpl_unseq::__is_brace_constructible_v); -static_assert(dpl_unseq::__is_brace_constructible_v); - -// Brace initialization differs from default construction in both directions. -static_assert(std::is_default_constructible_v); -static_assert(!dpl_unseq::__is_brace_constructible_v); -static_assert(!std::is_default_constructible_v); -static_assert(dpl_unseq::__is_brace_constructible_v); - -static_assert(!dpl_unseq::__is_brace_constructible_v>); - //----------------------------------------------------------------------------// // Reference types //----------------------------------------------------------------------------// @@ -164,6 +48,39 @@ struct FakeIterator operator*() const; }; +// An iterator whose reference narrows to its value type; the bricks are instantiated for it in main(). +struct NarrowingIterator +{ + using iterator_category = std::random_access_iterator_tag; + using value_type = std::int32_t; + using difference_type = std::ptrdiff_t; + using pointer = void; + using reference = double; + + const double* ptr; + + reference + operator*() const + { + return *ptr; + } + reference + operator[](difference_type __i) const + { + return ptr[__i]; + } + NarrowingIterator + operator+(difference_type __i) const + { + return NarrowingIterator{ptr + __i}; + } + difference_type + operator-(const NarrowingIterator& __other) const + { + return ptr - __other.ptr; + } +}; + //----------------------------------------------------------------------------// // __is_value_storable_v //----------------------------------------------------------------------------// @@ -173,31 +90,42 @@ static_assert(dpl_unseq::__is_value_storable_v); static_assert(dpl_unseq::__is_value_storable_v); static_assert(dpl_unseq::__is_value_storable_v::iterator>); static_assert(dpl_unseq::__is_value_storable_v::const_iterator>); -static_assert(dpl_unseq::__is_value_storable_v); -static_assert(dpl_unseq::__is_value_storable_v); -static_assert(dpl_unseq::__is_value_storable_v); -// The requirements are brace initialization, copy construction and copy assignment, and nothing else. -static_assert(dpl_unseq::__is_value_storable_v); -static_assert(dpl_unseq::__is_value_storable_v); -static_assert(dpl_unseq::__is_value_storable_v); -// The reference type is not part of the requirement. +static_assert(dpl_unseq::__is_value_storable_v); +static_assert(dpl_unseq::__is_value_storable_v); +static_assert(dpl_unseq::__is_value_storable_v); +// Default construction is not required: the reduction object is always built from a value. +static_assert(!std::is_default_constructible_v>); +static_assert(dpl_unseq::__is_value_storable_v*>); +static_assert(!std::is_default_constructible_v); +static_assert(dpl_unseq::__is_value_storable_v); +// The requirements are copy construction, copy assignment and copy-initialization from the reference type, and nothing +// else - in particular the move operations are not required. +static_assert(dpl_unseq::__is_value_storable_v); +static_assert(dpl_unseq::__is_value_storable_v); +static_assert(dpl_unseq::__is_value_storable_v); +// A proxy reference is accepted as long as the value type can be copy-initialized from it. static_assert(dpl_unseq::__is_value_storable_v::iterator>); static_assert(dpl_unseq::__is_value_storable_v>); static_assert(dpl_unseq::__is_value_storable_v, std::pair>>); -static_assert(dpl_unseq::__is_value_storable_v>); -// Accepted although the bricks do not compile for them: these iterators do not meet the requirements of a forward -// iterator, which is not detected here. -static_assert(std::is_copy_constructible_v); -static_assert(dpl_unseq::__is_value_storable_v); -static_assert(dpl_unseq::__is_value_storable_v); -static_assert(dpl_unseq::__is_value_storable_v>); - -// Rejected because of the value type: the first two fail brace initialization, the third copy assignment, and the last -// one copy construction. -static_assert(!dpl_unseq::__is_value_storable_v*>); -static_assert(!dpl_unseq::__is_value_storable_v); -static_assert(!dpl_unseq::__is_value_storable_v); -static_assert(!dpl_unseq::__is_value_storable_v); +// Narrowing is accepted: the bricks copy-initialize the value, they do not list-initialize it. +static_assert(dpl_unseq::__is_value_storable_v); + +// Rejected because of the value type: copy assignment, copy construction. +static_assert(!dpl_unseq::__is_value_storable_v); +static_assert(!dpl_unseq::__is_value_storable_v); + +// Rejected because the value cannot be copy-initialized from what the iterator dereferences to, which is how the +// bricks read an element - copy-constructibility of the value type alone does not imply that. +static_assert(std::is_copy_constructible_v); +static_assert(!dpl_unseq::__is_value_storable_v); +static_assert(std::is_copy_constructible_v); +static_assert(!dpl_unseq::__is_value_storable_v); +static_assert(!dpl_unseq::__is_value_storable_v>); + +// Rejected conservatively: the conversion is checked from an rvalue of the reference type, while the bricks initialize +// from *__first, which is a prvalue here and needs no move constructor; such an iterator only misses vectorization. +static_assert(!dpl_unseq::__is_value_storable_v< + FakeIterator>); // Rejected because an output iterator reports void as its value type. static_assert(!dpl_unseq::__is_value_storable_v>>); @@ -205,5 +133,19 @@ static_assert(!dpl_unseq::__is_value_storable_v{}) - __first, + "wrong __simd_min_element on NarrowingIterator"); + const auto __minmax = dpl_unseq::__simd_minmax_element(__first, __n, std::less<>{}); + EXPECT_EQ(1, __minmax.first - __first, "wrong minimum from __simd_minmax_element on NarrowingIterator"); + EXPECT_EQ(0, __minmax.second - __first, "wrong maximum from __simd_minmax_element on NarrowingIterator"); +#endif return TestUtils::done(); } diff --git a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp index 5dffca8ca8d..e64ab22bc93 100644 --- a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp +++ b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp @@ -20,12 +20,7 @@ #include "support/utils.h" -#include -#include -#include -#include -#include -#include +#include // for std::set, to keep the generated indices unique #if !defined(_PSTL_TEST_MIN_ELEMENT) && !defined(_PSTL_TEST_MAX_ELEMENT) &&\ !defined(_PSTL_TEST_MINMAX_ELEMENT) && !_PSTL_ICPX_TEST_MINMAX_ELEMENT_PASS_BROKEN @@ -276,138 +271,7 @@ test_by_type(std::size_t n) } } -// should provide minimal requirements only -struct OnlyLessCompare -{ - std::int32_t val; - OnlyLessCompare() : val(0) {} - OnlyLessCompare(std::int32_t val_) : val(val_) {} - bool - operator<(const OnlyLessCompare& other) const - { - return val < other.val; - } -}; - -// Default-constructible through an explicit default constructor only. -struct ExplicitDefaultCtorCompare -{ - std::int32_t val; - explicit ExplicitDefaultCtorCompare() : val(0) {} - ExplicitDefaultCtorCompare(std::int32_t val_) : val(val_) {} - bool - operator<(const ExplicitDefaultCtorCompare& other) const - { - return val < other.val; - } -}; - -// Not default-constructible, but brace-initializable: empty braces select the initializer-list constructor. -struct BraceInitOnlyCompare -{ - std::int32_t val; - BraceInitOnlyCompare(std::initializer_list init) : val(init.size() == 0 ? 0 : *init.begin()) {} - BraceInitOnlyCompare(std::int32_t val_) : val(val_) {} - bool - operator<(const BraceInitOnlyCompare& other) const - { - return val < other.val; - } -}; - -// Copyable, but with deleted move operations. -struct CopyOnlyNoMoveCompare -{ - std::int32_t val; - CopyOnlyNoMoveCompare() : val(0) {} - CopyOnlyNoMoveCompare(std::int32_t val_) : val(val_) {} - CopyOnlyNoMoveCompare(const CopyOnlyNoMoveCompare&) = default; - CopyOnlyNoMoveCompare& - operator=(const CopyOnlyNoMoveCompare&) = default; - CopyOnlyNoMoveCompare(CopyOnlyNoMoveCompare&&) = delete; - CopyOnlyNoMoveCompare& - operator=(CopyOnlyNoMoveCompare&&) = delete; - bool - operator<(const CopyOnlyNoMoveCompare& other) const - { - return val < other.val; - } -}; - -// The copy assignment returns void instead of VoidAssignCompare&. -struct VoidAssignCompare -{ - std::int32_t val; - VoidAssignCompare() : val(0) {} - VoidAssignCompare(std::int32_t val_) : val(val_) {} - void - operator=(const VoidAssignCompare& other) - { - val = other.val; - } - bool - operator<(const VoidAssignCompare& other) const - { - return val < other.val; - } -}; - -// Copyable and assignable from a const lvalue only, so it requires const iterators. -struct ConstCopyOnlyCompare -{ - std::int32_t val; - ConstCopyOnlyCompare() : val(0) {} - ConstCopyOnlyCompare(std::int32_t val_) : val(val_) {} - ConstCopyOnlyCompare(const ConstCopyOnlyCompare&) = default; - ConstCopyOnlyCompare(ConstCopyOnlyCompare&) = delete; - ConstCopyOnlyCompare& - operator=(const ConstCopyOnlyCompare&) = default; - ConstCopyOnlyCompare& - operator=(ConstCopyOnlyCompare&) = delete; - bool - operator<(const ConstCopyOnlyCompare& other) const - { - return val < other.val; - } -}; - -// A type that is not default-constructible is taken from the test utilities: -// TestUtils::NoDefaultCtorWrapper. It compares through its conversion to the underlying type. - -// Not copy-assignable. -struct NoCopyAssignCompare -{ - std::int32_t val; - NoCopyAssignCompare() : val(0) {} - NoCopyAssignCompare(std::int32_t val_) : val(val_) {} - NoCopyAssignCompare(const NoCopyAssignCompare&) = default; - NoCopyAssignCompare& - operator=(const NoCopyAssignCompare&) = delete; - bool - operator<(const NoCopyAssignCompare& other) const - { - return val < other.val; - } -}; - -// Not copy-constructible. -struct MoveOnlyCompare -{ - std::int32_t val; - MoveOnlyCompare() : val(0) {} - MoveOnlyCompare(std::int32_t val_) : val(val_) {} - MoveOnlyCompare(MoveOnlyCompare&&) = default; - MoveOnlyCompare& - operator=(MoveOnlyCompare&&) = default; - MoveOnlyCompare(const MoveOnlyCompare&) = delete; - MoveOnlyCompare& - operator=(const MoveOnlyCompare&) = delete; - bool - operator<(const MoveOnlyCompare& other) const - { - return val < other.val; - } -}; +// The value types with restricted operations that the test runs the algorithms on are defined in test/support/utils.h. template static void @@ -438,10 +302,23 @@ test_by_type_host_policies(std::size_t n) data.emplace_back(std::int32_t(TestUtils::HashBits(i, 30))); using Iterator = std::conditional_t::const_iterator, - typename std::vector::iterator>; + typename std::vector::iterator>; check_by_type_host_policies(Iterator(data.begin()), Iterator(data.end())); } +// An aggregate cannot be constructed with parentheses before C++20, so elements are brace-initialized, not emplaced. +template +static void +test_by_type_host_policies_brace_init(std::size_t n) +{ + std::vector data; + data.reserve(n); + for (std::size_t i = 0; i < n; ++i) + data.push_back(T{std::int32_t(TestUtils::HashBits(i, 30))}); + + check_by_type_host_policies(data.begin(), data.end()); +} + // A type with deleted move operations cannot be pushed into a std::vector, so the vector is sized up front and its // elements are assigned. A plain array is not used on purpose: with the bounds known at compile time, GCC reports a // false out-of-bounds subscript in the parallel reduction. @@ -514,6 +391,9 @@ main() using TestUtils::float64_t; const std::size_t N = 100000; const std::size_t NSmall = 10; + // Large enough for the parallel backend to split the sequence into several chunks and to combine their results, and + // for the vector loop to reduce over several lanes rather than to fall entirely into its remainder. + const std::size_t NMultiChunk = 1000; for (std::size_t n = 0; n < N; n = n < 16 ? n + 1 : size_t(3.14159 * n)) { @@ -524,21 +404,22 @@ main() test_by_type(n); } - // These value types are accepted by the vector code path: it must be instantiated for them. Whether it compiles - // does not depend on the sequence size, so a single small size is enough for all the checks below. + // These value types are accepted by the vector code path: it must be instantiated for them. Compiling it does not + // depend on the sequence size, so a single small size is enough for all the checks below. test_by_type(NSmall); - test_by_type_host_policies(NSmall); + test_by_type_host_policies_brace_init(NSmall); test_by_type_host_policies_no_move(NSmall); test_by_type_host_policies(NSmall); test_by_type_host_policies(NSmall); + // Default construction is not required, so these two are accepted as well. + test_by_type_host_policies>(NSmall); + test_by_type_host_policies(NSmall); // These value types are rejected by the vector code path: the call must compile and fall back to the serial one. - test_by_type_host_policies>(NSmall); test_by_type_host_policies(NSmall); test_by_type_host_policies(NSmall); - // The sequence is long enough for the vector code to process several blocks and to combine their results. - test_comparator_with_overloaded_address_of(1000); + test_comparator_with_overloaded_address_of(NMultiChunk); #ifdef _PSTL_TEST_MIN_ELEMENT test_algo_basic_single(run_for_rnd_fw>()); diff --git a/test/support/utils.h b/test/support/utils.h index cfd1a4f4df6..2f8b6458e85 100644 --- a/test/support/utils.h +++ b/test/support/utils.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -1405,6 +1406,177 @@ struct NoDefaultCtorWrapper { } }; +//----------------------------------------------------------------------------// +// Value types with restricted operations +//----------------------------------------------------------------------------// +// +// Each of these types is an element of an algorithm with one aspect of its interface restricted, so that a test can +// tell which operations an implementation really requires of its value type. The name says what the restriction is. +// +// All of them are less-than-comparable through a member operator<. Most are also default-constructible and +// constructible from std::int32_t; the exceptions are noted on the types themselves. +// +// They are shared by test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp, which runs the +// algorithms on them, and test/general/implementation_details/value_storable.pass.cpp, which checks the trait that +// selects the vector code path for them. Keep them in lock-step: a type accepted by the trait has to be one the +// algorithms actually compile for. + +// Nothing is restricted: this is the baseline the others are compared against. +struct OnlyLessCompare +{ + std::int32_t val; + OnlyLessCompare() : val(0) {} + OnlyLessCompare(std::int32_t val_) : val(val_) {} + bool + operator<(const OnlyLessCompare& other) const + { + return val < other.val; + } +}; + +struct ExplicitDefaultCtorCompare +{ + std::int32_t val; + explicit ExplicitDefaultCtorCompare() : val(0) {} + ExplicitDefaultCtorCompare(std::int32_t val_) : val(val_) {} + bool + operator<(const ExplicitDefaultCtorCompare& other) const + { + return val < other.val; + } +}; + +// Default-constructible, but not brace-initializable: empty braces copy-list-initialize the member, which its explicit +// default constructor rejects. It is an aggregate, so it takes no std::int32_t constructor. +struct AggregateOfExplicitDefaultCtorCompare +{ + ExplicitDefaultCtorCompare member; + bool + operator<(const AggregateOfExplicitDefaultCtorCompare& other) const + { + return member < other.member; + } +}; + +// Not default-constructible: neither constructor takes zero arguments. Empty braces select the initializer-list one. +struct BraceInitOnlyCompare +{ + std::int32_t val; + BraceInitOnlyCompare(std::initializer_list init) : val(init.size() == 0 ? 0 : *init.begin()) {} + BraceInitOnlyCompare(std::int32_t val_) : val(val_) {} + bool + operator<(const BraceInitOnlyCompare& other) const + { + return val < other.val; + } +}; + +struct CopyOnlyNoMoveCompare +{ + std::int32_t val; + CopyOnlyNoMoveCompare() : val(0) {} + CopyOnlyNoMoveCompare(std::int32_t val_) : val(val_) {} + CopyOnlyNoMoveCompare(const CopyOnlyNoMoveCompare&) = default; + CopyOnlyNoMoveCompare& + operator=(const CopyOnlyNoMoveCompare&) = default; + CopyOnlyNoMoveCompare(CopyOnlyNoMoveCompare&&) = delete; + CopyOnlyNoMoveCompare& + operator=(CopyOnlyNoMoveCompare&&) = delete; + bool + operator<(const CopyOnlyNoMoveCompare& other) const + { + return val < other.val; + } +}; + +// The copy assignment returns void instead of VoidAssignCompare&. The copy constructor has to be declared explicitly: +// a user-declared copy assignment operator only deprecates the implicit one, which -Wdeprecated-copy reports. +struct VoidAssignCompare +{ + std::int32_t val; + VoidAssignCompare() : val(0) {} + VoidAssignCompare(std::int32_t val_) : val(val_) {} + VoidAssignCompare(const VoidAssignCompare&) = default; + void + operator=(const VoidAssignCompare& other) + { + val = other.val; + } + bool + operator<(const VoidAssignCompare& other) const + { + return val < other.val; + } +}; + +// Copyable and assignable from a const lvalue only, so it requires const iterators. +struct ConstCopyOnlyCompare +{ + std::int32_t val; + ConstCopyOnlyCompare() : val(0) {} + ConstCopyOnlyCompare(std::int32_t val_) : val(val_) {} + ConstCopyOnlyCompare(const ConstCopyOnlyCompare&) = default; + ConstCopyOnlyCompare(ConstCopyOnlyCompare&) = delete; + ConstCopyOnlyCompare& + operator=(const ConstCopyOnlyCompare&) = default; + ConstCopyOnlyCompare& + operator=(ConstCopyOnlyCompare&) = delete; + bool + operator<(const ConstCopyOnlyCompare& other) const + { + return val < other.val; + } +}; + +// Copy-constructible, but its elements cannot be copy-initialized, because the copy constructor is explicit. +struct ExplicitCopyCtorCompare +{ + std::int32_t val; + ExplicitCopyCtorCompare() : val(0) {} + ExplicitCopyCtorCompare(std::int32_t val_) : val(val_) {} + explicit ExplicitCopyCtorCompare(const ExplicitCopyCtorCompare& other) : val(other.val) {} + ExplicitCopyCtorCompare& + operator=(const ExplicitCopyCtorCompare&) = default; + bool + operator<(const ExplicitCopyCtorCompare& other) const + { + return val < other.val; + } +}; + +struct NoCopyAssignCompare +{ + std::int32_t val; + NoCopyAssignCompare() : val(0) {} + NoCopyAssignCompare(std::int32_t val_) : val(val_) {} + NoCopyAssignCompare(const NoCopyAssignCompare&) = default; + NoCopyAssignCompare& + operator=(const NoCopyAssignCompare&) = delete; + bool + operator<(const NoCopyAssignCompare& other) const + { + return val < other.val; + } +}; + +struct MoveOnlyCompare +{ + std::int32_t val; + MoveOnlyCompare() : val(0) {} + MoveOnlyCompare(std::int32_t val_) : val(val_) {} + MoveOnlyCompare(MoveOnlyCompare&&) = default; + MoveOnlyCompare& + operator=(MoveOnlyCompare&&) = default; + MoveOnlyCompare(const MoveOnlyCompare&) = delete; + MoveOnlyCompare& + operator=(const MoveOnlyCompare&) = delete; + bool + operator<(const MoveOnlyCompare& other) const + { + return val < other.val; + } +}; + #if _ENABLE_STD_RANGES_TESTING // A minimalistic range: it can't be applied directly to hetero range-based algorithms, From 86545ae893d736e841222c51a2cc785cf7635a7c Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 15 Sep 2026 09:30:20 +0200 Subject: [PATCH 041/148] include/oneapi/dpl/pstl/algorithm_impl.h - remove extra changes --- include/oneapi/dpl/pstl/algorithm_impl.h | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/include/oneapi/dpl/pstl/algorithm_impl.h b/include/oneapi/dpl/pstl/algorithm_impl.h index d373e0d611a..acbdd3bae0a 100644 --- a/include/oneapi/dpl/pstl/algorithm_impl.h +++ b/include/oneapi/dpl/pstl/algorithm_impl.h @@ -4875,16 +4875,12 @@ _RandomAccessIterator __brick_min_element(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp, /* __is_vector = */ ::std::true_type) noexcept { -#if _ONEDPL_UDR_PRESENT +#if _ONEDPL_UDR_PRESENT // _PSTL_UDR_PRESENT if constexpr (__unseq_backend::__is_value_storable_v<_RandomAccessIterator>) - { return __unseq_backend::__simd_min_element(__first, __last - __first, __comp); - } else -#endif // _ONEDPL_UDR_PRESENT - { +#endif return std::min_element(__first, __last, __comp); - } } template @@ -4947,16 +4943,12 @@ ::std::pair<_RandomAccessIterator, _RandomAccessIterator> __brick_minmax_element(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp, /* __is_vector = */ ::std::true_type) noexcept { -#if _ONEDPL_UDR_PRESENT +#if _ONEDPL_UDR_PRESENT // _PSTL_UDR_PRESENT if constexpr (__unseq_backend::__is_value_storable_v<_RandomAccessIterator>) - { return __unseq_backend::__simd_minmax_element(__first, __last - __first, __comp); - } else -#endif // _ONEDPL_UDR_PRESENT - { +#endif return std::minmax_element(__first, __last, __comp); - } } template From fff962120b62f8a148bf2b0d8846edfb07637c7f Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 15 Sep 2026 09:35:04 +0200 Subject: [PATCH 042/148] include/oneapi/dpl/pstl/unseq_backend_simd.h - change the comments --- include/oneapi/dpl/pstl/unseq_backend_simd.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/include/oneapi/dpl/pstl/unseq_backend_simd.h b/include/oneapi/dpl/pstl/unseq_backend_simd.h index 11afb591782..29e25a80c7e 100644 --- a/include/oneapi/dpl/pstl/unseq_backend_simd.h +++ b/include/oneapi/dpl/pstl/unseq_backend_simd.h @@ -616,16 +616,13 @@ __simd_scan(_InputIterator __first, _Size __n, _OutputIterator __result, _UnaryO return ::std::make_pair(__result + __n, __init_.__value); } -// Implementation detail of __simd_min_element / __simd_minmax_element, not a contract of the algorithms. Copy -// construction is needed because the OpenMP clause initializer(omp_priv = omp_orig) copy-initializes the whole -// reduction object, hence its members. Convertibility of the reference type is not implied by copy-constructibility - -// it also holds for an explicit copy constructor and for one deleted for non-const lvalues. template ::value_type, typename _ReferenceType = typename std::iterator_traits<_Iterator>::reference> inline constexpr bool __is_value_storable_v = std::is_copy_constructible_v<_ValueType> && std::is_copy_assignable_v<_ValueType> && std::is_convertible_v<_ReferenceType, _ValueType>; +// [restriction] - the restrictions are formulated in the trait __is_value_storable_v // complexity [violation] - We will have at most (__n-1 + number_of_lanes) comparisons instead of at most __n-1. template _ForwardIterator @@ -680,6 +677,7 @@ __simd_min_element(_ForwardIterator __first, _Size __n, _Compare __comp) noexcep return __first + __init.__min_ind; } +// [restriction] - the restrictions are formulated in the trait __is_value_storable_v // complexity [violation] - We will have at most (2*(__n-1) + 4*number_of_lanes) comparisons instead of at most [1.5*(__n-1)]. template std::pair<_ForwardIterator, _ForwardIterator> From 2fb3a998f9deea323fb24095b3fd079e33c29c6d Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 15 Sep 2026 09:39:59 +0200 Subject: [PATCH 043/148] test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp - revert extra changes --- .../alg.min.max/minmax_element.pass.cpp | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp index e64ab22bc93..d6ca60f59d7 100644 --- a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp +++ b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp @@ -20,7 +20,7 @@ #include "support/utils.h" -#include // for std::set, to keep the generated indices unique +#include #if !defined(_PSTL_TEST_MIN_ELEMENT) && !defined(_PSTL_TEST_MAX_ELEMENT) &&\ !defined(_PSTL_TEST_MINMAX_ELEMENT) && !_PSTL_ICPX_TEST_MINMAX_ELEMENT_PASS_BROKEN @@ -38,7 +38,7 @@ struct check_minelement void operator()(Policy&& exec, Iterator begin, Iterator end) { - const Iterator expect = std::min_element(begin, end); + const Iterator expect = ::std::min_element(begin, end); const Iterator result = std::min_element(std::forward(exec), begin, end); EXPECT_EQ(expect, result, "wrong return result from min_element"); } @@ -52,7 +52,7 @@ struct check_minelement_predicate operator()(Policy&& exec, Iterator begin, Iterator end) { using T = typename std::iterator_traits::value_type; - const Iterator expect = std::min_element(begin, end); + const Iterator expect = ::std::min_element(begin, end); const Iterator result_pred = std::min_element(std::forward(exec), begin, end, std::less()); EXPECT_EQ(expect, result_pred, "wrong return result from min_element with predicate"); } @@ -65,7 +65,7 @@ struct check_maxelement void operator()(Policy&& exec, Iterator begin, Iterator end) { - const Iterator expect = std::max_element(begin, end); + const Iterator expect = ::std::max_element(begin, end); const Iterator result = std::max_element(std::forward(exec), begin, end); EXPECT_EQ(expect, result, "wrong return result from max_element"); } @@ -79,7 +79,7 @@ struct check_maxelement_predicate operator()(Policy&& exec, Iterator begin, Iterator end) { using T = typename std::iterator_traits::value_type; - const Iterator expect = std::max_element(begin, end); + const Iterator expect = ::std::max_element(begin, end); const Iterator result_pred = std::max_element(std::forward(exec), begin, end, std::less()); EXPECT_EQ(expect, result_pred, "wrong return result from max_element with predicate"); } @@ -92,7 +92,7 @@ struct check_minmaxelement void operator()(Policy&& exec, Iterator begin, Iterator end) { - const std::pair expect = std::minmax_element(begin, end); + const ::std::pair expect = ::std::minmax_element(begin, end); const std::pair got = std::minmax_element(std::forward(exec), begin, end); EXPECT_EQ(expect.first, got.first, "wrong return result from minmax_element (min part)"); EXPECT_EQ(expect.second, got.second, "wrong return result from minmax_element (max part)"); @@ -107,7 +107,7 @@ struct check_minmaxelement_predicate operator()(Policy&& exec, Iterator begin, Iterator end) { using T = typename std::iterator_traits::value_type; - const std::pair expect = std::minmax_element(begin, end); + const ::std::pair expect = ::std::minmax_element(begin, end); const std::pair got_pred = std::minmax_element(std::forward(exec), begin, end, std::less()); EXPECT_EQ(expect, got_pred, "wrong return result from minmax_element with predicate"); } @@ -174,40 +174,40 @@ struct sequence_wrapper TestUtils::Sequence seq; const T min_value; const T max_value; - static const std::size_t bits = 30; // We assume that T can handle signed 2^bits+1 value + static const ::std::size_t bits = 30; // We assume that T can handle signed 2^bits+1 value // TestUtils::HashBits returns value between 0 and (1< T { return T(TestUtils::HashBits(i, bits)); }); + seq.fill([](::std::size_t i) -> T { return T(TestUtils::HashBits(i, bits)); }); } // sets first one at position `at` and bunch of them farther void - set_desired_value(std::size_t at, T value) + set_desired_value(::std::size_t at, T value) { if (seq.size() == 0) return; seq[at] = value; //Producing several red herrings - for (std::size_t i = at + 1; i < seq.size(); i += 1 + TestUtils::HashBits(i, 5)) + for (::std::size_t i = at + 1; i < seq.size(); i += 1 + TestUtils::HashBits(i, 5)) seq[i] = value; } }; template void -test_by_type(std::size_t n) +test_by_type(::std::size_t n) { sequence_wrapper wseq(n); - // to avoid overtesing we use std::set to leave only unique indexes - std::set targets{0}; + // to avoid overtesing we use ::std::set to leave only unique indexes + ::std::set<::std::size_t> targets{0}; if (n > 1) { targets.insert(1); @@ -217,7 +217,7 @@ test_by_type(std::size_t n) targets.insert(n - 1); // last } - for (std::set::iterator it = targets.begin(); it != targets.end(); ++it) + for (::std::set<::std::size_t>::iterator it = targets.begin(); it != targets.end(); ++it) { wseq.pattern_fill(); #ifdef _PSTL_TEST_MIN_ELEMENT @@ -243,7 +243,7 @@ test_by_type(std::size_t n) #ifdef _PSTL_TEST_MINMAX_ELEMENT if (targets.size() > 1) { - for (std::set::reverse_iterator rit = targets.rbegin(); rit != targets.rend(); ++rit) + for (::std::set<::std::size_t>::reverse_iterator rit = targets.rbegin(); rit != targets.rend(); ++rit) { if (*rit == *it) // we requires at least 2 unique indexes in targets break; From 040db37e626d0cb1c88f294bd3747544ede6649e Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 15 Sep 2026 09:43:09 +0200 Subject: [PATCH 044/148] test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp - partially using oneapi::dpl:: instead of std:: --- .../alg.sorting/alg.min.max/minmax_element.pass.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp index d6ca60f59d7..3cc156ed0e9 100644 --- a/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp +++ b/test/parallel_api/algorithm/alg.sorting/alg.min.max/minmax_element.pass.cpp @@ -136,7 +136,7 @@ struct check_minelement_overloaded_address_of operator()(Policy&& exec, Iterator begin, Iterator end) { const Iterator expect = std::min_element(begin, end); - const Iterator result = std::min_element(std::forward(exec), begin, end, OverloadedAddressOfLess()); + const Iterator result = oneapi::dpl::min_element(std::forward(exec), begin, end, OverloadedAddressOfLess()); EXPECT_EQ(expect, result, "wrong return result from min_element with a comparator overloading operator&"); } }; @@ -149,7 +149,7 @@ struct check_maxelement_overloaded_address_of operator()(Policy&& exec, Iterator begin, Iterator end) { const Iterator expect = std::max_element(begin, end); - const Iterator result = std::max_element(std::forward(exec), begin, end, OverloadedAddressOfLess()); + const Iterator result = oneapi::dpl::max_element(std::forward(exec), begin, end, OverloadedAddressOfLess()); EXPECT_EQ(expect, result, "wrong return result from max_element with a comparator overloading operator&"); } }; @@ -162,8 +162,7 @@ struct check_minmaxelement_overloaded_address_of operator()(Policy&& exec, Iterator begin, Iterator end) { const std::pair expect = std::minmax_element(begin, end); - const std::pair got = - std::minmax_element(std::forward(exec), begin, end, OverloadedAddressOfLess()); + const std::pair got = oneapi::dpl::minmax_element(std::forward(exec), begin, end, OverloadedAddressOfLess()); EXPECT_EQ(expect, got, "wrong return result from minmax_element with a comparator overloading operator&"); } }; @@ -359,7 +358,7 @@ struct test_non_const_max_element void operator()(Policy&& exec, Iterator iter) { - max_element(std::forward(exec), iter, iter, non_const(std::less())); + std::max_element(std::forward(exec), iter, iter, non_const(std::less())); } }; @@ -370,7 +369,7 @@ struct test_non_const_min_element void operator()(Policy&& exec, Iterator iter) { - min_element(std::forward(exec), iter, iter, non_const(std::less())); + std::min_element(std::forward(exec), iter, iter, non_const(std::less())); } }; @@ -381,7 +380,7 @@ struct test_non_const_minmax_element void operator()(Policy&& exec, Iterator iter) { - minmax_element(std::forward(exec), iter, iter, non_const(std::less())); + std::minmax_element(std::forward(exec), iter, iter, non_const(std::less())); } }; From 6cb706ec8434c22f39165dd1686ce16e639c1294 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Fri, 28 Aug 2026 17:28:12 +0200 Subject: [PATCH 045/148] test/parallel_api/ranges/std_ranges_memory_test.h --- .../ranges/std_ranges_memory_test.h | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/test/parallel_api/ranges/std_ranges_memory_test.h b/test/parallel_api/ranges/std_ranges_memory_test.h index b9dfed877b6..77b8c441ad5 100644 --- a/test/parallel_api/ranges/std_ranges_memory_test.h +++ b/test/parallel_api/ranges/std_ranges_memory_test.h @@ -54,25 +54,30 @@ struct Elem_0 template constexpr int test_mode_id = 0; -template +// OutElem is the element type of the output range of two-range algorithms (uninitialized_copy, +// uninitialized_move); it may differ from the input element type Elem. +template struct test_memory_algo { void run_host(auto algo, auto checker, auto&&... args) { std::allocator alloc; - run_one_policy(alloc, oneapi::dpl::execution::seq, algo, checker, args...); - run_one_policy(alloc, oneapi::dpl::execution::unseq, algo, checker, args...); - run_one_policy(alloc, oneapi::dpl::execution::par, algo, checker, args...); - run_one_policy(alloc, oneapi::dpl::execution::par_unseq, algo, checker, std::forward(args)...); + std::allocator out_alloc; + run_one_policy(alloc, out_alloc, oneapi::dpl::execution::seq, algo, checker, args...); + run_one_policy(alloc, out_alloc, oneapi::dpl::execution::unseq, algo, checker, args...); + run_one_policy(alloc, out_alloc, oneapi::dpl::execution::par, algo, checker, args...); + run_one_policy(alloc, out_alloc, oneapi::dpl::execution::par_unseq, algo, checker, + std::forward(args)...); } #if TEST_DPCPP_BACKEND_PRESENT void run_device(auto algo, auto checker, auto&&... args) { //sycl::usm::alloc _alloc_type - auto policy = TestUtils::get_dpcpp_test_policy(); + auto policy = TestUtils::get_dpcpp_test_policy(); sycl::usm_allocator q_alloc{policy.queue()}; + sycl::usm_allocator q_out_alloc{policy.queue()}; - run_one_policy(q_alloc, policy, algo, checker, std::forward(args)...); + run_one_policy(q_alloc, q_out_alloc, policy, algo, checker, std::forward(args)...); } #endif //TEST_DPCPP_BACKEND_PRESENT @@ -86,7 +91,7 @@ struct test_memory_algo private: // Tests both subrange and span - void run_one_policy(auto& alloc, auto&& policy, auto algo, auto checker, auto&&... args) + void run_one_policy(auto& alloc, auto& out_alloc, auto&& policy, auto algo, auto checker, auto&&... args) { const std::size_t n_in = medium_size; Elem* data_in1 = alloc.allocate(n_in); @@ -100,12 +105,12 @@ struct test_memory_algo if constexpr (test_mode_id> == 1) { const std::size_t n_out = n_in / 2; // to check minimal size logic - Elem* data_out1 = alloc.allocate(n_out); - Elem* data_out2 = alloc.allocate(n_out); + OutElem* data_out1 = out_alloc.allocate(n_out); + OutElem* data_out2 = out_alloc.allocate(n_out); std::ranges::subrange subrange_out(data_out1, data_out1 + n_out); std::span span_out(data_out2, n_out); - std::memset(reinterpret_cast(data_out1), no_init_val, n_out*sizeof(Elem)); - std::memset(reinterpret_cast(data_out2), no_init_val, n_out*sizeof(Elem)); + std::memset(reinterpret_cast(data_out1), no_init_val, n_out*sizeof(OutElem)); + std::memset(reinterpret_cast(data_out2), no_init_val, n_out*sizeof(OutElem)); std::uninitialized_fill(data_in1, data_in1 + n_in, 5); std::uninitialized_fill(data_in2, data_in2 + n_in, 5); @@ -115,8 +120,8 @@ struct test_memory_algo run_impl(CLONE_TEST_POLICY_IDX(policy, 1), algo, checker, std::move(span_in), std::move(span_out), std::forward(args)...); #endif - alloc.deallocate(data_out1, n_out); - alloc.deallocate(data_out2, n_out); + out_alloc.deallocate(data_out1, n_out); + out_alloc.deallocate(data_out2, n_out); } // One range: destroy, uninitialized_fill, uninitialized_default_construct, uninitialized_value_construct else From cfa65875d344881dd88a5fac0df37447e6e4d9c2 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Fri, 28 Aug 2026 17:28:18 +0200 Subject: [PATCH 046/148] test/parallel_api/ranges/std_ranges_algo_archetypes_test.h --- .../ranges/std_ranges_algo_archetypes_test.h | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 test/parallel_api/ranges/std_ranges_algo_archetypes_test.h diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h b/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h new file mode 100644 index 00000000000..d94b91c5d40 --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h @@ -0,0 +1,135 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#ifndef _STD_RANGES_ALGO_ARCHETYPES_TEST_H +#define _STD_RANGES_ALGO_ARCHETYPES_TEST_H + +#include + +#include "support/test_config.h" +#include "support/utils.h" + +#if _ENABLE_STD_RANGES_TESTING + +#include "std_ranges_archetypes.h" + +#include +#include +#include +#include + +namespace test_std_ranges +{ + +// The archetypes are neither copyable nor movable, so they cannot live in a container: the storage +// is raw memory with in-place constructed elements, wrapped into archetype_view, which is random +// access and sized but neither contiguous nor common. This leaves the implementation no way to fall +// back to raw pointer arithmetic or to a hidden copy of the elements. +inline constexpr std::size_t archetype_test_size = 1000; + +// Runs a one-range algorithm and checks the result with __checker(view, result). +template +void +run_algo(_Alloc __alloc, _Policy&& __policy, _Algo __algo, _Checker __checker, const char* __algo_name) +{ + archetypes::archetype_storage<_Elem, _Alloc> __storage(__alloc, archetype_test_size, + [](std::size_t __i) { return (int)__i; }); + auto __view = __storage.view(); + + auto __res = __algo(std::forward<_Policy>(__policy), __view); + + EXPECT_TRUE(__checker(__view, __res), (std::string("wrong result from ") + __algo_name).c_str()); +} + +// Runs a two-range algorithm and checks the result with __checker(view1, view2, result). +template +void +run_algo2(_Alloc1 __alloc1, _Alloc2 __alloc2, _Policy&& __policy, _Algo __algo, _Checker __checker, + const char* __algo_name) +{ + archetypes::archetype_storage<_Elem1, _Alloc1> __storage1(__alloc1, archetype_test_size, + [](std::size_t __i) { return (int)__i; }); + archetypes::archetype_storage<_Elem2, _Alloc2> __storage2(__alloc2, archetype_test_size, + [](std::size_t __i) { return (int)__i; }); + auto __view1 = __storage1.view(); + auto __view2 = __storage2.view(); + + auto __res = __algo(std::forward<_Policy>(__policy), __view1, __view2); + + EXPECT_TRUE(__checker(__view1, __view2, __res), (std::string("wrong result from ") + __algo_name).c_str()); +} + +// Runs a one-range algorithm with the host policies only. A value argument which is neither +// copyable nor movable cannot be passed to a device kernel, so such an archetype is meaningful for +// the host policies only, where the implementation is required to keep a reference to the value. +template +void +run_algo_host_policies(_Algo __algo, _Checker __checker, const char* __algo_name) +{ + std::allocator<_Elem> __alloc; + run_algo<_Elem>(__alloc, oneapi::dpl::execution::seq, __algo, __checker, __algo_name); + run_algo<_Elem>(__alloc, oneapi::dpl::execution::unseq, __algo, __checker, __algo_name); + run_algo<_Elem>(__alloc, oneapi::dpl::execution::par, __algo, __checker, __algo_name); + run_algo<_Elem>(__alloc, oneapi::dpl::execution::par_unseq, __algo, __checker, __algo_name); +} + +// Runs a two-range algorithm with the host policies only, see run_algo_host_policies. +template +void +run_algo2_host_policies(_Algo __algo, _Checker __checker, const char* __algo_name) +{ + std::allocator<_Elem1> __alloc1; + std::allocator<_Elem2> __alloc2; + run_algo2<_Elem1, _Elem2>(__alloc1, __alloc2, oneapi::dpl::execution::seq, __algo, __checker, __algo_name); + run_algo2<_Elem1, _Elem2>(__alloc1, __alloc2, oneapi::dpl::execution::unseq, __algo, __checker, __algo_name); + run_algo2<_Elem1, _Elem2>(__alloc1, __alloc2, oneapi::dpl::execution::par, __algo, __checker, __algo_name); + run_algo2<_Elem1, _Elem2>(__alloc1, __alloc2, oneapi::dpl::execution::par_unseq, __algo, __checker, __algo_name); +} + +// _CallId makes the SYCL kernel name of the device call unique: every instantiation of the harness +// submits its own kernel, and with -fno-sycl-unnamed-lambda two kernels sharing a name are a +// "definition with same mangled name" error. +template +void +run_algo_all_policies(_Algo __algo, _Checker __checker, const char* __algo_name) +{ + run_algo_host_policies<_Elem>(__algo, __checker, __algo_name); + +#if TEST_DPCPP_BACKEND_PRESENT + auto __policy = TestUtils::get_dpcpp_test_policy<_CallId>(); + sycl::usm_allocator<_Elem, sycl::usm::alloc::shared> __q_alloc{__policy.queue()}; + run_algo<_Elem>(__q_alloc, __policy, __algo, __checker, __algo_name); +#endif //TEST_DPCPP_BACKEND_PRESENT +} + +template +void +run_algo2_all_policies(_Algo __algo, _Checker __checker, const char* __algo_name) +{ + run_algo2_host_policies<_Elem1, _Elem2>(__algo, __checker, __algo_name); + +#if TEST_DPCPP_BACKEND_PRESENT + auto __policy = TestUtils::get_dpcpp_test_policy<_CallId>(); + sycl::usm_allocator<_Elem1, sycl::usm::alloc::shared> __q_alloc1{__policy.queue()}; + sycl::usm_allocator<_Elem2, sycl::usm::alloc::shared> __q_alloc2{__policy.queue()}; + run_algo2<_Elem1, _Elem2>(__q_alloc1, __q_alloc2, __policy, __algo, __checker, __algo_name); +#endif //TEST_DPCPP_BACKEND_PRESENT +} + +} //namespace test_std_ranges + +#endif //_ENABLE_STD_RANGES_TESTING +#endif //_STD_RANGES_ALGO_ARCHETYPES_TEST_H From ef0e0d5e4e56c2753f68c11c02a1bb579962b56a Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Fri, 28 Aug 2026 17:28:24 +0200 Subject: [PATCH 047/148] test/parallel_api/ranges/std_ranges_archetypes.h --- .../ranges/std_ranges_archetypes.h | 1061 +++++++++++++++++ 1 file changed, 1061 insertions(+) create mode 100644 test/parallel_api/ranges/std_ranges_archetypes.h diff --git a/test/parallel_api/ranges/std_ranges_archetypes.h b/test/parallel_api/ranges/std_ranges_archetypes.h new file mode 100644 index 00000000000..8ebc92eb46e --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_archetypes.h @@ -0,0 +1,1061 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#ifndef _STD_RANGES_ARCHETYPES_H +#define _STD_RANGES_ARCHETYPES_H + +#if _ENABLE_STD_RANGES_TESTING + +#include +#include +#include +#include +#include +#include +#include + +// The types below are "archetypes": each of them satisfies exactly the constraints written in the +// requires-clause of the corresponding oneapi::dpl::ranges algorithm and nothing more. Every +// operation which is not implied by those constraints is explicitly deleted. If an algorithm +// compiles and works with an archetype, the implementation does not silently require more from a +// user type than it declares; otherwise the extra requirement shows up as a compilation error. +// +// Each archetype keeps two observable fields, val1 and val2, so that a test can check which part of +// the raw memory has been written, exactly as the pre-existing Elem/Elem_0 types do. + +// Unary operator& is not required by any constraint, so a conforming implementation has to use +// std::addressof instead of taking the address directly. Define this macro to 0 to relax the +// archetypes if the deleted operator& hides other findings. +#ifndef TEST_ARCHETYPE_DELETE_ADDRESSOF +# define TEST_ARCHETYPE_DELETE_ADDRESSOF 1 +#endif + +#if TEST_ARCHETYPE_DELETE_ADDRESSOF +# define TEST_ARCHETYPE_DELETED_ADDRESSOF void operator&() const = delete; +#else +# define TEST_ARCHETYPE_DELETED_ADDRESSOF +#endif + +// Deletes everything a "regular" type would provide but no constraint of the tested algorithms asks +// for: copying, moving, assignment and taking the address. +#define TEST_ARCHETYPE_DELETED_OPERATIONS(_Name) \ + _Name(const _Name&) = delete; \ + _Name(_Name&&) = delete; \ + _Name& operator=(const _Name&) = delete; \ + _Name& operator=(_Name&&) = delete; \ + TEST_ARCHETYPE_DELETED_ADDRESSOF + +namespace test_std_ranges +{ +namespace archetypes +{ + +// std::default_initializable, required by uninitialized_default_construct. +// The default constructor is user-provided, so default- and value-initialization are the same and +// val2 is left untouched by the algorithm. +struct default_construct_archetype +{ + int val1; + int val2; + + default_construct_archetype() { val1 = 1; } + + TEST_ARCHETYPE_DELETED_OPERATIONS(default_construct_archetype) +}; + +static_assert(std::default_initializable); +static_assert(std::destructible); +static_assert(!std::copy_constructible); +static_assert(!std::move_constructible); +static_assert(!std::equality_comparable); +static_assert(!std::swappable); + +// std::default_initializable, required by uninitialized_value_construct. +// The default constructor is defaulted on its first declaration and therefore is not user-provided: +// value-initialization zero-initializes the whole object, which lets the test tell value +// construction apart from default construction. +struct value_construct_archetype +{ + int val1; + int val2; + + value_construct_archetype() = default; + + TEST_ARCHETYPE_DELETED_OPERATIONS(value_construct_archetype) +}; + +static_assert(std::default_initializable); +static_assert(std::destructible); +static_assert(!std::copy_constructible); +static_assert(!std::move_constructible); +static_assert(!std::equality_comparable); +static_assert(!std::swappable); + +// The _T template parameter of uninitialized_fill is deduced from the value argument, so the filler +// type is deliberately different from the range value type: the only required conversion is +// std::constructible_from, const fill_source&>. +struct fill_source +{ + int val; +}; + +struct fill_archetype +{ + int val1; + int val2; + + explicit fill_archetype(const fill_source& src) { val2 = src.val; } + + TEST_ARCHETYPE_DELETED_OPERATIONS(fill_archetype) +}; + +static_assert(std::constructible_from); +static_assert(std::destructible); +static_assert(!std::default_initializable); +static_assert(!std::copy_constructible); +static_assert(!std::move_constructible); + +// Input element type of uninitialized_copy and uninitialized_move. No constraint is imposed on it +// besides forming a random access range, so it is only constructible from an int, which is what the +// test harness uses to prepare the input data. +struct transfer_source +{ + int val1; + int val2; + + explicit transfer_source(int v) { val2 = v; } + + TEST_ARCHETYPE_DELETED_OPERATIONS(transfer_source) +}; + +static_assert(std::destructible); +static_assert(!std::default_initializable); +static_assert(!std::copy_constructible); +static_assert(!std::move_constructible); + +// std::constructible_from, range_reference_t<_InRange>>, required by +// uninitialized_copy. range_reference_t of a range of transfer_source is exactly transfer_source&, +// so the implementation must not pass a const lvalue or an rvalue instead. +struct copy_archetype +{ + int val1; + int val2; + + explicit copy_archetype(transfer_source& src) { val2 = src.val2; } + + TEST_ARCHETYPE_DELETED_OPERATIONS(copy_archetype) +}; + +static_assert(std::constructible_from); +static_assert(std::destructible); +static_assert(!std::constructible_from); +static_assert(!std::constructible_from); +static_assert(!std::default_initializable); +static_assert(!std::copy_constructible); + +// std::constructible_from, range_rvalue_reference_t<_InRange>>, required by +// uninitialized_move. Only an rvalue is accepted, so the implementation has to move the source +// element (std::ranges::iter_move) rather than copy it. +struct move_archetype +{ + int val1; + int val2; + + explicit move_archetype(transfer_source&& src) { val2 = src.val2; } + + TEST_ARCHETYPE_DELETED_OPERATIONS(move_archetype) +}; + +static_assert(std::constructible_from); +static_assert(std::destructible); +static_assert(!std::constructible_from); +static_assert(!std::default_initializable); +static_assert(!std::copy_constructible); + +// std::destructible, required by destroy. No constructor at all is declared, which is enough for the +// test: the harness works on raw memory and only observes the effect of the destructor. +struct destroy_archetype +{ + int val1; + volatile int val2; // volatile prevents optimization of the destructor observed with g++ + + ~destroy_archetype() { val2 = 3; } + + TEST_ARCHETYPE_DELETED_OPERATIONS(destroy_archetype) +}; + +static_assert(std::destructible); +static_assert(!std::default_initializable); +static_assert(!std::copy_constructible); +static_assert(!std::move_constructible); + +// A random access iterator which is deliberately not a contiguous one. Unlike a pointer, a span +// iterator or a subrange over pointers, it gives the implementation no way to fall back to raw +// pointer arithmetic on the underlying storage. +template +class archetype_iterator +{ + T* ptr = nullptr; + + public: + using iterator_concept = std::random_access_iterator_tag; + using iterator_category = std::random_access_iterator_tag; + using value_type = T; + using difference_type = std::ptrdiff_t; + using reference = T&; + using pointer = T*; + + archetype_iterator() = default; + explicit archetype_iterator(T* p) : ptr(p) {} + + T* base() const { return ptr; } + + reference operator*() const { return *ptr; } + pointer operator->() const { return ptr; } + reference operator[](difference_type n) const { return ptr[n]; } + + archetype_iterator& operator++() { ++ptr; return *this; } + archetype_iterator operator++(int) { auto tmp = *this; ++ptr; return tmp; } + archetype_iterator& operator--() { --ptr; return *this; } + archetype_iterator operator--(int) { auto tmp = *this; --ptr; return tmp; } + + archetype_iterator& operator+=(difference_type n) { ptr += n; return *this; } + archetype_iterator& operator-=(difference_type n) { ptr -= n; return *this; } + + friend archetype_iterator operator+(archetype_iterator i, difference_type n) { return i += n; } + friend archetype_iterator operator+(difference_type n, archetype_iterator i) { return i += n; } + friend archetype_iterator operator-(archetype_iterator i, difference_type n) { return i -= n; } + friend difference_type operator-(archetype_iterator i, archetype_iterator j) { return i.ptr - j.ptr; } + + friend bool operator==(archetype_iterator i, archetype_iterator j) { return i.ptr == j.ptr; } + friend auto operator<=>(archetype_iterator i, archetype_iterator j) { return i.ptr <=> j.ptr; } +}; + +// A sentinel type distinct from the iterator, which makes the range non-common while keeping it +// sized via the sized_sentinel_for requirement. +template +class archetype_sentinel +{ + T* ptr = nullptr; + + public: + archetype_sentinel() = default; + explicit archetype_sentinel(T* p) : ptr(p) {} + + T* base() const { return ptr; } + + friend bool operator==(archetype_iterator i, archetype_sentinel s) { return i.base() == s.ptr; } + friend std::ptrdiff_t operator-(archetype_iterator i, archetype_sentinel s) { return i.base() - s.ptr; } + friend std::ptrdiff_t operator-(archetype_sentinel s, archetype_iterator i) { return s.ptr - i.base(); } +}; + +// A view over raw storage which satisfies __nothrow_random_access_range and sized_range, but is +// neither contiguous nor common. It is marked as a borrowed range so that the algorithms keep +// returning a real iterator rather than std::ranges::dangling. +template +class archetype_view : public std::ranges::view_interface> +{ + T* first = nullptr; + T* last = nullptr; + + public: + archetype_view() = default; + archetype_view(T* p, std::size_t n) : first(p), last(p + n) {} + + archetype_iterator begin() const { return archetype_iterator(first); } + archetype_sentinel end() const { return archetype_sentinel(last); } +}; + +} // namespace archetypes +} // namespace test_std_ranges + +template +inline constexpr bool std::ranges::enable_borrowed_range> = true; + +namespace test_std_ranges +{ +namespace archetypes +{ + +static_assert(std::random_access_iterator>); +static_assert(!std::contiguous_iterator>); +static_assert(std::sized_sentinel_for, archetype_iterator>); + +static_assert(std::ranges::random_access_range>); +static_assert(std::ranges::sized_range>); +static_assert(std::ranges::borrowed_range>); +static_assert(!std::ranges::contiguous_range>); +static_assert(!std::ranges::common_range>); + +// The two extra requirements of __nothrow_random_access_range beyond random_access_range. +static_assert(std::is_lvalue_reference_v>>); +static_assert(std::same_as>>, + std::ranges::range_value_t>>); + +// Owns raw storage and constructs the elements in place. The archetypes are neither copyable nor +// movable, so they cannot be kept in a standard container; the allocator is a template parameter so +// that the very same storage works with std::allocator on the host and with sycl::usm_allocator on +// a device. +template +class archetype_storage +{ + Alloc alloc; + std::size_t count = 0; + T* data = nullptr; + + public: + // _Factory is called as __factory(i) for every index and has to return the arguments of the + // element constructor. + template + archetype_storage(Alloc __alloc, std::size_t __n, _Factory __factory) : alloc(__alloc), count(__n) + { + data = alloc.allocate(count); + for (std::size_t __i = 0; __i < count; ++__i) + std::construct_at(data + __i, __factory(__i)); + } + + archetype_storage(const archetype_storage&) = delete; + archetype_storage& operator=(const archetype_storage&) = delete; + + ~archetype_storage() + { + for (std::size_t __i = 0; __i < count; ++__i) + std::destroy_at(data + __i); + alloc.deallocate(data, count); + } + + std::size_t size() const { return count; } + T* begin_ptr() const { return data; } + + archetype_view view() const { return archetype_view(data, count); } +}; + +//------------------------------------------------------------------------------------------------ +// Archetypes for the algorithms of glue_algorithm_ranges_impl.h +// +// Every algorithm there constrains its range parameters with std::ranges::random_access_range and +// std::ranges::sized_range only; all the remaining requirements are expressed as indirect concepts +// on the iterators. The element archetypes below therefore drop everything a "regular" type would +// have and add back exactly the operations one concept family needs. archetype_view is reused as the +// range, so the ranges are random access and sized but neither contiguous nor common. +//------------------------------------------------------------------------------------------------ + +// Family 1: read-only algorithms parameterized by a callable. +// std::indirectly_unary_invocable / std::indirect_unary_predicate / std::indirect_strict_weak_order / +// std::indirect_equivalence_relation only require the callable to be invocable with the projected +// value; they impose nothing at all on the element type itself. +// Used by: for_each, find_if, find_if_not, find_last_if, find_last_if_not, any_of, all_of, none_of, +// count_if, is_partitioned, adjacent_find, is_sorted, is_sorted_until, is_heap, is_heap_until, +// min_element, max_element, minmax_element, lexicographical_compare, includes. +struct read_archetype +{ + int val; + + explicit read_archetype(int __v) : val(__v) {} + + TEST_ARCHETYPE_DELETED_OPERATIONS(read_archetype) +}; + +static_assert(std::destructible); +static_assert(!std::default_initializable); +static_assert(!std::copy_constructible); +static_assert(!std::move_constructible); +static_assert(!std::equality_comparable); +static_assert(!std::totally_ordered); + +// The callables take exactly const _T& and return exactly the required type, so an implementation +// cannot pass an rvalue, a copy, or expect a wider return type. +struct read_unary_fun +{ + void operator()(const read_archetype&) const {} +}; + +struct read_unary_pred +{ + bool operator()(const read_archetype& __v) const { return __v.val % 3 == 0; } +}; + +struct read_binary_pred +{ + bool operator()(const read_archetype& __v1, const read_archetype& __v2) const { return __v1.val == __v2.val; } +}; + +struct read_comp +{ + bool operator()(const read_archetype& __v1, const read_archetype& __v2) const { return __v1.val < __v2.val; } +}; + +// A projection which returns a prvalue of an unrelated type, so nothing links the projected type +// back to the element type. +struct read_proj_result +{ + int val; +}; + +struct read_proj +{ + read_proj_result operator()(const read_archetype& __v) const { return read_proj_result{__v.val}; } +}; + +struct read_proj_pred +{ + bool operator()(const read_proj_result& __v) const { return __v.val % 3 == 0; } +}; + +using read_iterator_t = std::ranges::iterator_t>; + +static_assert(std::indirectly_unary_invocable); +static_assert(std::indirect_unary_predicate); +static_assert(std::indirect_binary_predicate); +static_assert(std::indirect_strict_weak_order); +static_assert(std::indirect_unary_predicate>); + +// Family 2: algorithms taking a search value. +// The constraint is +// std::indirect_binary_predicate, _Proj>, +// const _T*> +// std::ranges::equal_to is itself constrained by std::equality_comparable_with, which is much +// stronger than a bare `element == value`: both types have to be equality comparable with +// themselves and to share a common reference type. The archetypes below provide exactly that and +// nothing else, in particular they are still neither copyable nor movable. +// Used by: find, find_last, count, contains, remove, remove_copy, replace, replace_copy. +// The value is passed to a device kernel by copy, so, unlike the other archetypes, it has to be +// trivially copyable and thus device copyable. Everything else a "regular" type provides is still +// missing: no default constructor, no ordering, no relation to the element type but equality. +struct nocopy_search_value; + +struct search_value +{ + int val; + + explicit search_value(int __v) : val(__v) {} + + search_value(const search_value&) = default; + search_value& operator=(const search_value&) = default; + + friend bool operator==(const search_value& __v1, const search_value& __v2) { return __v1.val == __v2.val; } +}; + +struct searchable_archetype +{ + int val; + + explicit searchable_archetype(int __v) : val(__v) {} + + TEST_ARCHETYPE_DELETED_OPERATIONS(searchable_archetype) + + friend bool operator==(const searchable_archetype& __e1, const searchable_archetype& __e2) + { + return __e1.val == __e2.val; + } + + friend bool operator==(const searchable_archetype& __e, const search_value& __v) { return __e.val == __v.val; } + + friend bool operator==(const searchable_archetype& __e, const nocopy_search_value& __v); +}; + +// Family 2b: the very same constraint, but the search value is neither copyable nor movable. +// std::indirect_binary_predicate, _Proj>, +// const _T*> says nothing about copying _T, so a host policy must keep a reference to the value +// instead of storing a copy of it. A device policy legitimately copies the value into the kernel, +// so this archetype is only ever used with the host policies. +struct nocopy_search_value +{ + int val; + + explicit nocopy_search_value(int __v) : val(__v) {} + + TEST_ARCHETYPE_DELETED_OPERATIONS(nocopy_search_value) + + friend bool operator==(const nocopy_search_value& __v1, const nocopy_search_value& __v2) + { + return __v1.val == __v2.val; + } +}; + +inline bool +operator==(const searchable_archetype& __e, const nocopy_search_value& __v) +{ + return __e.val == __v.val; +} + +// The element archetype of the removing algorithms. remove() requires +// std::permutable> && indirect_binary_predicate +// so the element has to be movable, but still not copyable and not default constructible. +struct removable_archetype +{ + int val; + + explicit removable_archetype(int __v) : val(__v) {} + + removable_archetype(removable_archetype&& __other) : val(__other.val) {} + + removable_archetype& + operator=(removable_archetype&& __other) + { + val = __other.val; + return *this; + } + + removable_archetype(const removable_archetype&) = delete; + removable_archetype& operator=(const removable_archetype&) = delete; + TEST_ARCHETYPE_DELETED_ADDRESSOF + + friend bool operator==(const removable_archetype& __e1, const removable_archetype& __e2) + { + return __e1.val == __e2.val; + } + + friend bool operator==(const removable_archetype& __e, const nocopy_search_value& __v) + { + return __e.val == __v.val; + } + + friend bool operator==(const removable_archetype& __e, const search_value& __v) { return __e.val == __v.val; } +}; + +// The common reference required by std::equality_comparable_with. It is only ever formed as a +// reference by the concept machinery, so a minimal type which both archetypes convert to is enough. +struct search_common +{ + int val; + + search_common(const searchable_archetype& __e) : val(__e.val) {} + search_common(const removable_archetype& __e) : val(__e.val) {} + search_common(const search_value& __v) : val(__v.val) {} + search_common(const nocopy_search_value& __v) : val(__v.val) {} + + friend bool operator==(const search_common& __v1, const search_common& __v2) { return __v1.val == __v2.val; } +}; + +} // namespace archetypes +} // namespace test_std_ranges + +namespace std +{ +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; +} // namespace std + +namespace test_std_ranges +{ +namespace archetypes +{ + +using searchable_iterator_t = std::ranges::iterator_t>; + +static_assert(std::indirect_binary_predicate); +static_assert( + std::indirect_binary_predicate); +static_assert(!std::copy_constructible); +static_assert(!std::move_constructible); +static_assert(!std::default_initializable); + +using removable_iterator_t = std::ranges::iterator_t>; + +static_assert(std::permutable); +static_assert(std::indirect_binary_predicate); +static_assert(std::indirect_binary_predicate); +static_assert(!std::copy_constructible); +static_assert(!std::default_initializable); +static_assert(!std::totally_ordered); +static_assert(!std::copy_constructible); +static_assert(!std::move_constructible); +static_assert(std::is_trivially_copyable_v); +static_assert(!std::default_initializable); +static_assert(!std::totally_ordered); +static_assert(!std::default_initializable); +static_assert(!std::totally_ordered); + +// Family 3: two-range algorithms constrained by std::indirectly_comparable. +// std::indirectly_comparable only asks for the predicate to be +// invocable on the two projected references, so the two element types stay unrelated and neither of +// them is comparable with itself. +// Used by: equal, mismatch, search, find_end, find_first_of, contains_subrange, starts_with, +// ends_with. +struct lhs_archetype +{ + int val; + + explicit lhs_archetype(int __v) : val(__v) {} + + TEST_ARCHETYPE_DELETED_OPERATIONS(lhs_archetype) +}; + +struct rhs_archetype +{ + int val; + + explicit rhs_archetype(int __v) : val(__v) {} + + TEST_ARCHETYPE_DELETED_OPERATIONS(rhs_archetype) +}; + +struct cross_pred +{ + bool operator()(const lhs_archetype& __v1, const rhs_archetype& __v2) const { return __v1.val == __v2.val; } +}; + +using lhs_iterator_t = std::ranges::iterator_t>; +using rhs_iterator_t = std::ranges::iterator_t>; + +static_assert(std::indirectly_comparable); +static_assert(!std::equality_comparable); +static_assert(!std::equality_comparable); +static_assert(!std::copy_constructible); +static_assert(!std::copy_constructible); + +// Family 4: algorithms writing a value into the range itself. +// The constraint is std::indirectly_writable, const _T&>, which needs `*it = value` +// for a const lvalue value and nothing else: the element still does not have to be copyable, +// movable or default constructible, and _T stays an unrelated type. +// Used by: fill, replace_if, replace (new value), replace_copy_if / replace_copy (new value). +struct write_value +{ + int val; + + explicit write_value(int __v) : val(__v) {} + + TEST_ARCHETYPE_DELETED_OPERATIONS(write_value) +}; + +struct writable_archetype +{ + int val; + + explicit writable_archetype(int __v) : val(__v) {} + + writable_archetype(const writable_archetype&) = delete; + writable_archetype(writable_archetype&&) = delete; + writable_archetype& operator=(const writable_archetype&) = delete; + writable_archetype& operator=(writable_archetype&&) = delete; + TEST_ARCHETYPE_DELETED_ADDRESSOF + + writable_archetype& operator=(const write_value& __v) + { + val = __v.val; + return *this; + } +}; + +using writable_iterator_t = std::ranges::iterator_t>; + +static_assert(std::indirectly_writable); +static_assert(!std::copyable); +static_assert(!std::movable); +static_assert(!std::default_initializable); + +// Family 5: copying algorithms. +// std::indirectly_copyable == indirectly_readable && indirectly_writable>, so the output element only has to be assignable from a non-const lvalue of +// the input element type. Neither element type has to be copyable, movable or default +// constructible, and the two types are deliberately different. +// Used by: copy, copy_if, reverse_copy, rotate_copy, remove_copy, remove_copy_if, unique_copy, +// replace_copy, replace_copy_if, partition_copy, partial_sort_copy. +struct copy_in_archetype +{ + int val; + + explicit copy_in_archetype(int __v) : val(__v) {} + + TEST_ARCHETYPE_DELETED_OPERATIONS(copy_in_archetype) +}; + +struct copy_out_archetype +{ + int val; + + explicit copy_out_archetype(int __v) : val(__v) {} + + copy_out_archetype(const copy_out_archetype&) = delete; + copy_out_archetype(copy_out_archetype&&) = delete; + copy_out_archetype& operator=(const copy_out_archetype&) = delete; + copy_out_archetype& operator=(copy_out_archetype&&) = delete; + TEST_ARCHETYPE_DELETED_ADDRESSOF + + copy_out_archetype& operator=(copy_in_archetype& __v) + { + val = __v.val; + return *this; + } +}; + +using copy_in_iterator_t = std::ranges::iterator_t>; +using copy_out_iterator_t = std::ranges::iterator_t>; + +static_assert(std::indirectly_copyable); +static_assert(!std::copyable); +static_assert(!std::copyable); +static_assert(!std::default_initializable); + +// Family 6: the move algorithm. +// std::indirectly_movable asks for indirectly_writable>, +// so the output element is only assignable from an rvalue of the input element type: an +// implementation which copies instead of moving does not compile. +struct move_in_archetype +{ + int val; + + explicit move_in_archetype(int __v) : val(__v) {} + + TEST_ARCHETYPE_DELETED_OPERATIONS(move_in_archetype) +}; + +struct move_out_archetype +{ + int val; + + explicit move_out_archetype(int __v) : val(__v) {} + + move_out_archetype(const move_out_archetype&) = delete; + move_out_archetype(move_out_archetype&&) = delete; + move_out_archetype& operator=(const move_out_archetype&) = delete; + move_out_archetype& operator=(move_out_archetype&&) = delete; + TEST_ARCHETYPE_DELETED_ADDRESSOF + + move_out_archetype& operator=(move_in_archetype&& __v) + { + val = __v.val; + return *this; + } +}; + +using move_in_iterator_t = std::ranges::iterator_t>; +using move_out_iterator_t = std::ranges::iterator_t>; + +static_assert(std::indirectly_movable); +// An lvalue is explicitly rejected, so copying instead of moving is a compilation error. +static_assert(!std::indirectly_copyable); +static_assert(!std::movable); + +// Family 7: swap_ranges. +// std::indirectly_swappable needs std::ranges::swap on the two references, both ways. A +// dedicated hidden-friend swap is provided, so the element does not have to be move constructible +// or move assignable, which is what the fallback std::swap would require. +struct swap_archetype +{ + int val; + + explicit swap_archetype(int __v) : val(__v) {} + + TEST_ARCHETYPE_DELETED_OPERATIONS(swap_archetype) + + friend void swap(swap_archetype& __v1, swap_archetype& __v2) + { + const int __tmp = __v1.val; + __v1.val = __v2.val; + __v2.val = __tmp; + } +}; + +using swap_iterator_t = std::ranges::iterator_t>; + +static_assert(std::indirectly_swappable); +static_assert(!std::movable); +static_assert(!std::move_constructible); +static_assert(!std::default_initializable); + +// Family 8: transform. +// The output constraint is +// std::indirectly_writable, std::indirect_result_t<_F&, projected...>> +// so the output element is only assignable from the result of the functor, which is a third, +// unrelated type. _F itself is only required to be std::copy_constructible. +struct transform_in_archetype +{ + int val; + + explicit transform_in_archetype(int __v) : val(__v) {} + + TEST_ARCHETYPE_DELETED_OPERATIONS(transform_in_archetype) +}; + +// The result of the functor. indirectly_writable requires the assignment to work for the prvalue, +// the const lvalue and the const rvalue forms of the result type, which a prvalue-returning functor +// naturally provides. +struct transform_result +{ + int val; +}; + +struct transform_out_archetype +{ + int val; + + explicit transform_out_archetype(int __v) : val(__v) {} + + transform_out_archetype(const transform_out_archetype&) = delete; + transform_out_archetype(transform_out_archetype&&) = delete; + transform_out_archetype& operator=(const transform_out_archetype&) = delete; + transform_out_archetype& operator=(transform_out_archetype&&) = delete; + TEST_ARCHETYPE_DELETED_ADDRESSOF + + transform_out_archetype& operator=(const transform_result& __v) + { + val = __v.val; + return *this; + } +}; + +struct transform_unary_op +{ + transform_result operator()(const transform_in_archetype& __v) const { return transform_result{__v.val * 2}; } +}; + +struct transform_binary_op +{ + transform_result operator()(const transform_in_archetype& __v1, const transform_in_archetype& __v2) const + { + return transform_result{__v1.val + __v2.val}; + } +}; + +using transform_in_iterator_t = std::ranges::iterator_t>; +using transform_out_iterator_t = std::ranges::iterator_t>; + +static_assert(std::copy_constructible); +static_assert(std::copy_constructible); +static_assert(std::indirectly_writable>); +static_assert(std::indirectly_writable< + transform_out_iterator_t, + std::indirect_result_t>); +static_assert(!std::copyable); +static_assert(!std::default_initializable); + +// Family 9: permuting algorithms. +// std::permutable == forward_iterator && indirectly_movable_storable && +// indirectly_swappable, which does require the element to be movable and move +// constructible, but still not copyable, not default constructible and not comparable. +// Used by: reverse, rotate, shift_left, shift_right, remove_if, remove, unique, partition, +// stable_partition. +struct permutable_archetype +{ + int val; + + explicit permutable_archetype(int __v) : val(__v) {} + + permutable_archetype(permutable_archetype&& __other) : val(__other.val) {} + + permutable_archetype& operator=(permutable_archetype&& __other) + { + val = __other.val; + return *this; + } + + permutable_archetype(const permutable_archetype&) = delete; + permutable_archetype& operator=(const permutable_archetype&) = delete; + TEST_ARCHETYPE_DELETED_ADDRESSOF +}; + +using permutable_iterator_t = std::ranges::iterator_t>; + +static_assert(std::permutable); +static_assert(!std::copy_constructible); +static_assert(!std::default_initializable); +static_assert(!std::equality_comparable); +static_assert(!std::totally_ordered); + +// The predicate and the comparator of the permuting algorithms only see the projected reference. +struct permutable_pred +{ + bool operator()(const permutable_archetype& __v) const { return __v.val % 3 == 0; } +}; + +struct permutable_equiv +{ + bool operator()(const permutable_archetype& __v1, const permutable_archetype& __v2) const + { + return __v1.val == __v2.val; + } +}; + +// std::sortable == permutable && indirect_strict_weak_order<_Comp, +// projected>, so the very same element archetype works and the ordering has to come from +// the comparator, never from an operator< on the element. +// Used by: sort, stable_sort, partial_sort, inplace_merge, nth_element, partial_sort_copy. +struct permutable_comp +{ + bool operator()(const permutable_archetype& __v1, const permutable_archetype& __v2) const + { + return __v1.val < __v2.val; + } +}; + +static_assert(std::sortable); + +// The merge family additionally needs std::indirectly_copyable from both inputs into the output. +// The output element is therefore assignable from a non-const lvalue of either input element type, +// while remaining non-copyable itself. +// Used by: merge, set_union, set_intersection, set_difference, set_symmetric_difference. +struct merge_in_archetype +{ + int val; + + explicit merge_in_archetype(int __v) : val(__v) {} + + merge_in_archetype(merge_in_archetype&& __other) : val(__other.val) {} + + merge_in_archetype& operator=(merge_in_archetype&& __other) + { + val = __other.val; + return *this; + } + + merge_in_archetype(const merge_in_archetype&) = delete; + merge_in_archetype& operator=(const merge_in_archetype&) = delete; + TEST_ARCHETYPE_DELETED_ADDRESSOF +}; + +struct merge_out_archetype +{ + int val; + + explicit merge_out_archetype(int __v) : val(__v) {} + + merge_out_archetype(merge_out_archetype&& __other) : val(__other.val) {} + + merge_out_archetype& operator=(merge_out_archetype&& __other) + { + val = __other.val; + return *this; + } + + merge_out_archetype(const merge_out_archetype&) = delete; + merge_out_archetype& operator=(const merge_out_archetype&) = delete; + TEST_ARCHETYPE_DELETED_ADDRESSOF + + merge_out_archetype& operator=(merge_in_archetype& __v) + { + val = __v.val; + return *this; + } +}; + +struct merge_comp +{ + bool operator()(const merge_in_archetype& __v1, const merge_in_archetype& __v2) const + { + return __v1.val < __v2.val; + } +}; + +using merge_in_iterator_t = std::ranges::iterator_t>; +using merge_out_iterator_t = std::ranges::iterator_t>; + +static_assert(std::mergeable); +static_assert(!std::copy_constructible); +static_assert(!std::copy_constructible); +static_assert(!std::default_initializable); + +// min / max / minmax additionally require +// std::indirectly_copyable_storable, range_value_t<_R>*>, which does need a copy +// constructor and copy assignment, but still no default constructor and no ordering operator. +struct storable_archetype +{ + int val; + + explicit storable_archetype(int __v) : val(__v) {} + + storable_archetype(const storable_archetype& __other) : val(__other.val) {} + + storable_archetype& operator=(const storable_archetype& __other) + { + val = __other.val; + return *this; + } + + TEST_ARCHETYPE_DELETED_ADDRESSOF +}; + +struct storable_comp +{ + bool operator()(const storable_archetype& __v1, const storable_archetype& __v2) const + { + return __v1.val < __v2.val; + } +}; + +using storable_iterator_t = std::ranges::iterator_t>; + +static_assert(std::indirectly_copyable_storable); +static_assert(std::indirect_strict_weak_order); +static_assert(!std::default_initializable); +static_assert(!std::equality_comparable); +static_assert(!std::totally_ordered); + +} // namespace archetypes +} // namespace test_std_ranges + +#if TEST_DPCPP_BACKEND_PRESENT +namespace sycl +{ + template <> + struct is_device_copyable : std::true_type { }; + + template <> + struct is_device_copyable : std::true_type { }; + + template <> + struct is_device_copyable : std::true_type { }; +} +#endif + +#endif // _ENABLE_STD_RANGES_TESTING +#endif // _STD_RANGES_ARCHETYPES_H From 83694f4f053f08a918713c7d403760c1cd2cf628 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Fri, 28 Aug 2026 17:28:52 +0200 Subject: [PATCH 048/148] test/support/test_config.h --- test/support/test_config.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/support/test_config.h b/test/support/test_config.h index 1852dcd6a99..eae090589b7 100644 --- a/test/support/test_config.h +++ b/test/support/test_config.h @@ -364,4 +364,18 @@ // std::input_iterator and std::output_iterator on the same pre-P2325R3 implementations. #define _ONEDPL_CPP20_IN_OUT_ITERATOR_BROKEN TEST_STD_RANGES_VIEW_CONCEPT_REQUIRES_DEFAULT_INITIALIZABLE +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_CONTAINS 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_COUNT 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FILL 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_MAX_ELEMENT 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_IF 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE 1 + #endif // _TEST_CONFIG_H From 3365e900f266928ce5bf06b36e0c921b6bd43661 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Fri, 28 Aug 2026 17:28:56 +0200 Subject: [PATCH 049/148] test/parallel_api/ranges/std_ranges_memory_archetypes.pass.cpp --- .../std_ranges_memory_archetypes.pass.cpp | 190 ++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 test/parallel_api/ranges/std_ranges_memory_archetypes.pass.cpp diff --git a/test/parallel_api/ranges/std_ranges_memory_archetypes.pass.cpp b/test/parallel_api/ranges/std_ranges_memory_archetypes.pass.cpp new file mode 100644 index 00000000000..0f87e3c91c0 --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_memory_archetypes.pass.cpp @@ -0,0 +1,190 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#include +#include + +#include "support/test_config.h" +#include "support/test_macros.h" +#include "support/utils.h" + +#if _ENABLE_STD_RANGES_TESTING +#include "std_ranges_memory_test.h" +#include "std_ranges_archetypes.h" + +#include + +namespace test_std_ranges +{ +template<> +constexpr int test_mode_id> = 1; +template<> +constexpr int test_mode_id> = 1; + +// Every algorithm below must be callable with the archetype which satisfies exactly its declared +// constraints. A failure here means the constraints are not sufficient to call the algorithm. +static_assert(std::invocable&>); +static_assert(std::invocable&>); +static_assert(std::invocable&>); + +// Runs a one-range algorithm over archetype_view, which is random access and sized but neither +// contiguous nor common, so the implementation cannot fall back to raw pointer arithmetic. +template +void +run_over_archetype_view(Alloc& alloc, Policy&& policy, Algo algo, Checker checker, const char* algo_name) +{ + const std::size_t n = medium_size; + Elem* data = alloc.allocate(n); + std::memset(reinterpret_cast(data), -1, n * sizeof(Elem)); // -1 means no initialization + + archetypes::archetype_view view(data, n); + + auto res = algo(std::forward(policy), view); + + EXPECT_TRUE(res == view.begin() + n, (std::string("wrong return value from ") + algo_name + + " over archetype_view").c_str()); + EXPECT_TRUE(std::ranges::all_of(view, checker), (std::string("wrong effect from ") + algo_name + + " over archetype_view").c_str()); + + alloc.deallocate(data, n); +} + +template +void +run_archetype_view_all_policies(Algo algo, Checker checker, const char* algo_name) +{ + std::allocator alloc; + run_over_archetype_view(alloc, oneapi::dpl::execution::seq, algo, checker, algo_name); + run_over_archetype_view(alloc, oneapi::dpl::execution::unseq, algo, checker, algo_name); + run_over_archetype_view(alloc, oneapi::dpl::execution::par, algo, checker, algo_name); + run_over_archetype_view(alloc, oneapi::dpl::execution::par_unseq, algo, checker, algo_name); + +#if TEST_DPCPP_BACKEND_PRESENT + auto policy = TestUtils::get_dpcpp_test_policy(); + sycl::usm_allocator q_alloc{policy.queue()}; + run_over_archetype_view(q_alloc, policy, algo, checker, algo_name); +#endif //TEST_DPCPP_BACKEND_PRESENT +} + +} //namespace test_std_ranges +#endif //_ENABLE_STD_RANGES_TESTING + +int +main() +{ +#if _ENABLE_STD_RANGES_TESTING + using namespace test_std_ranges; + using namespace test_std_ranges::archetypes; + namespace dpl_ranges = oneapi::dpl::ranges; + + // The single required operation is std::default_initializable. The default constructor is + // user-provided, so only val1 is written and val2 must keep the no-initialization pattern. + auto default_construct_checker = + [](const auto& res, const auto& r) { + using R = std::remove_cvref_t; + bool bres1 = (res == std::ranges::borrowed_iterator_t(std::ranges::begin(r) + std::ranges::size(r))); + bool bres2 = std::ranges::all_of(r, [](const auto& v) { return v.val1 == 1 && v.val2 == -1;}); + + return std::pair{bres1, bres2}; + }; + + test_memory_algo{}.run(dpl_ranges::uninitialized_default_construct, default_construct_checker); + + // The default constructor is defaulted on its first declaration, so value-initialization + // zero-initializes the whole object, including val2. + auto value_construct_checker = + [](const auto& res, const auto& r) { + using R = std::remove_cvref_t; + bool bres1 = (res == std::ranges::borrowed_iterator_t(std::ranges::begin(r) + std::ranges::size(r))); + bool bres2 = std::ranges::all_of(r, [](const auto& v) { return v.val1 == 0 && v.val2 == 0;}); + + return std::pair{bres1, bres2}; + }; + + test_memory_algo{}.run(dpl_ranges::uninitialized_value_construct, value_construct_checker); + + // The filler type differs from the range value type, so the only required operation is + // std::constructible_from. + auto fill_checker = + [](const auto& res, const auto& r, const auto& value) { + using R = std::remove_cvref_t; + bool bres1 = (res == std::ranges::borrowed_iterator_t(std::ranges::begin(r) + std::ranges::size(r))); + bool bres2 = std::ranges::all_of(r, [](const auto& v) { return v.val1 == -1;}) + && std::ranges::all_of(r, [value](const auto& v) { return v.val2 == value.val;}); + + return std::pair{bres1, bres2}; + }; + + test_memory_algo{}.run(dpl_ranges::uninitialized_fill, fill_checker, fill_source{2}); + + // Input and output element types are different, which the requires-clause of uninitialized_copy + // and uninitialized_move explicitly allows. copy_archetype is constructible only from + // transfer_source&, move_archetype only from transfer_source&&. + auto transfer_checker = + [](const auto& res, auto&& r_in, auto&& r_out) { + using InRange = std::remove_cvref_t; + using OutRange = std::remove_cvref_t; + + using Size = std::common_type_t, std::ranges::range_size_t>; + const Size sz = std::ranges::min((Size)std::ranges::size(r_in), (Size)std::ranges::size(r_out)); + + const bool bres1 = (res.in == std::ranges::borrowed_iterator_t(std::ranges::begin(r_in) + sz) + && res.out == std::ranges::borrowed_iterator_t(std::ranges::begin(r_out) + sz)); + + const bool bres2 = std::ranges::all_of(r_out, [](const auto& v) { return v.val1 == -1;}) + && std::ranges::equal(std::ranges::take_view(r_in, sz), std::ranges::take_view(r_out, sz), + [](const auto& v1, const auto& v2) { return v1.val2 == v2.val2;}) + && std::ranges::all_of(std::ranges::drop_view(r_out, sz), [](const auto& v) { return v.val2 == -1;}); + + return std::pair{bres1, bres2}; + }; + + test_memory_algo{}.run(dpl_ranges::uninitialized_copy, transfer_checker); + test_memory_algo{}.run(dpl_ranges::uninitialized_move, transfer_checker); + + // The single required operation is std::destructible. + auto destroy_checker = + [](const auto& res, const auto& r) { + using R = std::remove_cvref_t; + bool bres1 = (res == std::ranges::borrowed_iterator_t(std::ranges::begin(r) + std::ranges::size(r))); + bool bres2 = std::ranges::all_of(r, [](const auto& v) { return v.val1 == -1 && v.val2 == 3;}); + + return std::pair{bres1, bres2}; + }; + + test_memory_algo{}.run(dpl_ranges::destroy, destroy_checker); + + // The same algorithms over a range which is random access and sized, but neither contiguous nor + // common. + run_archetype_view_all_policies( + dpl_ranges::uninitialized_default_construct, + [](const auto& v) { return v.val1 == 1 && v.val2 == -1; }, "uninitialized_default_construct"); + + run_archetype_view_all_policies( + dpl_ranges::uninitialized_value_construct, + [](const auto& v) { return v.val1 == 0 && v.val2 == 0; }, "uninitialized_value_construct"); + + run_archetype_view_all_policies( + dpl_ranges::destroy, [](const auto& v) { return v.val1 == -1 && v.val2 == 3; }, "destroy"); + +#endif //_ENABLE_STD_RANGES_TESTING + + return TestUtils::done(_ENABLE_STD_RANGES_TESTING); +} From 624a47b6d92d804e3c0d491d662894a8f97375a1 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Fri, 28 Aug 2026 17:29:00 +0200 Subject: [PATCH 050/148] test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp --- .../std_ranges_algo_archetypes_write.pass.cpp | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp new file mode 100644 index 00000000000..c8ada9d69d7 --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp @@ -0,0 +1,121 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#include +#include + +#include "support/test_config.h" +#include "support/test_macros.h" +#include "support/utils.h" + +#if _ENABLE_STD_RANGES_TESTING +#include "std_ranges_archetypes.h" +#include "std_ranges_algo_archetypes_test.h" + +namespace test_std_ranges +{ +namespace dpl_ranges = oneapi::dpl::ranges; + +using seq_policy = decltype(oneapi::dpl::execution::seq); + +using writable_view = archetypes::archetype_view; +using copy_in_view = archetypes::archetype_view; +using copy_out_view = archetypes::archetype_view; +using move_in_view = archetypes::archetype_view; +using move_out_view = archetypes::archetype_view; +using swap_view = archetypes::archetype_view; +using transform_in_view = archetypes::archetype_view; +using transform_out_view = archetypes::archetype_view; + +// fill only requires std::indirectly_writable, const _T&>: the element type is not +// required to be copyable, movable or default constructible and _T stays unrelated to it. +static_assert(std::invocable); + +// The copying algorithms only require std::indirectly_copyable, so the output element is merely +// assignable from a non-const lvalue of the input element type. +static_assert(std::invocable); + +// move requires std::indirectly_movable, which is strictly weaker: assigning from an lvalue is +// deliberately rejected by move_out_archetype, so an implementation copying instead of moving fails. +static_assert(std::invocable); + +// swap_ranges requires std::indirectly_swappable only, which the hidden friend swap provides +// without the element being move constructible or move assignable. +static_assert(std::invocable); + +// transform writes the result of the functor, which is a third unrelated type; the functor itself +// only has to be std::copy_constructible. +static_assert(std::invocable); + +} //namespace test_std_ranges +#endif //_ENABLE_STD_RANGES_TESTING + +int +main() +{ +#if _ENABLE_STD_RANGES_TESTING + using namespace test_std_ranges; + using namespace test_std_ranges::archetypes; + namespace dpl_ranges = oneapi::dpl::ranges; + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FILL + // None of the archetypes below is device copyable, so the host policies are the only ones the + // constraints of these algorithms allow. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::fill(std::forward(policy), view, write_value{42}); + }, + [](auto&& view, auto) { + return std::ranges::begin(view)[0].val == 42 && + std::ranges::begin(view)[std::ranges::size(view) - 1].val == 42; + }, + "fill"); +#endif + + run_algo2_all_policies( + [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::copy(std::forward(policy), in_view, out_view); + }, + [](auto&& in_view, auto&& out_view, auto) { + return std::ranges::begin(out_view)[7].val == std::ranges::begin(in_view)[7].val; + }, + "copy"); + + run_algo2_all_policies( + [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::move(std::forward(policy), in_view, out_view); + }, + [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 7; }, "move"); + + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::swap_ranges(std::forward(policy), view1, view2); + }, + [](auto&& view1, auto&& view2, auto) { + return std::ranges::begin(view1)[7].val == 7 && std::ranges::begin(view2)[7].val == 7; + }, + "swap_ranges"); + + run_algo2_all_policies( + [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::transform(std::forward(policy), in_view, out_view, + transform_unary_op{}); + }, + [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 14; }, "transform"); +#endif //_ENABLE_STD_RANGES_TESTING + + return TestUtils::done(_ENABLE_STD_RANGES_TESTING); +} From c09d8175ba1f58950346c30788b3211967bf42a6 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Fri, 28 Aug 2026 17:29:04 +0200 Subject: [PATCH 051/148] test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp --- .../std_ranges_algo_archetypes_value.pass.cpp | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp new file mode 100644 index 00000000000..6678abc0dd6 --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp @@ -0,0 +1,157 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#include +#include + +#include "support/test_config.h" +#include "support/test_macros.h" +#include "support/utils.h" + +#if _ENABLE_STD_RANGES_TESTING +#include "std_ranges_archetypes.h" +#include "std_ranges_algo_archetypes_test.h" + +namespace test_std_ranges +{ +namespace dpl_ranges = oneapi::dpl::ranges; + +// The value based algorithms are constrained by +// std::indirect_binary_predicate, _Proj>, +// const _T*> +// only. In particular the value type is not required to be copyable, to be comparable with itself +// with anything but std::ranges::equal_to, or to be related to the element type in any other way, +// and the element type is not required to be comparable with itself either. +using searchable_view = archetypes::archetype_view; +using removable_view = archetypes::archetype_view; +using seq_policy = decltype(oneapi::dpl::execution::seq); + +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); + +} //namespace test_std_ranges +#endif //_ENABLE_STD_RANGES_TESTING + +int +main() +{ +#if _ENABLE_STD_RANGES_TESTING + using namespace test_std_ranges; + using namespace test_std_ranges::archetypes; + namespace dpl_ranges = oneapi::dpl::ranges; + + // The storage is filled with the values 0, 1, 2, ... so the value 3 is found exactly once. + constexpr int searched = 3; + + // search_value is trivially copyable and thus device copyable, so it can be used with all the + // policies including the device ones. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find(std::forward(policy), view, search_value{searched}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find"); + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_last(std::forward(policy), view, search_value{searched}); + }, + [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, + "find_last"); +#endif + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count(std::forward(policy), view, search_value{searched}); + }, + [](auto&&, auto res) { return res == 1; }, "count"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::contains(std::forward(policy), view, search_value{searched}); + }, + [](auto&&, auto res) { return res; }, "contains"); + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE + // removable_archetype is movable but not device copyable, so remove() is checked on the host + // policies only. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::remove(std::forward(policy), view, search_value{searched}); + }, + [](auto&& view, auto res) { return std::ranges::size(res) == std::ranges::size(view) - 1; }, "remove"); +#endif + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND + // nocopy_search_value is neither copyable nor movable: the host implementations must refer to + // the value passed by the user instead of storing a copy of it. It cannot be captured by a + // device kernel, hence the host policies only. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find(std::forward(policy), view, nocopy_search_value{searched}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find, noncopyable value"); +#endif + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_last(std::forward(policy), view, nocopy_search_value{searched}); + }, + [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, + "find_last, noncopyable value"); +#endif + + // count() must refer to the value instead of storing a copy of it: the requires-clause never + // asks for a copyable value type. +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_COUNT + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count(std::forward(policy), view, nocopy_search_value{searched}); + }, + [](auto&&, auto res) { return res == 1; }, "count, noncopyable value"); +#endif + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_CONTAINS + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::contains(std::forward(policy), view, nocopy_search_value{searched}); + }, + [](auto&&, auto res) { return res; }, "contains, noncopyable value"); +#endif + + // Same for remove(): the predicate it builds internally must hold a reference to the value for + // the host policies. +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::remove(std::forward(policy), view, nocopy_search_value{searched}); + }, + [](auto&& view, auto res) { return std::ranges::size(res) == std::ranges::size(view) - 1; }, + "remove, noncopyable value"); +#endif + +#endif //_ENABLE_STD_RANGES_TESTING + + return TestUtils::done(_ENABLE_STD_RANGES_TESTING); +} From 000149cc842a959f8da7927ca05a227271bc87e1 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Fri, 28 Aug 2026 17:29:11 +0200 Subject: [PATCH 052/148] test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp --- .../std_ranges_algo_archetypes_read.pass.cpp | 220 ++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp new file mode 100644 index 00000000000..48345cc107c --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp @@ -0,0 +1,220 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#include +#include + +#include "support/test_config.h" +#include "support/test_macros.h" +#include "support/utils.h" + +#if _ENABLE_STD_RANGES_TESTING +#include "std_ranges_archetypes.h" +#include "std_ranges_algo_archetypes_test.h" + +namespace test_std_ranges +{ +namespace dpl_ranges = oneapi::dpl::ranges; + +// Every algorithm below has to be callable with an archetype which satisfies exactly its declared +// constraints. A failure here means the implementation requires more from a user type than the +// requires-clause of the algorithm declares. +using read_view = archetypes::archetype_view; +using seq_policy = decltype(oneapi::dpl::execution::seq); + +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); + +// The projection is allowed to return a completely unrelated type, so the algorithm must never +// apply the predicate to the raw element. +static_assert(std::invocable); +static_assert(std::invocable); + +// The search value type of find/count/contains is unrelated to the element type. +using searchable_view = archetypes::archetype_view; + +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); + +// Two-range algorithms only require the predicate to accept the two projected references; the two +// element types stay unrelated and neither of them is comparable with itself. +using lhs_view = archetypes::archetype_view; +using rhs_view = archetypes::archetype_view; + +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); + +} //namespace test_std_ranges +#endif //_ENABLE_STD_RANGES_TESTING + +int +main() +{ +#if _ENABLE_STD_RANGES_TESTING + using namespace test_std_ranges; + using namespace test_std_ranges::archetypes; + namespace dpl_ranges = oneapi::dpl::ranges; + + // read_archetype is neither copyable, movable, default constructible nor comparable; the only + // operations available are the ones the callables of the algorithm provide. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_if(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "find_if"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_if_not(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + 1; }, "find_if_not"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::any_of(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&&, bool res) { return res; }, "any_of"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::all_of(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&&, bool res) { return !res; }, "all_of"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count_if(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&& view, auto res) { return res == (std::ranges::range_difference_t) + ((std::ranges::size(view) + 2) / 3); }, "count_if"); + + // The projection returns an unrelated prvalue type, so the predicate can only ever be applied to + // the projected value. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count_if(std::forward(policy), view, read_proj_pred{}, read_proj{}); + }, + [](auto&& view, auto res) { return res == (std::ranges::range_difference_t) + ((std::ranges::size(view) + 2) / 3); }, "count_if with proj"); + + // KSATODO: min_element/max_element/minmax_element only require std::indirect_strict_weak_order + // on the projected iterator, so the element type itself has to stay non-copyable and + // non-default-constructible. Both backends store the element by value instead of keeping an + // iterator to it, so the calls below do not compile: + // - unseq_backend_simd.h:635,637,649,662,663,666 - the _ComplexType helper of + // __simd_min_element holds a _ValueType member, value initializes it in its default + // constructor and copy assigns it while scanning; + // - algorithm_ranges_impl_hetero.h:1569 / utils_hetero.h:125 / tuple_impl.h:276 - the hetero + // path builds a std::pair and copies the element into it. + // Fixing this means carrying the index only and dereferencing the iterator for the comparison. +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_MAX_ELEMENT + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::min_element(std::forward(policy), view, read_comp{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "min_element"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::max_element(std::forward(policy), view, read_comp{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view) - 1; }, + "max_element"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::minmax_element(std::forward(policy), view, read_comp{}); + }, + [](auto&& view, auto res) { + return res.min == std::ranges::begin(view) && + res.max == std::ranges::begin(view) + std::ranges::size(view) - 1; + }, + "minmax_element"); +#endif + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_sorted(std::forward(policy), view, read_comp{}); + }, + [](auto&&, bool res) { return res; }, "is_sorted"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::adjacent_find(std::forward(policy), view, read_binary_pred{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, + "adjacent_find"); + + // The search value type is unrelated to the element type. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find(std::forward(policy), view, search_value{7}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + 7; }, "find"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count(std::forward(policy), view, search_value{7}); + }, + [](auto&&, auto res) { return res == 1; }, "count"); + + // Two ranges of unrelated element types, compared only through the user predicate. + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::equal(std::forward(policy), view1, view2, cross_pred{}); + }, + [](auto&&, auto&&, bool res) { return res; }, "equal"); + + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::mismatch(std::forward(policy), view1, view2, cross_pred{}); + }, + [](auto&& view1, auto&& view2, auto res) { + return res.in1 == std::ranges::begin(view1) + std::ranges::size(view1) && + res.in2 == std::ranges::begin(view2) + std::ranges::size(view2); + }, + "mismatch"); + +#endif //_ENABLE_STD_RANGES_TESTING + + return TestUtils::done(_ENABLE_STD_RANGES_TESTING); +} From ddcd6c915c841d1e69074638d688074c6cd8765f Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Fri, 28 Aug 2026 17:29:15 +0200 Subject: [PATCH 053/148] test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp --- ...td_ranges_algo_archetypes_permute.pass.cpp | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp new file mode 100644 index 00000000000..949dbfdba91 --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp @@ -0,0 +1,131 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#include +#include + +#include "support/test_config.h" +#include "support/test_macros.h" +#include "support/utils.h" + +#if _ENABLE_STD_RANGES_TESTING +#include "std_ranges_archetypes.h" +#include "std_ranges_algo_archetypes_test.h" + +namespace test_std_ranges +{ +namespace dpl_ranges = oneapi::dpl::ranges; + +using seq_policy = decltype(oneapi::dpl::execution::seq); + +using permutable_view = archetypes::archetype_view; + +// The permuting algorithms are constrained by std::permutable> only, which requires +// the element to be movable, but not copyable, not default constructible and not comparable: any +// ordering or equality has to come from the comparator passed by the user. +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); + +// std::sortable == permutable && indirect_strict_weak_order<...>, so the very +// same element archetype works and the ordering never comes from an operator< on the element. +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); + +} //namespace test_std_ranges +#endif //_ENABLE_STD_RANGES_TESTING + +int +main() +{ +#if _ENABLE_STD_RANGES_TESTING + using namespace test_std_ranges; + using namespace test_std_ranges::archetypes; + namespace dpl_ranges = oneapi::dpl::ranges; + + // permutable_archetype is movable but not copyable, so it is not device copyable either: the + // host policies are the only ones its constraints allow. + run_algo_all_policies( + [](auto&& policy, auto&& view) { return dpl_ranges::reverse(std::forward(policy), view); }, + [](auto&& view, auto) { + const auto n = std::ranges::size(view); + return std::ranges::begin(view)[0].val == (int)n - 1 && std::ranges::begin(view)[n - 1].val == 0; + }, + "reverse"); + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_IF + // The storage is filled with 0, 1, 2, ... so every third element is removed. The returned + // subrange is the tail holding the removed elements. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::remove_if(std::forward(policy), view, permutable_pred{}); + }, + [](auto&& view, auto res) { + const auto n = std::ranges::size(view); + return std::ranges::size(res) == (n + 2) / 3; + }, + "remove_if"); +#endif + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE + // All the elements are unique, so nothing is dropped. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::unique(std::forward(policy), view, permutable_equiv{}); + }, + [](auto&& view, auto res) { return std::ranges::size(res) == 0; }, "unique"); +#endif + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT + // prpbably incorrect type applied + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::sort(std::forward(policy), view, permutable_comp{}); + }, + [](auto&& view, auto) { + return std::ranges::begin(view)[0].val == 0 && + std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; + }, + "sort"); +#endif + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT + // prpbably incorrect type applied + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::stable_sort(std::forward(policy), view, permutable_comp{}); + }, + [](auto&& view, auto) { + return std::ranges::begin(view)[0].val == 0 && + std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; + }, + "stable_sort"); +#endif + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_sorted(std::forward(policy), view, permutable_comp{}); + }, + [](auto&&, auto res) { return res; }, "is_sorted"); +#endif //_ENABLE_STD_RANGES_TESTING + + return TestUtils::done(_ENABLE_STD_RANGES_TESTING); +} From 8bb789a40d6ba48d8cf2895b2879ddd35cdadc7c Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Fri, 28 Aug 2026 17:29:19 +0200 Subject: [PATCH 054/148] test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp --- .../std_ranges_algo_archetypes_merge.pass.cpp | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp new file mode 100644 index 00000000000..4410fb6daa6 --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp @@ -0,0 +1,149 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#include +#include + +#include "support/test_config.h" +#include "support/test_macros.h" +#include "support/utils.h" + +#if _ENABLE_STD_RANGES_TESTING +#include "std_ranges_archetypes.h" +#include "std_ranges_algo_archetypes_test.h" + +namespace test_std_ranges +{ +namespace dpl_ranges = oneapi::dpl::ranges; + +using seq_policy = decltype(oneapi::dpl::execution::seq); + +using merge_in_view = archetypes::archetype_view; +using merge_out_view = archetypes::archetype_view; +using storable_view = archetypes::archetype_view; + +// The merge family is constrained by std::mergeable, which asks for indirectly_copyable from both +// inputs into the output plus a strict weak order: the output element stays non-copyable itself and +// the ordering never comes from an operator< on the element. +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); + +// min / max / minmax additionally require std::indirectly_copyable_storable, +// range_value_t<_R>*>, which does need a copy constructor and copy assignment, but still no default +// constructor and no ordering operator on the element. +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); + +} //namespace test_std_ranges +#endif //_ENABLE_STD_RANGES_TESTING + +int +main() +{ +#if _ENABLE_STD_RANGES_TESTING + using namespace test_std_ranges; + using namespace test_std_ranges::archetypes; + namespace dpl_ranges = oneapi::dpl::ranges; + + // Neither archetype is device copyable, so the host policies are the only ones their + // constraints allow. + + // Both inputs hold the very same sorted sequence 0, 1, 2, ... + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + archetype_storage> out_storage( + std::allocator{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = dpl_ranges::merge(std::forward(policy), view1, view2, out_view, merge_comp{}); + return std::ranges::begin(out_view)[0].val == 0 && std::ranges::begin(out_view)[1].val == 0 && + std::ranges::begin(out_view)[2].val == 1 && res.out == std::ranges::end(out_view); + }, + [](auto&&, auto&&, auto res) { return res; }, "merge"); + + // KSATODO: the set operations only require std::mergeable, i.e. indirectly_copyable from either + // input into the output, which is an assignment and not a construction. The implementation + // instead constructs the output element into raw memory, so the calls below do not compile: + // - set_algorithms_utils.h:91 - placement new of _OutValueType from *__it_in, which also takes + // the address of the element through std::addressof; + // - set_algorithms_utils.h:127,133,206 / memory_impl.h:96,111 - __uninitialized_copy_or_discard + // default constructs and copy constructs the output element type. + // Fixing this means assigning through the output iterator instead of constructing in place. +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + archetype_storage> out_storage( + std::allocator{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = + dpl_ranges::set_union(std::forward(policy), view1, view2, out_view, merge_comp{}); + // The two inputs hold the very same sequence, so the union is that sequence itself. + return std::ranges::begin(out_view)[7].val == 7 && + (std::size_t)(res.out - std::ranges::begin(out_view)) == archetype_test_size; + }, + [](auto&&, auto&&, auto res) { return res; }, "set_union"); +#endif + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + archetype_storage> out_storage( + std::allocator{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = dpl_ranges::set_difference(std::forward(policy), view1, view2, out_view, + merge_comp{}); + // The two inputs are equal, so the difference is empty. + return res.out == std::ranges::begin(out_view); + }, + [](auto&&, auto&&, auto res) { return res; }, "set_difference"); +#endif + + // KSATODO: min / max / minmax only require std::indirectly_copyable_storable, which needs a copy + // constructor and copy assignment, but no default constructor. The helpers of __simd_min_element + // and __simd_minmax_element at unseq_backend_simd.h:635 and :695 value initialize their + // _ValueType members in the default constructor, so the calls below do not compile with a + // non-default-constructible element type. +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_MAX_ELEMENT + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::min(std::forward(policy), view, storable_comp{}); + }, + [](auto&&, auto res) { return res.val == 0; }, "min"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::max(std::forward(policy), view, storable_comp{}); + }, + [](auto&&, auto res) { return res.val == (int)archetype_test_size - 1; }, "max"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::minmax(std::forward(policy), view, storable_comp{}); + }, + [](auto&&, auto&& res) { return res.min.val == 0 && res.max.val == (int)archetype_test_size - 1; }, "minmax"); +#endif + +#endif //_ENABLE_STD_RANGES_TESTING + + return TestUtils::done(_ENABLE_STD_RANGES_TESTING); +} From f8638ca4b0df0c66161cf455f513bb3807c54a98 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Mon, 31 Aug 2026 11:09:15 +0200 Subject: [PATCH 055/148] Split broken tests to host- and hetero-policy parts --- .../std_ranges_algo_archetypes_merge.pass.cpp | 69 +++++++++++++-- ...td_ranges_algo_archetypes_permute.pass.cpp | 61 +++++++++++-- .../std_ranges_algo_archetypes_read.pass.cpp | 38 ++++++++- .../ranges/std_ranges_algo_archetypes_test.h | 32 +++++-- .../std_ranges_algo_archetypes_value.pass.cpp | 85 ++++++++++++++++--- .../std_ranges_algo_archetypes_write.pass.cpp | 17 +++- test/support/test_config.h | 51 ++++++++--- 7 files changed, 297 insertions(+), 56 deletions(-) diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp index 4410fb6daa6..03b30f472b9 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp @@ -89,8 +89,8 @@ main() // - set_algorithms_utils.h:127,133,206 / memory_impl.h:96,111 - __uninitialized_copy_or_discard // default constructs and copy constructs the output element type. // Fixing this means assigning through the output iterator instead of constructing in place. -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION - run_algo2_all_policies( +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HOST + run_algo2_host_policies( [](auto&& policy, auto&& view1, auto&& view2) { archetype_storage> out_storage( std::allocator{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); @@ -103,9 +103,37 @@ main() }, [](auto&&, auto&&, auto res) { return res; }, "set_union"); #endif +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HETERO + run_algo2_hetero_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + archetype_storage> out_storage( + std::allocator{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = + dpl_ranges::set_union(std::forward(policy), view1, view2, out_view, merge_comp{}); + // The two inputs hold the very same sequence, so the union is that sequence itself. + return std::ranges::begin(out_view)[7].val == 7 && + (std::size_t)(res.out - std::ranges::begin(out_view)) == archetype_test_size; + }, + [](auto&&, auto&&, auto res) { return res; }, "set_union"); +#endif + -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE - run_algo2_all_policies( +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HOST + run_algo2_host_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + archetype_storage> out_storage( + std::allocator{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = dpl_ranges::set_difference(std::forward(policy), view1, view2, out_view, + merge_comp{}); + // The two inputs are equal, so the difference is empty. + return res.out == std::ranges::begin(out_view); + }, + [](auto&&, auto&&, auto res) { return res; }, "set_difference"); +#endif +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HETERO + run_algo2_hetero_policies( [](auto&& policy, auto&& view1, auto&& view2) { archetype_storage> out_storage( std::allocator{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); @@ -123,20 +151,45 @@ main() // and __simd_minmax_element at unseq_backend_simd.h:635 and :695 value initialize their // _ValueType members in the default constructor, so the calls below do not compile with a // non-default-constructible element type. -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_MAX_ELEMENT - run_algo_all_policies( +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_HOST + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::min(std::forward(policy), view, storable_comp{}); + }, + [](auto&&, auto res) { return res.val == 0; }, "min"); +#endif +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_HETERO + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::min(std::forward(policy), view, storable_comp{}); }, [](auto&&, auto res) { return res.val == 0; }, "min"); +#endif - run_algo_all_policies( +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_HOST + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::max(std::forward(policy), view, storable_comp{}); + }, + [](auto&&, auto res) { return res.val == (int)archetype_test_size - 1; }, "max"); +#endif +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_HETERO + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::max(std::forward(policy), view, storable_comp{}); }, [](auto&&, auto res) { return res.val == (int)archetype_test_size - 1; }, "max"); +#endif - run_algo_all_policies( +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_HOST + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::minmax(std::forward(policy), view, storable_comp{}); + }, + [](auto&&, auto&& res) { return res.min.val == 0 && res.max.val == (int)archetype_test_size - 1; }, "minmax"); +#endif +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_HETERO + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::minmax(std::forward(policy), view, storable_comp{}); }, diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp index 949dbfdba91..c51794a649a 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp @@ -71,10 +71,23 @@ main() }, "reverse"); -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_IF +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_IF_HOST // The storage is filled with 0, 1, 2, ... so every third element is removed. The returned // subrange is the tail holding the removed elements. - run_algo_all_policies( + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::remove_if(std::forward(policy), view, permutable_pred{}); + }, + [](auto&& view, auto res) { + const auto n = std::ranges::size(view); + return std::ranges::size(res) == (n + 2) / 3; + }, + "remove_if"); +#endif +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_IF_HETERO + // The storage is filled with 0, 1, 2, ... so every third element is removed. The returned + // subrange is the tail holding the removed elements. + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::remove_if(std::forward(policy), view, permutable_pred{}); }, @@ -85,18 +98,38 @@ main() "remove_if"); #endif -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE_HOST // All the elements are unique, so nothing is dropped. - run_algo_all_policies( + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::unique(std::forward(policy), view, permutable_equiv{}); + }, + [](auto&& view, auto res) { return std::ranges::size(res) == 0; }, "unique"); +#endif +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE_HETERO + // All the elements are unique, so nothing is dropped. + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::unique(std::forward(policy), view, permutable_equiv{}); }, [](auto&& view, auto res) { return std::ranges::size(res) == 0; }, "unique"); #endif -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT_HOST // prpbably incorrect type applied - run_algo_all_policies( + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::sort(std::forward(policy), view, permutable_comp{}); + }, + [](auto&& view, auto) { + return std::ranges::begin(view)[0].val == 0 && + std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; + }, + "sort"); +#endif +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT_HETERO + // prpbably incorrect type applied + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::sort(std::forward(policy), view, permutable_comp{}); }, @@ -107,9 +140,21 @@ main() "sort"); #endif -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT_HOST + // prpbably incorrect type applied + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::stable_sort(std::forward(policy), view, permutable_comp{}); + }, + [](auto&& view, auto) { + return std::ranges::begin(view)[0].val == 0 && + std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; + }, + "stable_sort"); +#endif +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT_HETERO // prpbably incorrect type applied - run_algo_all_policies( + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::stable_sort(std::forward(policy), view, permutable_comp{}); }, diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp index 48345cc107c..49c7d95a3c9 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp @@ -146,21 +146,51 @@ main() // - algorithm_ranges_impl_hetero.h:1569 / utils_hetero.h:125 / tuple_impl.h:276 - the hetero // path builds a std::pair and copies the element into it. // Fixing this means carrying the index only and dereferencing the iterator for the comparison. -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_MAX_ELEMENT - run_algo_all_policies( +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_ELEMENT_HOST + run_algo_host_policies( [](auto&& policy, auto&& view) { return dpl_ranges::min_element(std::forward(policy), view, read_comp{}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "min_element"); +#endif +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_ELEMENT_HETERO + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::min_element(std::forward(policy), view, read_comp{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "min_element"); +#endif - run_algo_all_policies( +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_ELEMENT_HOST + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::max_element(std::forward(policy), view, read_comp{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view) - 1; }, + "max_element"); +#endif +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_ELEMENT_HETERO + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::max_element(std::forward(policy), view, read_comp{}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view) - 1; }, "max_element"); +#endif - run_algo_all_policies( +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_ELEMENT_HOST + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::minmax_element(std::forward(policy), view, read_comp{}); + }, + [](auto&& view, auto res) { + return res.min == std::ranges::begin(view) && + res.max == std::ranges::begin(view) + std::ranges::size(view) - 1; + }, + "minmax_element"); +#endif +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_ELEMENT_HETERO + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::minmax_element(std::forward(policy), view, read_comp{}); }, diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h b/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h index d94b91c5d40..2c4ae2a32ce 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h @@ -99,6 +99,17 @@ run_algo2_host_policies(_Algo __algo, _Checker __checker, const char* __algo_nam run_algo2<_Elem1, _Elem2>(__alloc1, __alloc2, oneapi::dpl::execution::par_unseq, __algo, __checker, __algo_name); } +#if TEST_DPCPP_BACKEND_PRESENT +template +void +run_algo_hetero_policies(_Algo __algo, _Checker __checker, const char* __algo_name) +{ + auto __policy = TestUtils::get_dpcpp_test_policy<_CallId>(); + sycl::usm_allocator<_Elem, sycl::usm::alloc::shared> __q_alloc{__policy.queue()}; + run_algo<_Elem>(__q_alloc, __policy, __algo, __checker, __algo_name); +} +#endif + // _CallId makes the SYCL kernel name of the device call unique: every instantiation of the harness // submits its own kernel, and with -fno-sycl-unnamed-lambda two kernels sharing a name are a // "definition with same mangled name" error. @@ -109,12 +120,22 @@ run_algo_all_policies(_Algo __algo, _Checker __checker, const char* __algo_name) run_algo_host_policies<_Elem>(__algo, __checker, __algo_name); #if TEST_DPCPP_BACKEND_PRESENT - auto __policy = TestUtils::get_dpcpp_test_policy<_CallId>(); - sycl::usm_allocator<_Elem, sycl::usm::alloc::shared> __q_alloc{__policy.queue()}; - run_algo<_Elem>(__q_alloc, __policy, __algo, __checker, __algo_name); + run_algo_hetero_policies<_Elem, _CallId, _Algo, _Checker>(__algo, __checker, __algo_name); #endif //TEST_DPCPP_BACKEND_PRESENT } +#if TEST_DPCPP_BACKEND_PRESENT +template +void +run_algo2_hetero_policies(_Algo __algo, _Checker __checker, const char* __algo_name) +{ + auto __policy = TestUtils::get_dpcpp_test_policy<_CallId>(); + sycl::usm_allocator<_Elem1, sycl::usm::alloc::shared> __q_alloc1{__policy.queue()}; + sycl::usm_allocator<_Elem2, sycl::usm::alloc::shared> __q_alloc2{__policy.queue()}; + run_algo2<_Elem1, _Elem2>(__q_alloc1, __q_alloc2, __policy, __algo, __checker, __algo_name); +} +#endif + template void run_algo2_all_policies(_Algo __algo, _Checker __checker, const char* __algo_name) @@ -122,10 +143,7 @@ run_algo2_all_policies(_Algo __algo, _Checker __checker, const char* __algo_name run_algo2_host_policies<_Elem1, _Elem2>(__algo, __checker, __algo_name); #if TEST_DPCPP_BACKEND_PRESENT - auto __policy = TestUtils::get_dpcpp_test_policy<_CallId>(); - sycl::usm_allocator<_Elem1, sycl::usm::alloc::shared> __q_alloc1{__policy.queue()}; - sycl::usm_allocator<_Elem2, sycl::usm::alloc::shared> __q_alloc2{__policy.queue()}; - run_algo2<_Elem1, _Elem2>(__q_alloc1, __q_alloc2, __policy, __algo, __checker, __algo_name); + run_algo2_hetero_policies<_Elem1, _Elem2, _CallId, _Algo, _Checker>(__algo, __checker, __algo_name); #endif //TEST_DPCPP_BACKEND_PRESENT } diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp index 6678abc0dd6..9f6b5a50d60 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp @@ -71,8 +71,16 @@ main() }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find"); -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST - run_algo_all_policies( +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_HOST + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_last(std::forward(policy), view, search_value{searched}); + }, + [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, + "find_last"); +#endif +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_HETERO + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::find_last(std::forward(policy), view, search_value{searched}); }, @@ -92,29 +100,56 @@ main() }, [](auto&&, auto res) { return res; }, "contains"); -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_HOST + // removable_archetype is movable but not device copyable, so remove() is checked on the host + // policies only. + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::remove(std::forward(policy), view, search_value{searched}); + }, + [](auto&& view, auto res) { return std::ranges::size(res) == std::ranges::size(view) - 1; }, "remove"); +#endif +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_HETERO // removable_archetype is movable but not device copyable, so remove() is checked on the host // policies only. - run_algo_all_policies( + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::remove(std::forward(policy), view, search_value{searched}); }, [](auto&& view, auto res) { return std::ranges::size(res) == std::ranges::size(view) - 1; }, "remove"); #endif -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_HOST // nocopy_search_value is neither copyable nor movable: the host implementations must refer to // the value passed by the user instead of storing a copy of it. It cannot be captured by a // device kernel, hence the host policies only. - run_algo_all_policies( + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find(std::forward(policy), view, nocopy_search_value{searched}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find, noncopyable value"); +#endif +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_HETERO + // nocopy_search_value is neither copyable nor movable: the host implementations must refer to + // the value passed by the user instead of storing a copy of it. It cannot be captured by a + // device kernel, hence the host policies only. + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::find(std::forward(policy), view, nocopy_search_value{searched}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find, noncopyable value"); #endif -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST - run_algo_all_policies( +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_HOST + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_last(std::forward(policy), view, nocopy_search_value{searched}); + }, + [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, + "find_last, noncopyable value"); +#endif +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_HETERO + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::find_last(std::forward(policy), view, nocopy_search_value{searched}); }, @@ -124,16 +159,30 @@ main() // count() must refer to the value instead of storing a copy of it: the requires-clause never // asks for a copyable value type. -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_COUNT - run_algo_all_policies( +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_COUNT_HOST + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count(std::forward(policy), view, nocopy_search_value{searched}); + }, + [](auto&&, auto res) { return res == 1; }, "count, noncopyable value"); +#endif +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_COUNT_HETERO + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::count(std::forward(policy), view, nocopy_search_value{searched}); }, [](auto&&, auto res) { return res == 1; }, "count, noncopyable value"); #endif -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_CONTAINS - run_algo_all_policies( +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_CONTAINS_HOST + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::contains(std::forward(policy), view, nocopy_search_value{searched}); + }, + [](auto&&, auto res) { return res; }, "contains, noncopyable value"); +#endif +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_CONTAINS_HETERO + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::contains(std::forward(policy), view, nocopy_search_value{searched}); }, @@ -142,8 +191,16 @@ main() // Same for remove(): the predicate it builds internally must hold a reference to the value for // the host policies. -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE - run_algo_all_policies( +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_HOST + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::remove(std::forward(policy), view, nocopy_search_value{searched}); + }, + [](auto&& view, auto res) { return std::ranges::size(res) == std::ranges::size(view) - 1; }, + "remove, noncopyable value"); +#endif +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_HETERO + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::remove(std::forward(policy), view, nocopy_search_value{searched}); }, diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp index c8ada9d69d7..f3d86754ffe 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp @@ -71,10 +71,23 @@ main() using namespace test_std_ranges::archetypes; namespace dpl_ranges = oneapi::dpl::ranges; -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FILL +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FILL_HOST // None of the archetypes below is device copyable, so the host policies are the only ones the // constraints of these algorithms allow. - run_algo_all_policies( + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::fill(std::forward(policy), view, write_value{42}); + }, + [](auto&& view, auto) { + return std::ranges::begin(view)[0].val == 42 && + std::ranges::begin(view)[std::ranges::size(view) - 1].val == 42; + }, + "fill"); +#endif +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FILL_HETERO + // None of the archetypes below is device copyable, so the host policies are the only ones the + // constraints of these algorithms allow. + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::fill(std::forward(policy), view, write_value{42}); }, diff --git a/test/support/test_config.h b/test/support/test_config.h index eae090589b7..dc788ee62bf 100644 --- a/test/support/test_config.h +++ b/test/support/test_config.h @@ -364,18 +364,43 @@ // std::input_iterator and std::output_iterator on the same pre-P2325R3 implementations. #define _ONEDPL_CPP20_IN_OUT_ITERATOR_BROKEN TEST_STD_RANGES_VIEW_CONCEPT_REQUIRES_DEFAULT_INITIALIZABLE -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_CONTAINS 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_COUNT 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FILL 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_MAX_ELEMENT 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_IF 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_CONTAINS_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_CONTAINS_HETERO 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_COUNT_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_COUNT_HETERO 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FILL_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FILL_HETERO 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_HETERO 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_HETERO 1 + +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_HETERO 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_ELEMENT_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_ELEMENT_HETERO 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_HETERO 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_ELEMENT_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_ELEMENT_HETERO 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_HETERO 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_ELEMENT_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_ELEMENT_HETERO 1 + +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_HETERO 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_IF_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_IF_HETERO 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HETERO 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HETERO 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT_HETERO 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT_HETERO 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE_HETERO 1 #endif // _TEST_CONFIG_H From 98a134b1de6a834e72ba9b8fbe860bfacd1e3cc2 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Mon, 31 Aug 2026 11:27:14 +0200 Subject: [PATCH 056/148] Fix broken host/hetero cases --- test/support/test_config.h | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/test/support/test_config.h b/test/support/test_config.h index dc788ee62bf..a6c8d713a65 100644 --- a/test/support/test_config.h +++ b/test/support/test_config.h @@ -364,43 +364,43 @@ // std::input_iterator and std::output_iterator on the same pre-P2325R3 implementations. #define _ONEDPL_CPP20_IN_OUT_ITERATOR_BROKEN TEST_STD_RANGES_VIEW_CONCEPT_REQUIRES_DEFAULT_INITIALIZABLE -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_CONTAINS_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_CONTAINS_HOST 0 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_CONTAINS_HETERO 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_COUNT_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_COUNT_HOST 0 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_COUNT_HETERO 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FILL_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FILL_HOST 0 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FILL_HETERO 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_HOST 0 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_HETERO 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_HOST 0 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_HETERO 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_HOST 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_HETERO 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_HETERO 0 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_ELEMENT_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_ELEMENT_HETERO 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_HOST 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_HETERO 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_HETERO 0 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_ELEMENT_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_ELEMENT_HETERO 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_HOST 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_HETERO 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_HETERO 0 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_ELEMENT_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_ELEMENT_HETERO 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_HOST 0 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_HETERO 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_IF_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_IF_HOST 0 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_IF_HETERO 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HETERO 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HETERO 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT_HOST 0 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT_HETERO 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT_HOST 0 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT_HETERO 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE_HOST 0 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE_HETERO 1 #endif // _TEST_CONFIG_H From 264da4f3ea8d66cfc98f9177217d349c32c32a8f Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Mon, 31 Aug 2026 12:45:07 +0200 Subject: [PATCH 057/148] Remove switched off broken test macros --- .../std_ranges_algo_archetypes_merge.pass.cpp | 9 +++------ ...td_ranges_algo_archetypes_permute.pass.cpp | 12 ++++------- .../std_ranges_algo_archetypes_value.pass.cpp | 20 ++++++------------- .../std_ranges_algo_archetypes_write.pass.cpp | 3 +-- test/support/test_config.h | 13 ------------ 5 files changed, 14 insertions(+), 43 deletions(-) diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp index 03b30f472b9..0ce47cfed65 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp @@ -158,13 +158,12 @@ main() }, [](auto&&, auto res) { return res.val == 0; }, "min"); #endif -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_HETERO + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::min(std::forward(policy), view, storable_comp{}); }, [](auto&&, auto res) { return res.val == 0; }, "min"); -#endif #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_HOST run_algo_host_policies( @@ -173,13 +172,12 @@ main() }, [](auto&&, auto res) { return res.val == (int)archetype_test_size - 1; }, "max"); #endif -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_HETERO + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::max(std::forward(policy), view, storable_comp{}); }, [](auto&&, auto res) { return res.val == (int)archetype_test_size - 1; }, "max"); -#endif #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_HOST run_algo_host_policies( @@ -188,13 +186,12 @@ main() }, [](auto&&, auto&& res) { return res.min.val == 0 && res.max.val == (int)archetype_test_size - 1; }, "minmax"); #endif -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_HETERO + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::minmax(std::forward(policy), view, storable_comp{}); }, [](auto&&, auto&& res) { return res.min.val == 0 && res.max.val == (int)archetype_test_size - 1; }, "minmax"); -#endif #endif //_ENABLE_STD_RANGES_TESTING diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp index c51794a649a..4f28ed1e503 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp @@ -71,7 +71,6 @@ main() }, "reverse"); -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_IF_HOST // The storage is filled with 0, 1, 2, ... so every third element is removed. The returned // subrange is the tail holding the removed elements. run_algo_host_policies( @@ -83,7 +82,7 @@ main() return std::ranges::size(res) == (n + 2) / 3; }, "remove_if"); -#endif + #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_IF_HETERO // The storage is filled with 0, 1, 2, ... so every third element is removed. The returned // subrange is the tail holding the removed elements. @@ -98,14 +97,13 @@ main() "remove_if"); #endif -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE_HOST // All the elements are unique, so nothing is dropped. run_algo_host_policies( [](auto&& policy, auto&& view) { return dpl_ranges::unique(std::forward(policy), view, permutable_equiv{}); }, [](auto&& view, auto res) { return std::ranges::size(res) == 0; }, "unique"); -#endif + #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE_HETERO // All the elements are unique, so nothing is dropped. run_algo_hetero_policies( @@ -115,7 +113,6 @@ main() [](auto&& view, auto res) { return std::ranges::size(res) == 0; }, "unique"); #endif -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT_HOST // prpbably incorrect type applied run_algo_host_policies( [](auto&& policy, auto&& view) { @@ -126,7 +123,7 @@ main() std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; }, "sort"); -#endif + #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT_HETERO // prpbably incorrect type applied run_algo_hetero_policies( @@ -140,7 +137,6 @@ main() "sort"); #endif -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT_HOST // prpbably incorrect type applied run_algo_host_policies( [](auto&& policy, auto&& view) { @@ -151,7 +147,7 @@ main() std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; }, "stable_sort"); -#endif + #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT_HETERO // prpbably incorrect type applied run_algo_hetero_policies( diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp index 9f6b5a50d60..cc19ea45f86 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp @@ -71,14 +71,13 @@ main() }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find"); -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_HOST run_algo_host_policies( [](auto&& policy, auto&& view) { return dpl_ranges::find_last(std::forward(policy), view, search_value{searched}); }, [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, "find_last"); -#endif + #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_HETERO run_algo_hetero_policies( [](auto&& policy, auto&& view) { @@ -100,7 +99,6 @@ main() }, [](auto&&, auto res) { return res; }, "contains"); -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_HOST // removable_archetype is movable but not device copyable, so remove() is checked on the host // policies only. run_algo_host_policies( @@ -108,7 +106,7 @@ main() return dpl_ranges::remove(std::forward(policy), view, search_value{searched}); }, [](auto&& view, auto res) { return std::ranges::size(res) == std::ranges::size(view) - 1; }, "remove"); -#endif + #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_HETERO // removable_archetype is movable but not device copyable, so remove() is checked on the host // policies only. @@ -119,7 +117,6 @@ main() [](auto&& view, auto res) { return std::ranges::size(res) == std::ranges::size(view) - 1; }, "remove"); #endif -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_HOST // nocopy_search_value is neither copyable nor movable: the host implementations must refer to // the value passed by the user instead of storing a copy of it. It cannot be captured by a // device kernel, hence the host policies only. @@ -128,7 +125,7 @@ main() return dpl_ranges::find(std::forward(policy), view, nocopy_search_value{searched}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find, noncopyable value"); -#endif + #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_HETERO // nocopy_search_value is neither copyable nor movable: the host implementations must refer to // the value passed by the user instead of storing a copy of it. It cannot be captured by a @@ -140,14 +137,13 @@ main() [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find, noncopyable value"); #endif -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_HOST run_algo_host_policies( [](auto&& policy, auto&& view) { return dpl_ranges::find_last(std::forward(policy), view, nocopy_search_value{searched}); }, [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, "find_last, noncopyable value"); -#endif + #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_HETERO run_algo_hetero_policies( [](auto&& policy, auto&& view) { @@ -159,13 +155,11 @@ main() // count() must refer to the value instead of storing a copy of it: the requires-clause never // asks for a copyable value type. -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_COUNT_HOST run_algo_host_policies( [](auto&& policy, auto&& view) { return dpl_ranges::count(std::forward(policy), view, nocopy_search_value{searched}); }, [](auto&&, auto res) { return res == 1; }, "count, noncopyable value"); -#endif #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_COUNT_HETERO run_algo_hetero_policies( [](auto&& policy, auto&& view) { @@ -174,13 +168,12 @@ main() [](auto&&, auto res) { return res == 1; }, "count, noncopyable value"); #endif -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_CONTAINS_HOST run_algo_host_policies( [](auto&& policy, auto&& view) { return dpl_ranges::contains(std::forward(policy), view, nocopy_search_value{searched}); }, [](auto&&, auto res) { return res; }, "contains, noncopyable value"); -#endif + #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_CONTAINS_HETERO run_algo_hetero_policies( [](auto&& policy, auto&& view) { @@ -191,14 +184,13 @@ main() // Same for remove(): the predicate it builds internally must hold a reference to the value for // the host policies. -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_HOST run_algo_host_policies( [](auto&& policy, auto&& view) { return dpl_ranges::remove(std::forward(policy), view, nocopy_search_value{searched}); }, [](auto&& view, auto res) { return std::ranges::size(res) == std::ranges::size(view) - 1; }, "remove, noncopyable value"); -#endif + #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_HETERO run_algo_hetero_policies( [](auto&& policy, auto&& view) { diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp index f3d86754ffe..0e73099e8fa 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp @@ -71,7 +71,6 @@ main() using namespace test_std_ranges::archetypes; namespace dpl_ranges = oneapi::dpl::ranges; -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FILL_HOST // None of the archetypes below is device copyable, so the host policies are the only ones the // constraints of these algorithms allow. run_algo_host_policies( @@ -83,7 +82,7 @@ main() std::ranges::begin(view)[std::ranges::size(view) - 1].val == 42; }, "fill"); -#endif + #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FILL_HETERO // None of the archetypes below is device copyable, so the host policies are the only ones the // constraints of these algorithms allow. diff --git a/test/support/test_config.h b/test/support/test_config.h index a6c8d713a65..ce52e39374b 100644 --- a/test/support/test_config.h +++ b/test/support/test_config.h @@ -364,43 +364,30 @@ // std::input_iterator and std::output_iterator on the same pre-P2325R3 implementations. #define _ONEDPL_CPP20_IN_OUT_ITERATOR_BROKEN TEST_STD_RANGES_VIEW_CONCEPT_REQUIRES_DEFAULT_INITIALIZABLE -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_CONTAINS_HOST 0 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_CONTAINS_HETERO 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_COUNT_HOST 0 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_COUNT_HETERO 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FILL_HOST 0 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FILL_HETERO 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_HOST 0 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_HETERO 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_HOST 0 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_HETERO 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_HOST 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_HETERO 0 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_ELEMENT_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_ELEMENT_HETERO 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_HOST 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_HETERO 0 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_ELEMENT_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_ELEMENT_HETERO 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_HOST 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_HETERO 0 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_ELEMENT_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_ELEMENT_HETERO 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_HOST 0 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_HETERO 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_IF_HOST 0 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_IF_HETERO 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HETERO 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HETERO 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT_HOST 0 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT_HETERO 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT_HOST 0 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT_HETERO 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE_HOST 0 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE_HETERO 1 #endif // _TEST_CONFIG_H From 1689fd931307431a0be41efec5b4a93ec03ef98e Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 1 Sep 2026 11:05:36 +0200 Subject: [PATCH 058/148] Create *_dc archetypes for hetero polict and run_algo2_all_policies -> run_algo2_host_policies + run_algo2_hetero_policies and etc. --- .../std_ranges_algo_archetypes_merge.pass.cpp | 45 +- ...td_ranges_algo_archetypes_permute.pass.cpp | 30 +- .../std_ranges_algo_archetypes_read.pass.cpp | 133 ++++- .../ranges/std_ranges_algo_archetypes_test.h | 35 +- .../std_ranges_algo_archetypes_value.pass.cpp | 44 +- .../std_ranges_algo_archetypes_write.pass.cpp | 55 ++- .../ranges/std_ranges_archetypes.h | 458 +++++++++++++++++- 7 files changed, 708 insertions(+), 92 deletions(-) diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp index 0ce47cfed65..486880e1d15 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp @@ -70,10 +70,12 @@ main() // constraints allow. // Both inputs hold the very same sorted sequence 0, 1, 2, ... - run_algo2_all_policies( + run_algo2_host_policies( [](auto&& policy, auto&& view1, auto&& view2) { - archetype_storage> out_storage( - std::allocator{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); + using __out_elem = + typename std::ranges::range_value_t>::out_type; + archetype_storage<__out_elem, std::allocator<__out_elem>> out_storage( + std::allocator<__out_elem>{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); auto out_view = out_storage.view(); auto res = dpl_ranges::merge(std::forward(policy), view1, view2, out_view, merge_comp{}); return std::ranges::begin(out_view)[0].val == 0 && std::ranges::begin(out_view)[1].val == 0 && @@ -81,6 +83,21 @@ main() }, [](auto&&, auto&&, auto res) { return res; }, "merge"); +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + using __out_elem = + typename std::ranges::range_value_t>::out_type; + archetype_storage<__out_elem, std::allocator<__out_elem>> out_storage( + std::allocator<__out_elem>{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = dpl_ranges::merge(std::forward(policy), view1, view2, out_view, merge_comp{}); + return std::ranges::begin(out_view)[0].val == 0 && std::ranges::begin(out_view)[1].val == 0 && + std::ranges::begin(out_view)[2].val == 1 && res.out == std::ranges::end(out_view); + }, + [](auto&&, auto&&, auto res) { return res; }, "merge"); +#endif //TEST_DPCPP_BACKEND_PRESENT + // KSATODO: the set operations only require std::mergeable, i.e. indirectly_copyable from either // input into the output, which is an assignment and not a construction. The implementation // instead constructs the output element into raw memory, so the calls below do not compile: @@ -104,10 +121,12 @@ main() [](auto&&, auto&&, auto res) { return res; }, "set_union"); #endif #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HETERO - run_algo2_hetero_policies( + run_algo2_hetero_policies( [](auto&& policy, auto&& view1, auto&& view2) { - archetype_storage> out_storage( - std::allocator{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); + using __out_elem = + typename std::ranges::range_value_t>::out_type; + archetype_storage<__out_elem, std::allocator<__out_elem>> out_storage( + std::allocator<__out_elem>{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); auto out_view = out_storage.view(); auto res = dpl_ranges::set_union(std::forward(policy), view1, view2, out_view, merge_comp{}); @@ -133,10 +152,12 @@ main() [](auto&&, auto&&, auto res) { return res; }, "set_difference"); #endif #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HETERO - run_algo2_hetero_policies( + run_algo2_hetero_policies( [](auto&& policy, auto&& view1, auto&& view2) { - archetype_storage> out_storage( - std::allocator{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); + using __out_elem = + typename std::ranges::range_value_t>::out_type; + archetype_storage<__out_elem, std::allocator<__out_elem>> out_storage( + std::allocator<__out_elem>{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); auto out_view = out_storage.view(); auto res = dpl_ranges::set_difference(std::forward(policy), view1, view2, out_view, merge_comp{}); @@ -159,7 +180,7 @@ main() [](auto&&, auto res) { return res.val == 0; }, "min"); #endif - run_algo_hetero_policies( + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::min(std::forward(policy), view, storable_comp{}); }, @@ -173,7 +194,7 @@ main() [](auto&&, auto res) { return res.val == (int)archetype_test_size - 1; }, "max"); #endif - run_algo_hetero_policies( + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::max(std::forward(policy), view, storable_comp{}); }, @@ -187,7 +208,7 @@ main() [](auto&&, auto&& res) { return res.min.val == 0 && res.max.val == (int)archetype_test_size - 1; }, "minmax"); #endif - run_algo_hetero_policies( + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::minmax(std::forward(policy), view, storable_comp{}); }, diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp index 4f28ed1e503..4c0f0e841e6 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp @@ -63,13 +63,23 @@ main() // permutable_archetype is movable but not copyable, so it is not device copyable either: the // host policies are the only ones its constraints allow. - run_algo_all_policies( + run_algo_host_policies( + [](auto&& policy, auto&& view) { return dpl_ranges::reverse(std::forward(policy), view); }, + [](auto&& view, auto) { + const auto n = std::ranges::size(view); + return std::ranges::begin(view)[0].val == (int)n - 1 && std::ranges::begin(view)[n - 1].val == 0; + }, + "reverse"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::reverse(std::forward(policy), view); }, [](auto&& view, auto) { const auto n = std::ranges::size(view); return std::ranges::begin(view)[0].val == (int)n - 1 && std::ranges::begin(view)[n - 1].val == 0; }, "reverse"); +#endif //TEST_DPCPP_BACKEND_PRESENT // The storage is filled with 0, 1, 2, ... so every third element is removed. The returned // subrange is the tail holding the removed elements. @@ -86,7 +96,7 @@ main() #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_IF_HETERO // The storage is filled with 0, 1, 2, ... so every third element is removed. The returned // subrange is the tail holding the removed elements. - run_algo_hetero_policies( + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::remove_if(std::forward(policy), view, permutable_pred{}); }, @@ -106,7 +116,7 @@ main() #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE_HETERO // All the elements are unique, so nothing is dropped. - run_algo_hetero_policies( + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::unique(std::forward(policy), view, permutable_equiv{}); }, @@ -126,7 +136,7 @@ main() #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT_HETERO // prpbably incorrect type applied - run_algo_hetero_policies( + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::sort(std::forward(policy), view, permutable_comp{}); }, @@ -150,7 +160,7 @@ main() #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT_HETERO // prpbably incorrect type applied - run_algo_hetero_policies( + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::stable_sort(std::forward(policy), view, permutable_comp{}); }, @@ -161,11 +171,19 @@ main() "stable_sort"); #endif - run_algo_all_policies( + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_sorted(std::forward(policy), view, permutable_comp{}); + }, + [](auto&&, auto res) { return res; }, "is_sorted"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::is_sorted(std::forward(policy), view, permutable_comp{}); }, [](auto&&, auto res) { return res; }, "is_sorted"); +#endif //TEST_DPCPP_BACKEND_PRESENT #endif //_ENABLE_STD_RANGES_TESTING return TestUtils::done(_ENABLE_STD_RANGES_TESTING); diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp index 49c7d95a3c9..f58bff7baeb 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp @@ -96,45 +96,95 @@ main() // read_archetype is neither copyable, movable, default constructible nor comparable; the only // operations available are the ones the callables of the algorithm provide. - run_algo_all_policies( + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_if(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "find_if"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::find_if(std::forward(policy), view, read_unary_pred{}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "find_if"); +#endif //TEST_DPCPP_BACKEND_PRESENT - run_algo_all_policies( + run_algo_host_policies( [](auto&& policy, auto&& view) { return dpl_ranges::find_if_not(std::forward(policy), view, read_unary_pred{}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + 1; }, "find_if_not"); - run_algo_all_policies( +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_if_not(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + 1; }, "find_if_not"); +#endif //TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( [](auto&& policy, auto&& view) { return dpl_ranges::any_of(std::forward(policy), view, read_unary_pred{}); }, [](auto&&, bool res) { return res; }, "any_of"); - run_algo_all_policies( +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::any_of(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&&, bool res) { return res; }, "any_of"); +#endif //TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::all_of(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&&, bool res) { return !res; }, "all_of"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::all_of(std::forward(policy), view, read_unary_pred{}); }, [](auto&&, bool res) { return !res; }, "all_of"); +#endif //TEST_DPCPP_BACKEND_PRESENT - run_algo_all_policies( + run_algo_host_policies( [](auto&& policy, auto&& view) { return dpl_ranges::count_if(std::forward(policy), view, read_unary_pred{}); }, [](auto&& view, auto res) { return res == (std::ranges::range_difference_t) ((std::ranges::size(view) + 2) / 3); }, "count_if"); +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count_if(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&& view, auto res) { return res == (std::ranges::range_difference_t) + ((std::ranges::size(view) + 2) / 3); }, "count_if"); +#endif //TEST_DPCPP_BACKEND_PRESENT + // The projection returns an unrelated prvalue type, so the predicate can only ever be applied to // the projected value. - run_algo_all_policies( + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count_if(std::forward(policy), view, read_proj_pred{}, read_proj{}); + }, + [](auto&& view, auto res) { return res == (std::ranges::range_difference_t) + ((std::ranges::size(view) + 2) / 3); }, "count_if with proj"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::count_if(std::forward(policy), view, read_proj_pred{}, read_proj{}); }, [](auto&& view, auto res) { return res == (std::ranges::range_difference_t) ((std::ranges::size(view) + 2) / 3); }, "count_if with proj"); +#endif //TEST_DPCPP_BACKEND_PRESENT // KSATODO: min_element/max_element/minmax_element only require std::indirect_strict_weak_order // on the projected iterator, so the element type itself has to stay non-copyable and @@ -154,7 +204,7 @@ main() [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "min_element"); #endif #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_ELEMENT_HETERO - run_algo_hetero_policies( + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::min_element(std::forward(policy), view, read_comp{}); }, @@ -170,7 +220,7 @@ main() "max_element"); #endif #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_ELEMENT_HETERO - run_algo_hetero_policies( + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::max_element(std::forward(policy), view, read_comp{}); }, @@ -190,7 +240,7 @@ main() "minmax_element"); #endif #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_ELEMENT_HETERO - run_algo_hetero_policies( + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::minmax_element(std::forward(policy), view, read_comp{}); }, @@ -201,40 +251,92 @@ main() "minmax_element"); #endif - run_algo_all_policies( + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_sorted(std::forward(policy), view, read_comp{}); + }, + [](auto&&, bool res) { return res; }, "is_sorted"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::is_sorted(std::forward(policy), view, read_comp{}); }, [](auto&&, bool res) { return res; }, "is_sorted"); +#endif //TEST_DPCPP_BACKEND_PRESENT - run_algo_all_policies( + run_algo_host_policies( [](auto&& policy, auto&& view) { return dpl_ranges::adjacent_find(std::forward(policy), view, read_binary_pred{}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, "adjacent_find"); +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::adjacent_find(std::forward(policy), view, read_binary_pred{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, + "adjacent_find"); +#endif //TEST_DPCPP_BACKEND_PRESENT + // The search value type is unrelated to the element type. - run_algo_all_policies( + run_algo_host_policies( [](auto&& policy, auto&& view) { return dpl_ranges::find(std::forward(policy), view, search_value{7}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + 7; }, "find"); - run_algo_all_policies( +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find(std::forward(policy), view, search_value{7}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + 7; }, "find"); +#endif //TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( [](auto&& policy, auto&& view) { return dpl_ranges::count(std::forward(policy), view, search_value{7}); }, [](auto&&, auto res) { return res == 1; }, "count"); +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count(std::forward(policy), view, search_value{7}); + }, + [](auto&&, auto res) { return res == 1; }, "count"); +#endif //TEST_DPCPP_BACKEND_PRESENT + // Two ranges of unrelated element types, compared only through the user predicate. - run_algo2_all_policies( + run_algo2_host_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::equal(std::forward(policy), view1, view2, cross_pred{}); + }, + [](auto&&, auto&&, bool res) { return res; }, "equal"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies( [](auto&& policy, auto&& view1, auto&& view2) { return dpl_ranges::equal(std::forward(policy), view1, view2, cross_pred{}); }, [](auto&&, auto&&, bool res) { return res; }, "equal"); +#endif //TEST_DPCPP_BACKEND_PRESENT + + run_algo2_host_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::mismatch(std::forward(policy), view1, view2, cross_pred{}); + }, + [](auto&& view1, auto&& view2, auto res) { + return res.in1 == std::ranges::begin(view1) + std::ranges::size(view1) && + res.in2 == std::ranges::begin(view2) + std::ranges::size(view2); + }, + "mismatch"); - run_algo2_all_policies( +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies( [](auto&& policy, auto&& view1, auto&& view2) { return dpl_ranges::mismatch(std::forward(policy), view1, view2, cross_pred{}); }, @@ -243,6 +345,7 @@ main() res.in2 == std::ranges::begin(view2) + std::ranges::size(view2); }, "mismatch"); +#endif //TEST_DPCPP_BACKEND_PRESENT #endif //_ENABLE_STD_RANGES_TESTING diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h b/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h index 2c4ae2a32ce..aa54efa7973 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h @@ -100,6 +100,13 @@ run_algo2_host_policies(_Algo __algo, _Checker __checker, const char* __algo_nam } #if TEST_DPCPP_BACKEND_PRESENT +// A device policy passes the element type into a kernel, so the caller is expected to name the +// device copyable archetype (the _dc one) explicitly. Everything else the host only archetype lacks +// (default construction, comparison, ordering, ...) is still missing in the _dc counterpart. +// +// _CallId makes the SYCL kernel name of the device call unique: every instantiation of the harness +// submits its own kernel, and with -fno-sycl-unnamed-lambda two kernels sharing a name are a +// "definition with same mangled name" error. template void run_algo_hetero_policies(_Algo __algo, _Checker __checker, const char* __algo_name) @@ -108,23 +115,8 @@ run_algo_hetero_policies(_Algo __algo, _Checker __checker, const char* __algo_na sycl::usm_allocator<_Elem, sycl::usm::alloc::shared> __q_alloc{__policy.queue()}; run_algo<_Elem>(__q_alloc, __policy, __algo, __checker, __algo_name); } -#endif - -// _CallId makes the SYCL kernel name of the device call unique: every instantiation of the harness -// submits its own kernel, and with -fno-sycl-unnamed-lambda two kernels sharing a name are a -// "definition with same mangled name" error. -template -void -run_algo_all_policies(_Algo __algo, _Checker __checker, const char* __algo_name) -{ - run_algo_host_policies<_Elem>(__algo, __checker, __algo_name); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies<_Elem, _CallId, _Algo, _Checker>(__algo, __checker, __algo_name); -#endif //TEST_DPCPP_BACKEND_PRESENT -} - -#if TEST_DPCPP_BACKEND_PRESENT +// Runs a two-range algorithm with the hetero policies, see run_algo_hetero_policies. template void run_algo2_hetero_policies(_Algo __algo, _Checker __checker, const char* __algo_name) @@ -136,17 +128,6 @@ run_algo2_hetero_policies(_Algo __algo, _Checker __checker, const char* __algo_n } #endif -template -void -run_algo2_all_policies(_Algo __algo, _Checker __checker, const char* __algo_name) -{ - run_algo2_host_policies<_Elem1, _Elem2>(__algo, __checker, __algo_name); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies<_Elem1, _Elem2, _CallId, _Algo, _Checker>(__algo, __checker, __algo_name); -#endif //TEST_DPCPP_BACKEND_PRESENT -} - } //namespace test_std_ranges #endif //_ENABLE_STD_RANGES_TESTING diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp index cc19ea45f86..25eeaec3551 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp @@ -65,12 +65,20 @@ main() // search_value is trivially copyable and thus device copyable, so it can be used with all the // policies including the device ones. - run_algo_all_policies( + run_algo_host_policies( [](auto&& policy, auto&& view) { return dpl_ranges::find(std::forward(policy), view, search_value{searched}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find"); +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find(std::forward(policy), view, search_value{searched}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find"); +#endif //TEST_DPCPP_BACKEND_PRESENT + run_algo_host_policies( [](auto&& policy, auto&& view) { return dpl_ranges::find_last(std::forward(policy), view, search_value{searched}); @@ -79,7 +87,7 @@ main() "find_last"); #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_HETERO - run_algo_hetero_policies( + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::find_last(std::forward(policy), view, search_value{searched}); }, @@ -87,17 +95,33 @@ main() "find_last"); #endif - run_algo_all_policies( + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count(std::forward(policy), view, search_value{searched}); + }, + [](auto&&, auto res) { return res == 1; }, "count"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::count(std::forward(policy), view, search_value{searched}); }, [](auto&&, auto res) { return res == 1; }, "count"); +#endif //TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::contains(std::forward(policy), view, search_value{searched}); + }, + [](auto&&, auto res) { return res; }, "contains"); - run_algo_all_policies( +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::contains(std::forward(policy), view, search_value{searched}); }, [](auto&&, auto res) { return res; }, "contains"); +#endif //TEST_DPCPP_BACKEND_PRESENT // removable_archetype is movable but not device copyable, so remove() is checked on the host // policies only. @@ -110,7 +134,7 @@ main() #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_HETERO // removable_archetype is movable but not device copyable, so remove() is checked on the host // policies only. - run_algo_hetero_policies( + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::remove(std::forward(policy), view, search_value{searched}); }, @@ -130,7 +154,7 @@ main() // nocopy_search_value is neither copyable nor movable: the host implementations must refer to // the value passed by the user instead of storing a copy of it. It cannot be captured by a // device kernel, hence the host policies only. - run_algo_hetero_policies( + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::find(std::forward(policy), view, nocopy_search_value{searched}); }, @@ -145,7 +169,7 @@ main() "find_last, noncopyable value"); #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_HETERO - run_algo_hetero_policies( + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::find_last(std::forward(policy), view, nocopy_search_value{searched}); }, @@ -161,7 +185,7 @@ main() }, [](auto&&, auto res) { return res == 1; }, "count, noncopyable value"); #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_COUNT_HETERO - run_algo_hetero_policies( + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::count(std::forward(policy), view, nocopy_search_value{searched}); }, @@ -175,7 +199,7 @@ main() [](auto&&, auto res) { return res; }, "contains, noncopyable value"); #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_CONTAINS_HETERO - run_algo_hetero_policies( + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::contains(std::forward(policy), view, nocopy_search_value{searched}); }, @@ -192,7 +216,7 @@ main() "remove, noncopyable value"); #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_HETERO - run_algo_hetero_policies( + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::remove(std::forward(policy), view, nocopy_search_value{searched}); }, diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp index 0e73099e8fa..3fffb538a1c 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp @@ -75,7 +75,8 @@ main() // constraints of these algorithms allow. run_algo_host_policies( [](auto&& policy, auto&& view) { - return dpl_ranges::fill(std::forward(policy), view, write_value{42}); + using __elem = std::ranges::range_value_t>; + return dpl_ranges::fill(std::forward(policy), view, typename __elem::value_arg{42}); }, [](auto&& view, auto) { return std::ranges::begin(view)[0].val == 42 && @@ -86,9 +87,10 @@ main() #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FILL_HETERO // None of the archetypes below is device copyable, so the host policies are the only ones the // constraints of these algorithms allow. - run_algo_hetero_policies( + run_algo_hetero_policies( [](auto&& policy, auto&& view) { - return dpl_ranges::fill(std::forward(policy), view, write_value{42}); + using __elem = std::ranges::range_value_t>; + return dpl_ranges::fill(std::forward(policy), view, typename __elem::value_arg{42}); }, [](auto&& view, auto) { return std::ranges::begin(view)[0].val == 42 && @@ -97,7 +99,7 @@ main() "fill"); #endif - run_algo2_all_policies( + run_algo2_host_policies( [](auto&& policy, auto&& in_view, auto&& out_view) { return dpl_ranges::copy(std::forward(policy), in_view, out_view); }, @@ -106,13 +108,32 @@ main() }, "copy"); - run_algo2_all_policies( +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies( + [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::copy(std::forward(policy), in_view, out_view); + }, + [](auto&& in_view, auto&& out_view, auto) { + return std::ranges::begin(out_view)[7].val == std::ranges::begin(in_view)[7].val; + }, + "copy"); +#endif //TEST_DPCPP_BACKEND_PRESENT + + run_algo2_host_policies( [](auto&& policy, auto&& in_view, auto&& out_view) { return dpl_ranges::move(std::forward(policy), in_view, out_view); }, [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 7; }, "move"); - run_algo2_all_policies( +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies( + [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::move(std::forward(policy), in_view, out_view); + }, + [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 7; }, "move"); +#endif //TEST_DPCPP_BACKEND_PRESENT + + run_algo2_host_policies( [](auto&& policy, auto&& view1, auto&& view2) { return dpl_ranges::swap_ranges(std::forward(policy), view1, view2); }, @@ -121,12 +142,32 @@ main() }, "swap_ranges"); - run_algo2_all_policies( +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::swap_ranges(std::forward(policy), view1, view2); + }, + [](auto&& view1, auto&& view2, auto) { + return std::ranges::begin(view1)[7].val == 7 && std::ranges::begin(view2)[7].val == 7; + }, + "swap_ranges"); +#endif //TEST_DPCPP_BACKEND_PRESENT + + run_algo2_host_policies( + [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::transform(std::forward(policy), in_view, out_view, + transform_unary_op{}); + }, + [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 14; }, "transform"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies( [](auto&& policy, auto&& in_view, auto&& out_view) { return dpl_ranges::transform(std::forward(policy), in_view, out_view, transform_unary_op{}); }, [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 14; }, "transform"); +#endif //TEST_DPCPP_BACKEND_PRESENT #endif //_ENABLE_STD_RANGES_TESTING return TestUtils::done(_ENABLE_STD_RANGES_TESTING); diff --git a/test/parallel_api/ranges/std_ranges_archetypes.h b/test/parallel_api/ranges/std_ranges_archetypes.h index 8ebc92eb46e..7beb833eb49 100644 --- a/test/parallel_api/ranges/std_ranges_archetypes.h +++ b/test/parallel_api/ranges/std_ranges_archetypes.h @@ -26,6 +26,10 @@ #include #include +#if TEST_DPCPP_BACKEND_PRESENT +# include +#endif + // The types below are "archetypes": each of them satisfies exactly the constraints written in the // requires-clause of the corresponding oneapi::dpl::ranges algorithm and nothing more. Every // operation which is not implied by those constraints is explicitly deleted. If an algorithm @@ -57,6 +61,26 @@ _Name& operator=(_Name&&) = delete; \ TEST_ARCHETYPE_DELETED_ADDRESSOF +// The device copyable counterpart of TEST_ARCHETYPE_DELETED_OPERATIONS: the copy and the move +// operations are trivial, which makes the type trivially copyable and thus device copyable by +// default, while everything else stays exactly as restricted as in the host only archetype. +#define TEST_ARCHETYPE_DEFAULTED_OPERATIONS(_Name) \ + _Name(const _Name&) = default; \ + _Name(_Name&&) = default; \ + _Name& operator=(const _Name&) = default; \ + _Name& operator=(_Name&&) = default; \ + TEST_ARCHETYPE_DELETED_ADDRESSOF + +// Checks that a device copyable archetype really is accepted by SYCL without an explicit +// sycl::is_device_copyable specialization. +#if TEST_DPCPP_BACKEND_PRESENT +# define TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(_Name) \ + static_assert(std::is_trivially_copyable_v<_Name>); \ + static_assert(sycl::is_device_copyable_v<_Name>); +#else +# define TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(_Name) static_assert(std::is_trivially_copyable_v<_Name>); +#endif + namespace test_std_ranges { namespace archetypes @@ -375,26 +399,50 @@ static_assert(!std::move_constructible); static_assert(!std::equality_comparable); static_assert(!std::totally_ordered); +// The device copyable counterpart of read_archetype, used with the hetero policies: it is trivially +// copyable, so a device kernel may take it by value, but it is still not default constructible and +// not comparable. +struct read_archetype_dc +{ + int val; + + explicit read_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(read_archetype_dc) +}; + +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(read_archetype_dc) +static_assert(!std::default_initializable); +static_assert(!std::equality_comparable); +static_assert(!std::totally_ordered); + // The callables take exactly const _T& and return exactly the required type, so an implementation // cannot pass an rvalue, a copy, or expect a wider return type. struct read_unary_fun { void operator()(const read_archetype&) const {} + void operator()(const read_archetype_dc&) const {} }; struct read_unary_pred { bool operator()(const read_archetype& __v) const { return __v.val % 3 == 0; } + bool operator()(const read_archetype_dc& __v) const { return __v.val % 3 == 0; } }; struct read_binary_pred { bool operator()(const read_archetype& __v1, const read_archetype& __v2) const { return __v1.val == __v2.val; } + bool operator()(const read_archetype_dc& __v1, const read_archetype_dc& __v2) const + { + return __v1.val == __v2.val; + } }; struct read_comp { bool operator()(const read_archetype& __v1, const read_archetype& __v2) const { return __v1.val < __v2.val; } + bool operator()(const read_archetype_dc& __v1, const read_archetype_dc& __v2) const { return __v1.val < __v2.val; } }; // A projection which returns a prvalue of an unrelated type, so nothing links the projected type @@ -407,6 +455,7 @@ struct read_proj_result struct read_proj { read_proj_result operator()(const read_archetype& __v) const { return read_proj_result{__v.val}; } + read_proj_result operator()(const read_archetype_dc& __v) const { return read_proj_result{__v.val}; } }; struct read_proj_pred @@ -526,6 +575,50 @@ struct removable_archetype friend bool operator==(const removable_archetype& __e, const search_value& __v) { return __e.val == __v.val; } }; +// The device copyable counterparts of the two archetypes above, used with the hetero policies. +// They are trivially copyable and thus device copyable by default; nothing else is added. +struct searchable_archetype_dc +{ + int val; + + explicit searchable_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(searchable_archetype_dc) + + friend bool operator==(const searchable_archetype_dc& __e1, const searchable_archetype_dc& __e2) + { + return __e1.val == __e2.val; + } + + friend bool operator==(const searchable_archetype_dc& __e, const search_value& __v) { return __e.val == __v.val; } + + friend bool operator==(const searchable_archetype_dc& __e, const nocopy_search_value& __v) + { + return __e.val == __v.val; + } +}; + +struct removable_archetype_dc +{ + int val; + + explicit removable_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(removable_archetype_dc) + + friend bool operator==(const removable_archetype_dc& __e1, const removable_archetype_dc& __e2) + { + return __e1.val == __e2.val; + } + + friend bool operator==(const removable_archetype_dc& __e, const search_value& __v) { return __e.val == __v.val; } + + friend bool operator==(const removable_archetype_dc& __e, const nocopy_search_value& __v) + { + return __e.val == __v.val; + } +}; + // The common reference required by std::equality_comparable_with. It is only ever formed as a // reference by the concept machinery, so a minimal type which both archetypes convert to is enough. struct search_common @@ -534,6 +627,8 @@ struct search_common search_common(const searchable_archetype& __e) : val(__e.val) {} search_common(const removable_archetype& __e) : val(__e.val) {} + search_common(const searchable_archetype_dc& __e) : val(__e.val) {} + search_common(const removable_archetype_dc& __e) : val(__e.val) {} search_common(const search_value& __v) : val(__v.val) {} search_common(const nocopy_search_value& __v) : val(__v.val) {} @@ -592,6 +687,58 @@ struct common_type +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; } // namespace std namespace test_std_ranges @@ -624,6 +771,21 @@ static_assert(!std::totally_ordered); static_assert(!std::default_initializable); static_assert(!std::totally_ordered); +using searchable_dc_iterator_t = std::ranges::iterator_t>; +using removable_dc_iterator_t = std::ranges::iterator_t>; + +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(searchable_archetype_dc) +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(removable_archetype_dc) +static_assert(std::indirect_binary_predicate); +static_assert( + std::indirect_binary_predicate); +static_assert(std::permutable); +static_assert(std::indirect_binary_predicate); +static_assert(!std::default_initializable); +static_assert(!std::default_initializable); +static_assert(!std::totally_ordered); +static_assert(!std::totally_ordered); + // Family 3: two-range algorithms constrained by std::indirectly_comparable. // std::indirectly_comparable only asks for the predicate to be // invocable on the two projected references, so the two element types stay unrelated and neither of @@ -648,15 +810,42 @@ struct rhs_archetype TEST_ARCHETYPE_DELETED_OPERATIONS(rhs_archetype) }; +// The device copyable counterparts of the two archetypes above, used with the hetero policies. +struct lhs_archetype_dc +{ + int val; + + explicit lhs_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(lhs_archetype_dc) +}; + +struct rhs_archetype_dc +{ + int val; + + explicit rhs_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(rhs_archetype_dc) +}; + +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(lhs_archetype_dc) +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(rhs_archetype_dc) +static_assert(!std::equality_comparable); +static_assert(!std::equality_comparable); + struct cross_pred { bool operator()(const lhs_archetype& __v1, const rhs_archetype& __v2) const { return __v1.val == __v2.val; } + bool operator()(const lhs_archetype_dc& __v1, const rhs_archetype_dc& __v2) const { return __v1.val == __v2.val; } }; using lhs_iterator_t = std::ranges::iterator_t>; using rhs_iterator_t = std::ranges::iterator_t>; static_assert(std::indirectly_comparable); +static_assert(std::indirectly_comparable>, + std::ranges::iterator_t>, cross_pred>); static_assert(!std::equality_comparable); static_assert(!std::equality_comparable); static_assert(!std::copy_constructible); @@ -676,10 +865,27 @@ struct write_value TEST_ARCHETYPE_DELETED_OPERATIONS(write_value) }; +// The device copyable counterpart of write_value: a value argument is passed to a device kernel by +// copy, so the hetero policies need a trivially copyable one. +struct write_value_dc +{ + int val; + + explicit write_value_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(write_value_dc) +}; + +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(write_value_dc) + struct writable_archetype { int val; + // The value type the algorithm has to be called with, so that a generic test body may pick the + // right one for the element type it works on. + using value_arg = write_value; + explicit writable_archetype(int __v) : val(__v) {} writable_archetype(const writable_archetype&) = delete; @@ -695,9 +901,31 @@ struct writable_archetype } }; +struct writable_archetype_dc +{ + int val; + + using value_arg = write_value_dc; + + explicit writable_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(writable_archetype_dc) + + writable_archetype_dc& operator=(const write_value_dc& __v) + { + val = __v.val; + return *this; + } +}; + +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(writable_archetype_dc) + using writable_iterator_t = std::ranges::iterator_t>; static_assert(std::indirectly_writable); +static_assert(std::indirectly_writable>, + const write_value_dc&>); +static_assert(!std::default_initializable); static_assert(!std::copyable); static_assert(!std::movable); static_assert(!std::default_initializable); @@ -737,6 +965,37 @@ struct copy_out_archetype } }; +// The device copyable counterparts of the two archetypes above, used with the hetero policies. +struct copy_in_archetype_dc +{ + int val; + + explicit copy_in_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(copy_in_archetype_dc) +}; + +struct copy_out_archetype_dc +{ + int val; + + explicit copy_out_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(copy_out_archetype_dc) + + copy_out_archetype_dc& operator=(copy_in_archetype_dc& __v) + { + val = __v.val; + return *this; + } +}; + +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(copy_in_archetype_dc) +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(copy_out_archetype_dc) +static_assert(std::indirectly_copyable>, + std::ranges::iterator_t>>); +static_assert(!std::default_initializable); + using copy_in_iterator_t = std::ranges::iterator_t>; using copy_out_iterator_t = std::ranges::iterator_t>; @@ -777,6 +1036,40 @@ struct move_out_archetype } }; +// The device copyable counterparts of the two archetypes above, used with the hetero policies. The +// assignment from a non-const lvalue of the input type is still missing, so an implementation which +// copies instead of moving does not compile either. +struct move_in_archetype_dc +{ + int val; + + explicit move_in_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(move_in_archetype_dc) +}; + +struct move_out_archetype_dc +{ + int val; + + explicit move_out_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(move_out_archetype_dc) + + move_out_archetype_dc& operator=(move_in_archetype_dc&& __v) + { + val = __v.val; + return *this; + } +}; + +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(move_in_archetype_dc) +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(move_out_archetype_dc) +static_assert(std::indirectly_movable>, + std::ranges::iterator_t>>); +static_assert(!std::indirectly_copyable>, + std::ranges::iterator_t>>); + using move_in_iterator_t = std::ranges::iterator_t>; using move_out_iterator_t = std::ranges::iterator_t>; @@ -805,8 +1098,28 @@ struct swap_archetype } }; -using swap_iterator_t = std::ranges::iterator_t>; +// The device copyable counterpart of the archetype above, used with the hetero policies. The +// dedicated swap is kept, so the algorithm still has to go through std::ranges::swap. +struct swap_archetype_dc +{ + int val; + + explicit swap_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(swap_archetype_dc) + + friend void swap(swap_archetype_dc& __v1, swap_archetype_dc& __v2) + { + const int __tmp = __v1.val; + __v1.val = __v2.val; + __v2.val = __tmp; + } +}; + +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(swap_archetype_dc) +static_assert(!std::default_initializable); +using swap_iterator_t = std::ranges::iterator_t>; static_assert(std::indirectly_swappable); static_assert(!std::movable); static_assert(!std::move_constructible); @@ -853,9 +1166,39 @@ struct transform_out_archetype } }; +// The device copyable counterparts of the two archetypes above, used with the hetero policies. +struct transform_in_archetype_dc +{ + int val; + + explicit transform_in_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(transform_in_archetype_dc) +}; + +struct transform_out_archetype_dc +{ + int val; + + explicit transform_out_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(transform_out_archetype_dc) + + transform_out_archetype_dc& operator=(const transform_result& __v) + { + val = __v.val; + return *this; + } +}; + +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(transform_in_archetype_dc) +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(transform_out_archetype_dc) +static_assert(!std::default_initializable); + struct transform_unary_op { transform_result operator()(const transform_in_archetype& __v) const { return transform_result{__v.val * 2}; } + transform_result operator()(const transform_in_archetype_dc& __v) const { return transform_result{__v.val * 2}; } }; struct transform_binary_op @@ -864,6 +1207,10 @@ struct transform_binary_op { return transform_result{__v1.val + __v2.val}; } + transform_result operator()(const transform_in_archetype_dc& __v1, const transform_in_archetype_dc& __v2) const + { + return transform_result{__v1.val + __v2.val}; + } }; using transform_in_iterator_t = std::ranges::iterator_t>; @@ -904,7 +1251,23 @@ struct permutable_archetype TEST_ARCHETYPE_DELETED_ADDRESSOF }; +// The device copyable counterpart of the archetype above, used with the hetero policies. +struct permutable_archetype_dc +{ + int val; + + explicit permutable_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(permutable_archetype_dc) +}; + +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(permutable_archetype_dc) +static_assert(!std::default_initializable); +static_assert(!std::equality_comparable); +static_assert(!std::totally_ordered); + using permutable_iterator_t = std::ranges::iterator_t>; +using permutable_dc_iterator_t = std::ranges::iterator_t>; static_assert(std::permutable); static_assert(!std::copy_constructible); @@ -916,6 +1279,7 @@ static_assert(!std::totally_ordered); struct permutable_pred { bool operator()(const permutable_archetype& __v) const { return __v.val % 3 == 0; } + bool operator()(const permutable_archetype_dc& __v) const { return __v.val % 3 == 0; } }; struct permutable_equiv @@ -924,6 +1288,10 @@ struct permutable_equiv { return __v1.val == __v2.val; } + bool operator()(const permutable_archetype_dc& __v1, const permutable_archetype_dc& __v2) const + { + return __v1.val == __v2.val; + } }; // std::sortable == permutable && indirect_strict_weak_order<_Comp, @@ -936,18 +1304,30 @@ struct permutable_comp { return __v1.val < __v2.val; } + bool operator()(const permutable_archetype_dc& __v1, const permutable_archetype_dc& __v2) const + { + return __v1.val < __v2.val; + } }; static_assert(std::sortable); +static_assert(std::permutable); +static_assert(std::sortable); // The merge family additionally needs std::indirectly_copyable from both inputs into the output. // The output element is therefore assignable from a non-const lvalue of either input element type, // while remaining non-copyable itself. // Used by: merge, set_union, set_intersection, set_difference, set_symmetric_difference. +struct merge_out_archetype; + struct merge_in_archetype { int val; + // The output element type the algorithm has to be called with, so that a generic test body may + // pick the right one for the input element type it works on. + using out_type = merge_out_archetype; + explicit merge_in_archetype(int __v) : val(__v) {} merge_in_archetype(merge_in_archetype&& __other) : val(__other.val) {} @@ -988,14 +1368,54 @@ struct merge_out_archetype } }; +// The device copyable counterparts of the two archetypes above, used with the hetero policies. +struct merge_out_archetype_dc; + +struct merge_in_archetype_dc +{ + int val; + + using out_type = merge_out_archetype_dc; + + explicit merge_in_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(merge_in_archetype_dc) +}; + +struct merge_out_archetype_dc +{ + int val; + + explicit merge_out_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(merge_out_archetype_dc) + + merge_out_archetype_dc& operator=(merge_in_archetype_dc& __v) + { + val = __v.val; + return *this; + } +}; + struct merge_comp { bool operator()(const merge_in_archetype& __v1, const merge_in_archetype& __v2) const { return __v1.val < __v2.val; } + bool operator()(const merge_in_archetype_dc& __v1, const merge_in_archetype_dc& __v2) const + { + return __v1.val < __v2.val; + } }; +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(merge_in_archetype_dc) +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(merge_out_archetype_dc) +static_assert(std::mergeable>, + std::ranges::iterator_t>, + std::ranges::iterator_t>, merge_comp>); +static_assert(!std::default_initializable); + using merge_in_iterator_t = std::ranges::iterator_t>; using merge_out_iterator_t = std::ranges::iterator_t>; @@ -1024,12 +1444,31 @@ struct storable_archetype TEST_ARCHETYPE_DELETED_ADDRESSOF }; +// The device copyable counterpart of the archetype above, used with the hetero policies. +struct storable_archetype_dc +{ + int val; + + explicit storable_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(storable_archetype_dc) +}; + +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(storable_archetype_dc) +static_assert(!std::default_initializable); +static_assert(!std::equality_comparable); +static_assert(!std::totally_ordered); + struct storable_comp { bool operator()(const storable_archetype& __v1, const storable_archetype& __v2) const { return __v1.val < __v2.val; } + bool operator()(const storable_archetype_dc& __v1, const storable_archetype_dc& __v2) const + { + return __v1.val < __v2.val; + } }; using storable_iterator_t = std::ranges::iterator_t>; @@ -1040,22 +1479,11 @@ static_assert(!std::default_initializable); static_assert(!std::equality_comparable); static_assert(!std::totally_ordered); +static_assert(std::indirectly_copyable_storable>, + storable_archetype_dc*>); + } // namespace archetypes } // namespace test_std_ranges -#if TEST_DPCPP_BACKEND_PRESENT -namespace sycl -{ - template <> - struct is_device_copyable : std::true_type { }; - - template <> - struct is_device_copyable : std::true_type { }; - - template <> - struct is_device_copyable : std::true_type { }; -} -#endif - #endif // _ENABLE_STD_RANGES_TESTING #endif // _STD_RANGES_ARCHETYPES_H From 1a89cf050069d78a8f060786409701c48eb55431 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 1 Sep 2026 11:12:31 +0200 Subject: [PATCH 059/148] Create *_dc archetypes for hetero polict and run_algo2_all_policies -> run_algo2_host_policies + run_algo2_hetero_policies and etc. --- .../std_ranges_algo_archetypes_value.pass.cpp | 32 ++++++--- .../ranges/std_ranges_archetypes.h | 66 +++++++++++++++++++ 2 files changed, 90 insertions(+), 8 deletions(-) diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp index 25eeaec3551..25daf53e6db 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp @@ -49,6 +49,22 @@ static_assert(std::invocable); +// The device copyable counterpart of the value satisfies the very same constraints, and it really is +// accepted by SYCL without an explicit sycl::is_device_copyable specialization. +using searchable_dc_view = archetypes::archetype_view; +using removable_dc_view = archetypes::archetype_view; + +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); + } //namespace test_std_ranges #endif //_ENABLE_STD_RANGES_TESTING @@ -151,12 +167,11 @@ main() [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find, noncopyable value"); #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_HETERO - // nocopy_search_value is neither copyable nor movable: the host implementations must refer to - // the value passed by the user instead of storing a copy of it. It cannot be captured by a - // device kernel, hence the host policies only. + // A device policy copies the value into the kernel, so the hetero runs use the device copyable + // counterpart of the value: it is still neither default constructible nor ordered. run_algo_hetero_policies( [](auto&& policy, auto&& view) { - return dpl_ranges::find(std::forward(policy), view, nocopy_search_value{searched}); + return dpl_ranges::find(std::forward(policy), view, nocopy_search_value_dc{searched}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find, noncopyable value"); #endif @@ -171,7 +186,8 @@ main() #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_HETERO run_algo_hetero_policies( [](auto&& policy, auto&& view) { - return dpl_ranges::find_last(std::forward(policy), view, nocopy_search_value{searched}); + return dpl_ranges::find_last(std::forward(policy), view, + nocopy_search_value_dc{searched}); }, [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, "find_last, noncopyable value"); @@ -187,7 +203,7 @@ main() #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_COUNT_HETERO run_algo_hetero_policies( [](auto&& policy, auto&& view) { - return dpl_ranges::count(std::forward(policy), view, nocopy_search_value{searched}); + return dpl_ranges::count(std::forward(policy), view, nocopy_search_value_dc{searched}); }, [](auto&&, auto res) { return res == 1; }, "count, noncopyable value"); #endif @@ -201,7 +217,7 @@ main() #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_CONTAINS_HETERO run_algo_hetero_policies( [](auto&& policy, auto&& view) { - return dpl_ranges::contains(std::forward(policy), view, nocopy_search_value{searched}); + return dpl_ranges::contains(std::forward(policy), view, nocopy_search_value_dc{searched}); }, [](auto&&, auto res) { return res; }, "contains, noncopyable value"); #endif @@ -218,7 +234,7 @@ main() #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_HETERO run_algo_hetero_policies( [](auto&& policy, auto&& view) { - return dpl_ranges::remove(std::forward(policy), view, nocopy_search_value{searched}); + return dpl_ranges::remove(std::forward(policy), view, nocopy_search_value_dc{searched}); }, [](auto&& view, auto res) { return std::ranges::size(res) == std::ranges::size(view) - 1; }, "remove, noncopyable value"); diff --git a/test/parallel_api/ranges/std_ranges_archetypes.h b/test/parallel_api/ranges/std_ranges_archetypes.h index 7beb833eb49..013ca390a9a 100644 --- a/test/parallel_api/ranges/std_ranges_archetypes.h +++ b/test/parallel_api/ranges/std_ranges_archetypes.h @@ -540,6 +540,29 @@ operator==(const searchable_archetype& __e, const nocopy_search_value& __v) return __e.val == __v.val; } +// The device copyable counterpart of nocopy_search_value: a device policy copies the value into the +// kernel, so the value used with the hetero policies has to be trivially copyable. Everything else +// stays as restricted as in the host only type: no default constructor, no ordering, no relation to +// the element type but equality. +struct nocopy_search_value_dc +{ + int val; + + explicit nocopy_search_value_dc(int __v) : val(__v) {} + + nocopy_search_value_dc(const nocopy_search_value_dc&) = default; + nocopy_search_value_dc& operator=(const nocopy_search_value_dc&) = default; + + friend bool operator==(const nocopy_search_value_dc& __v1, const nocopy_search_value_dc& __v2) + { + return __v1.val == __v2.val; + } +}; + +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(nocopy_search_value_dc) +static_assert(!std::default_initializable); +static_assert(!std::totally_ordered); + // The element archetype of the removing algorithms. remove() requires // std::permutable> && indirect_binary_predicate // so the element has to be movable, but still not copyable and not default constructible. @@ -596,6 +619,11 @@ struct searchable_archetype_dc { return __e.val == __v.val; } + + friend bool operator==(const searchable_archetype_dc& __e, const nocopy_search_value_dc& __v) + { + return __e.val == __v.val; + } }; struct removable_archetype_dc @@ -617,6 +645,11 @@ struct removable_archetype_dc { return __e.val == __v.val; } + + friend bool operator==(const removable_archetype_dc& __e, const nocopy_search_value_dc& __v) + { + return __e.val == __v.val; + } }; // The common reference required by std::equality_comparable_with. It is only ever formed as a @@ -631,6 +664,7 @@ struct search_common search_common(const removable_archetype_dc& __e) : val(__e.val) {} search_common(const search_value& __v) : val(__v.val) {} search_common(const nocopy_search_value& __v) : val(__v.val) {} + search_common(const nocopy_search_value_dc& __v) : val(__v.val) {} friend bool operator==(const search_common& __v1, const search_common& __v2) { return __v1.val == __v2.val; } }; @@ -739,6 +773,34 @@ struct common_type +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; } // namespace std namespace test_std_ranges @@ -779,8 +841,12 @@ TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(removable_archetype_dc) static_assert(std::indirect_binary_predicate); static_assert( std::indirect_binary_predicate); +static_assert( + std::indirect_binary_predicate); static_assert(std::permutable); static_assert(std::indirect_binary_predicate); +static_assert( + std::indirect_binary_predicate); static_assert(!std::default_initializable); static_assert(!std::default_initializable); static_assert(!std::totally_ordered); From b41bc7d7eea44d8dd5b7d0017c20ea69d2bf6ea8 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 1 Sep 2026 11:18:57 +0200 Subject: [PATCH 060/148] Remove switched off broken test macros --- .../ranges/std_ranges_algo_archetypes_value.pass.cpp | 7 +------ .../ranges/std_ranges_algo_archetypes_write.pass.cpp | 2 -- test/support/test_config.h | 4 ---- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp index 25daf53e6db..bda012472c4 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp @@ -166,7 +166,6 @@ main() }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find, noncopyable value"); -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_HETERO // A device policy copies the value into the kernel, so the hetero runs use the device copyable // counterpart of the value: it is still neither default constructible nor ordered. run_algo_hetero_policies( @@ -174,7 +173,6 @@ main() return dpl_ranges::find(std::forward(policy), view, nocopy_search_value_dc{searched}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find, noncopyable value"); -#endif run_algo_host_policies( [](auto&& policy, auto&& view) { @@ -200,13 +198,12 @@ main() return dpl_ranges::count(std::forward(policy), view, nocopy_search_value{searched}); }, [](auto&&, auto res) { return res == 1; }, "count, noncopyable value"); -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_COUNT_HETERO + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::count(std::forward(policy), view, nocopy_search_value_dc{searched}); }, [](auto&&, auto res) { return res == 1; }, "count, noncopyable value"); -#endif run_algo_host_policies( [](auto&& policy, auto&& view) { @@ -214,13 +211,11 @@ main() }, [](auto&&, auto res) { return res; }, "contains, noncopyable value"); -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_CONTAINS_HETERO run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::contains(std::forward(policy), view, nocopy_search_value_dc{searched}); }, [](auto&&, auto res) { return res; }, "contains, noncopyable value"); -#endif // Same for remove(): the predicate it builds internally must hold a reference to the value for // the host policies. diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp index 3fffb538a1c..a35cb285b1d 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp @@ -84,7 +84,6 @@ main() }, "fill"); -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FILL_HETERO // None of the archetypes below is device copyable, so the host policies are the only ones the // constraints of these algorithms allow. run_algo_hetero_policies( @@ -97,7 +96,6 @@ main() std::ranges::begin(view)[std::ranges::size(view) - 1].val == 42; }, "fill"); -#endif run_algo2_host_policies( [](auto&& policy, auto&& in_view, auto&& out_view) { diff --git a/test/support/test_config.h b/test/support/test_config.h index ce52e39374b..c769908689b 100644 --- a/test/support/test_config.h +++ b/test/support/test_config.h @@ -364,10 +364,6 @@ // std::input_iterator and std::output_iterator on the same pre-P2325R3 implementations. #define _ONEDPL_CPP20_IN_OUT_ITERATOR_BROKEN TEST_STD_RANGES_VIEW_CONCEPT_REQUIRES_DEFAULT_INITIALIZABLE -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_CONTAINS_HETERO 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_COUNT_HETERO 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FILL_HETERO 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_HETERO 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_HETERO 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_HOST 1 From b73c92a5b3f10d155d9afae70c4b13a035402eae Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 1 Sep 2026 11:48:24 +0200 Subject: [PATCH 061/148] Extract to separate error: fix __has_subscription_op and struct __subscription_impl_view_simple --- include/oneapi/dpl/pstl/utils_ranges.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/include/oneapi/dpl/pstl/utils_ranges.h b/include/oneapi/dpl/pstl/utils_ranges.h index be3709f0986..626298890b1 100644 --- a/include/oneapi/dpl/pstl/utils_ranges.h +++ b/include/oneapi/dpl/pstl/utils_ranges.h @@ -815,8 +815,10 @@ struct __has_subscription_op : std::false_type { }; +// The check is done on a const lvalue since a range is accessed as const inside a kernel. template -struct __has_subscription_op<_R, std::void_t().operator[](0))>> : std::true_type +struct __has_subscription_op<_R, std::void_t&>().operator[](0))>> + : std::true_type { }; @@ -836,7 +838,8 @@ struct __subscription_impl_view_simple : std::ranges::view_interface<__subscript static_assert(!__has_subscription_op<_View>::value, "The usage of __subscription_impl_view_simple prohibited if _View::operator[] implemented"); - _View __base; + // mutable to support views which are not const-iterable (f.e. std::ranges::reverse_view over a non-common range) + mutable _View __base; constexpr __subscription_impl_view_simple() requires std::default_initializable<_View> From 880a03ea24c340c14d2755f87be0455c02ea8d0c Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 1 Sep 2026 11:49:56 +0200 Subject: [PATCH 062/148] Remove broket test macro _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_HETERO --- .../ranges/std_ranges_algo_archetypes_value.pass.cpp | 4 ---- test/support/test_config.h | 2 -- 2 files changed, 6 deletions(-) diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp index bda012472c4..8d7b145686e 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp @@ -102,14 +102,12 @@ main() [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, "find_last"); -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_HETERO run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::find_last(std::forward(policy), view, search_value{searched}); }, [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, "find_last"); -#endif run_algo_host_policies( [](auto&& policy, auto&& view) { @@ -181,7 +179,6 @@ main() [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, "find_last, noncopyable value"); -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_HETERO run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::find_last(std::forward(policy), view, @@ -189,7 +186,6 @@ main() }, [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, "find_last, noncopyable value"); -#endif // count() must refer to the value instead of storing a copy of it: the requires-clause never // asks for a copyable value type. diff --git a/test/support/test_config.h b/test/support/test_config.h index c769908689b..f66b9682594 100644 --- a/test/support/test_config.h +++ b/test/support/test_config.h @@ -364,8 +364,6 @@ // std::input_iterator and std::output_iterator on the same pre-P2325R3 implementations. #define _ONEDPL_CPP20_IN_OUT_ITERATOR_BROKEN TEST_STD_RANGES_VIEW_CONCEPT_REQUIRES_DEFAULT_INITIALIZABLE -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_HETERO 1 - #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_ELEMENT_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_ELEMENT_HETERO 1 From 127983ec8b558f7575114e8d300fed3094a7c0ec Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 1 Sep 2026 11:58:31 +0200 Subject: [PATCH 063/148] Remove broken test macros _TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_HETERO + _TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_IF_HETERO --- .../ranges/std_ranges_algo_archetypes_permute.pass.cpp | 2 -- .../ranges/std_ranges_algo_archetypes_value.pass.cpp | 4 ---- test/support/test_config.h | 2 -- 3 files changed, 8 deletions(-) diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp index 4c0f0e841e6..8f3047d6fb4 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp @@ -93,7 +93,6 @@ main() }, "remove_if"); -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_IF_HETERO // The storage is filled with 0, 1, 2, ... so every third element is removed. The returned // subrange is the tail holding the removed elements. run_algo_hetero_policies( @@ -105,7 +104,6 @@ main() return std::ranges::size(res) == (n + 2) / 3; }, "remove_if"); -#endif // All the elements are unique, so nothing is dropped. run_algo_host_policies( diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp index 8d7b145686e..028a3ac8ff5 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp @@ -145,7 +145,6 @@ main() }, [](auto&& view, auto res) { return std::ranges::size(res) == std::ranges::size(view) - 1; }, "remove"); -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_HETERO // removable_archetype is movable but not device copyable, so remove() is checked on the host // policies only. run_algo_hetero_policies( @@ -153,7 +152,6 @@ main() return dpl_ranges::remove(std::forward(policy), view, search_value{searched}); }, [](auto&& view, auto res) { return std::ranges::size(res) == std::ranges::size(view) - 1; }, "remove"); -#endif // nocopy_search_value is neither copyable nor movable: the host implementations must refer to // the value passed by the user instead of storing a copy of it. It cannot be captured by a @@ -222,14 +220,12 @@ main() [](auto&& view, auto res) { return std::ranges::size(res) == std::ranges::size(view) - 1; }, "remove, noncopyable value"); -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_HETERO run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::remove(std::forward(policy), view, nocopy_search_value_dc{searched}); }, [](auto&& view, auto res) { return std::ranges::size(res) == std::ranges::size(view) - 1; }, "remove, noncopyable value"); -#endif #endif //_ENABLE_STD_RANGES_TESTING diff --git a/test/support/test_config.h b/test/support/test_config.h index f66b9682594..82cb44ebb01 100644 --- a/test/support/test_config.h +++ b/test/support/test_config.h @@ -374,8 +374,6 @@ #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_ELEMENT_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_ELEMENT_HETERO 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_HETERO 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_IF_HETERO 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HETERO 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HOST 1 From 8036e32a464eb6b807778e92624f20fb3761851d Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 1 Sep 2026 12:06:06 +0200 Subject: [PATCH 064/148] Remove extra broken test macro _TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT_HETERO --- .../ranges/std_ranges_algo_archetypes_permute.pass.cpp | 2 -- test/support/test_config.h | 1 - 2 files changed, 3 deletions(-) diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp index 8f3047d6fb4..e863e468f81 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp @@ -132,7 +132,6 @@ main() }, "sort"); -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT_HETERO // prpbably incorrect type applied run_algo_hetero_policies( [](auto&& policy, auto&& view) { @@ -143,7 +142,6 @@ main() std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; }, "sort"); -#endif // prpbably incorrect type applied run_algo_host_policies( diff --git a/test/support/test_config.h b/test/support/test_config.h index 82cb44ebb01..c40ddd974fd 100644 --- a/test/support/test_config.h +++ b/test/support/test_config.h @@ -378,7 +378,6 @@ #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HETERO 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HETERO 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT_HETERO 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT_HETERO 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE_HETERO 1 From fc1e609b4ffb3f450806a18abcbf025b6b62492b Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 1 Sep 2026 12:09:38 +0200 Subject: [PATCH 065/148] Remove extra broken test macro _TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT_HETERO --- .../ranges/std_ranges_algo_archetypes_permute.pass.cpp | 2 -- test/support/test_config.h | 1 - 2 files changed, 3 deletions(-) diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp index e863e468f81..ed98529106f 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp @@ -154,7 +154,6 @@ main() }, "stable_sort"); -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT_HETERO // prpbably incorrect type applied run_algo_hetero_policies( [](auto&& policy, auto&& view) { @@ -165,7 +164,6 @@ main() std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; }, "stable_sort"); -#endif run_algo_host_policies( [](auto&& policy, auto&& view) { diff --git a/test/support/test_config.h b/test/support/test_config.h index c40ddd974fd..fbc6f7ef8ce 100644 --- a/test/support/test_config.h +++ b/test/support/test_config.h @@ -378,7 +378,6 @@ #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HETERO 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HETERO 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT_HETERO 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE_HETERO 1 #endif // _TEST_CONFIG_H From f735b22cbc1d25b56ef7c45add60886a901ec1c0 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 1 Sep 2026 12:13:49 +0200 Subject: [PATCH 066/148] Remove extra broken test macro _TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE_HETERO --- .../ranges/std_ranges_algo_archetypes_permute.pass.cpp | 2 -- test/support/test_config.h | 1 - 2 files changed, 3 deletions(-) diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp index ed98529106f..8d73f89dfc0 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp @@ -112,14 +112,12 @@ main() }, [](auto&& view, auto res) { return std::ranges::size(res) == 0; }, "unique"); -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE_HETERO // All the elements are unique, so nothing is dropped. run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::unique(std::forward(policy), view, permutable_equiv{}); }, [](auto&& view, auto res) { return std::ranges::size(res) == 0; }, "unique"); -#endif // prpbably incorrect type applied run_algo_host_policies( diff --git a/test/support/test_config.h b/test/support/test_config.h index fbc6f7ef8ce..8e9f4d30f14 100644 --- a/test/support/test_config.h +++ b/test/support/test_config.h @@ -378,6 +378,5 @@ #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HETERO 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HETERO 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE_HETERO 1 #endif // _TEST_CONFIG_H From f4eade0c48deb93da4605fa48ded83a734e169a8 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 1 Sep 2026 13:34:31 +0200 Subject: [PATCH 067/148] Remove extra broken test macro _TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_ELEMENT_HETERO --- .../ranges/std_ranges_algo_archetypes_read.pass.cpp | 3 +-- test/support/test_config.h | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp index f58bff7baeb..01da2a94c5a 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp @@ -203,13 +203,12 @@ main() }, [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "min_element"); #endif -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_ELEMENT_HETERO + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::min_element(std::forward(policy), view, read_comp{}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "min_element"); -#endif #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_ELEMENT_HOST run_algo_host_policies( diff --git a/test/support/test_config.h b/test/support/test_config.h index 8e9f4d30f14..23cd7892b1a 100644 --- a/test/support/test_config.h +++ b/test/support/test_config.h @@ -366,7 +366,6 @@ #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_ELEMENT_HOST 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_ELEMENT_HETERO 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_ELEMENT_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_ELEMENT_HETERO 1 From 8155cf323d7ca290aadfbc557e30d5e1894d49a5 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 1 Sep 2026 13:35:04 +0200 Subject: [PATCH 068/148] Remove extra brokem test macro _TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_ELEMENT_HETERO --- .../ranges/std_ranges_algo_archetypes_read.pass.cpp | 3 +-- test/support/test_config.h | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp index 01da2a94c5a..53eecda888c 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp @@ -218,14 +218,13 @@ main() [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view) - 1; }, "max_element"); #endif -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_ELEMENT_HETERO + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::max_element(std::forward(policy), view, read_comp{}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view) - 1; }, "max_element"); -#endif #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_ELEMENT_HOST run_algo_host_policies( diff --git a/test/support/test_config.h b/test/support/test_config.h index 23cd7892b1a..2361a9beb09 100644 --- a/test/support/test_config.h +++ b/test/support/test_config.h @@ -368,7 +368,6 @@ #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_ELEMENT_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_ELEMENT_HOST 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_ELEMENT_HETERO 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_ELEMENT_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_ELEMENT_HETERO 1 From e0398ec1b0abce64f09e9ff52ae2b73d5b2fc240 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Tue, 1 Sep 2026 13:35:26 +0200 Subject: [PATCH 069/148] Remove extra broken test macro _TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_ELEMENT_HETERO --- .../ranges/std_ranges_algo_archetypes_read.pass.cpp | 3 +-- test/support/test_config.h | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp index 53eecda888c..5b87bc4991f 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp @@ -237,7 +237,7 @@ main() }, "minmax_element"); #endif -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_ELEMENT_HETERO + run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::minmax_element(std::forward(policy), view, read_comp{}); @@ -247,7 +247,6 @@ main() res.max == std::ranges::begin(view) + std::ranges::size(view) - 1; }, "minmax_element"); -#endif run_algo_host_policies( [](auto&& policy, auto&& view) { diff --git a/test/support/test_config.h b/test/support/test_config.h index 2361a9beb09..747d4b92609 100644 --- a/test/support/test_config.h +++ b/test/support/test_config.h @@ -370,7 +370,6 @@ #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_ELEMENT_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_ELEMENT_HOST 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_ELEMENT_HETERO 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HETERO 1 From 7e7f9be923c843bfbbc0d266aa8a95da0cbf1192 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Wed, 9 Sep 2026 10:03:32 +0200 Subject: [PATCH 070/148] Remove extra broke test macros --- .../ranges/std_ranges_algo_archetypes_merge.pass.cpp | 11 ----------- test/support/test_config.h | 7 ------- 2 files changed, 18 deletions(-) diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp index 486880e1d15..4d07b6f43a7 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp @@ -167,18 +167,11 @@ main() [](auto&&, auto&&, auto res) { return res; }, "set_difference"); #endif - // KSATODO: min / max / minmax only require std::indirectly_copyable_storable, which needs a copy - // constructor and copy assignment, but no default constructor. The helpers of __simd_min_element - // and __simd_minmax_element at unseq_backend_simd.h:635 and :695 value initialize their - // _ValueType members in the default constructor, so the calls below do not compile with a - // non-default-constructible element type. -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_HOST run_algo_host_policies( [](auto&& policy, auto&& view) { return dpl_ranges::min(std::forward(policy), view, storable_comp{}); }, [](auto&&, auto res) { return res.val == 0; }, "min"); -#endif run_algo_hetero_policies( [](auto&& policy, auto&& view) { @@ -186,13 +179,11 @@ main() }, [](auto&&, auto res) { return res.val == 0; }, "min"); -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_HOST run_algo_host_policies( [](auto&& policy, auto&& view) { return dpl_ranges::max(std::forward(policy), view, storable_comp{}); }, [](auto&&, auto res) { return res.val == (int)archetype_test_size - 1; }, "max"); -#endif run_algo_hetero_policies( [](auto&& policy, auto&& view) { @@ -200,13 +191,11 @@ main() }, [](auto&&, auto res) { return res.val == (int)archetype_test_size - 1; }, "max"); -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_HOST run_algo_host_policies( [](auto&& policy, auto&& view) { return dpl_ranges::minmax(std::forward(policy), view, storable_comp{}); }, [](auto&&, auto&& res) { return res.min.val == 0 && res.max.val == (int)archetype_test_size - 1; }, "minmax"); -#endif run_algo_hetero_policies( [](auto&& policy, auto&& view) { diff --git a/test/support/test_config.h b/test/support/test_config.h index 747d4b92609..3bef8fb23b1 100644 --- a/test/support/test_config.h +++ b/test/support/test_config.h @@ -364,13 +364,6 @@ // std::input_iterator and std::output_iterator on the same pre-P2325R3 implementations. #define _ONEDPL_CPP20_IN_OUT_ITERATOR_BROKEN TEST_STD_RANGES_VIEW_CONCEPT_REQUIRES_DEFAULT_INITIALIZABLE -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_HOST 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_ELEMENT_HOST 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_HOST 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_ELEMENT_HOST 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_HOST 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_ELEMENT_HOST 1 - #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HETERO 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HOST 1 From 9a7c543fc697a3b292cbb1908a0701923a20f789 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Wed, 9 Sep 2026 10:11:42 +0200 Subject: [PATCH 071/148] Add missed TEST_DPCPP_BACKEND_PRESENT macro checks --- .../std_ranges_algo_archetypes_merge.pass.cpp | 23 +++++++++---- ...td_ranges_algo_archetypes_permute.pass.cpp | 13 ++++++-- .../std_ranges_algo_archetypes_read.pass.cpp | 32 +++++++++++-------- .../ranges/std_ranges_algo_archetypes_test.h | 2 +- .../std_ranges_algo_archetypes_value.pass.cpp | 20 ++++++++++-- .../std_ranges_algo_archetypes_write.pass.cpp | 10 +++--- 6 files changed, 71 insertions(+), 29 deletions(-) diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp index 4d07b6f43a7..01d79f8c870 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp @@ -96,7 +96,7 @@ main() std::ranges::begin(out_view)[2].val == 1 && res.out == std::ranges::end(out_view); }, [](auto&&, auto&&, auto res) { return res; }, "merge"); -#endif //TEST_DPCPP_BACKEND_PRESENT +#endif // TEST_DPCPP_BACKEND_PRESENT // KSATODO: the set operations only require std::mergeable, i.e. indirectly_copyable from either // input into the output, which is an assignment and not a construction. The implementation @@ -120,6 +120,8 @@ main() }, [](auto&&, auto&&, auto res) { return res; }, "set_union"); #endif + +#if TEST_DPCPP_BACKEND_PRESENT #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HETERO run_algo2_hetero_policies( [](auto&& policy, auto&& view1, auto&& view2) { @@ -135,8 +137,8 @@ main() (std::size_t)(res.out - std::ranges::begin(out_view)) == archetype_test_size; }, [](auto&&, auto&&, auto res) { return res; }, "set_union"); -#endif - +#endif // !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HETERO +#endif // TEST_DPCPP_BACKEND_PRESENT #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HOST run_algo2_host_policies( @@ -151,6 +153,8 @@ main() }, [](auto&&, auto&&, auto res) { return res; }, "set_difference"); #endif + +#if TEST_DPCPP_BACKEND_PRESENT #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HETERO run_algo2_hetero_policies( [](auto&& policy, auto&& view1, auto&& view2) { @@ -165,7 +169,8 @@ main() return res.out == std::ranges::begin(out_view); }, [](auto&&, auto&&, auto res) { return res; }, "set_difference"); -#endif +#endif // !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HETERO +#endif // TEST_DPCPP_BACKEND_PRESENT run_algo_host_policies( [](auto&& policy, auto&& view) { @@ -173,35 +178,41 @@ main() }, [](auto&&, auto res) { return res.val == 0; }, "min"); +#if TEST_DPCPP_BACKEND_PRESENT run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::min(std::forward(policy), view, storable_comp{}); }, [](auto&&, auto res) { return res.val == 0; }, "min"); +#endif // TEST_DPCPP_BACKEND_PRESENT run_algo_host_policies( [](auto&& policy, auto&& view) { return dpl_ranges::max(std::forward(policy), view, storable_comp{}); }, [](auto&&, auto res) { return res.val == (int)archetype_test_size - 1; }, "max"); - + +#if TEST_DPCPP_BACKEND_PRESENT run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::max(std::forward(policy), view, storable_comp{}); }, [](auto&&, auto res) { return res.val == (int)archetype_test_size - 1; }, "max"); +#endif // TEST_DPCPP_BACKEND_PRESENT run_algo_host_policies( [](auto&& policy, auto&& view) { return dpl_ranges::minmax(std::forward(policy), view, storable_comp{}); }, [](auto&&, auto&& res) { return res.min.val == 0 && res.max.val == (int)archetype_test_size - 1; }, "minmax"); - + +#if TEST_DPCPP_BACKEND_PRESENT run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::minmax(std::forward(policy), view, storable_comp{}); }, [](auto&&, auto&& res) { return res.min.val == 0 && res.max.val == (int)archetype_test_size - 1; }, "minmax"); +#endif // TEST_DPCPP_BACKEND_PRESENT #endif //_ENABLE_STD_RANGES_TESTING diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp index 8d73f89dfc0..a9d77bb8ba0 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp @@ -79,7 +79,7 @@ main() return std::ranges::begin(view)[0].val == (int)n - 1 && std::ranges::begin(view)[n - 1].val == 0; }, "reverse"); -#endif //TEST_DPCPP_BACKEND_PRESENT +#endif // TEST_DPCPP_BACKEND_PRESENT // The storage is filled with 0, 1, 2, ... so every third element is removed. The returned // subrange is the tail holding the removed elements. @@ -95,6 +95,7 @@ main() // The storage is filled with 0, 1, 2, ... so every third element is removed. The returned // subrange is the tail holding the removed elements. +#if TEST_DPCPP_BACKEND_PRESENT run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::remove_if(std::forward(policy), view, permutable_pred{}); @@ -104,6 +105,7 @@ main() return std::ranges::size(res) == (n + 2) / 3; }, "remove_if"); +#endif // TEST_DPCPP_BACKEND_PRESENT // All the elements are unique, so nothing is dropped. run_algo_host_policies( @@ -112,12 +114,14 @@ main() }, [](auto&& view, auto res) { return std::ranges::size(res) == 0; }, "unique"); +#if TEST_DPCPP_BACKEND_PRESENT // All the elements are unique, so nothing is dropped. run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::unique(std::forward(policy), view, permutable_equiv{}); }, [](auto&& view, auto res) { return std::ranges::size(res) == 0; }, "unique"); +#endif // TEST_DPCPP_BACKEND_PRESENT // prpbably incorrect type applied run_algo_host_policies( @@ -130,6 +134,7 @@ main() }, "sort"); +#if TEST_DPCPP_BACKEND_PRESENT // prpbably incorrect type applied run_algo_hetero_policies( [](auto&& policy, auto&& view) { @@ -140,6 +145,7 @@ main() std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; }, "sort"); +#endif // TEST_DPCPP_BACKEND_PRESENT // prpbably incorrect type applied run_algo_host_policies( @@ -152,6 +158,7 @@ main() }, "stable_sort"); +#if TEST_DPCPP_BACKEND_PRESENT // prpbably incorrect type applied run_algo_hetero_policies( [](auto&& policy, auto&& view) { @@ -162,6 +169,7 @@ main() std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; }, "stable_sort"); +#endif // TEST_DPCPP_BACKEND_PRESENT run_algo_host_policies( [](auto&& policy, auto&& view) { @@ -175,7 +183,8 @@ main() return dpl_ranges::is_sorted(std::forward(policy), view, permutable_comp{}); }, [](auto&&, auto res) { return res; }, "is_sorted"); -#endif //TEST_DPCPP_BACKEND_PRESENT +#endif // TEST_DPCPP_BACKEND_PRESENT + #endif //_ENABLE_STD_RANGES_TESTING return TestUtils::done(_ENABLE_STD_RANGES_TESTING); diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp index 5b87bc4991f..c5498fd0460 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp @@ -108,7 +108,7 @@ main() return dpl_ranges::find_if(std::forward(policy), view, read_unary_pred{}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "find_if"); -#endif //TEST_DPCPP_BACKEND_PRESENT +#endif // TEST_DPCPP_BACKEND_PRESENT run_algo_host_policies( [](auto&& policy, auto&& view) { @@ -122,7 +122,7 @@ main() return dpl_ranges::find_if_not(std::forward(policy), view, read_unary_pred{}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + 1; }, "find_if_not"); -#endif //TEST_DPCPP_BACKEND_PRESENT +#endif // TEST_DPCPP_BACKEND_PRESENT run_algo_host_policies( [](auto&& policy, auto&& view) { @@ -136,7 +136,7 @@ main() return dpl_ranges::any_of(std::forward(policy), view, read_unary_pred{}); }, [](auto&&, bool res) { return res; }, "any_of"); -#endif //TEST_DPCPP_BACKEND_PRESENT +#endif // TEST_DPCPP_BACKEND_PRESENT run_algo_host_policies( [](auto&& policy, auto&& view) { @@ -150,7 +150,7 @@ main() return dpl_ranges::all_of(std::forward(policy), view, read_unary_pred{}); }, [](auto&&, bool res) { return !res; }, "all_of"); -#endif //TEST_DPCPP_BACKEND_PRESENT +#endif // TEST_DPCPP_BACKEND_PRESENT run_algo_host_policies( [](auto&& policy, auto&& view) { @@ -166,7 +166,7 @@ main() }, [](auto&& view, auto res) { return res == (std::ranges::range_difference_t) ((std::ranges::size(view) + 2) / 3); }, "count_if"); -#endif //TEST_DPCPP_BACKEND_PRESENT +#endif // TEST_DPCPP_BACKEND_PRESENT // The projection returns an unrelated prvalue type, so the predicate can only ever be applied to // the projected value. @@ -184,7 +184,7 @@ main() }, [](auto&& view, auto res) { return res == (std::ranges::range_difference_t) ((std::ranges::size(view) + 2) / 3); }, "count_if with proj"); -#endif //TEST_DPCPP_BACKEND_PRESENT +#endif // TEST_DPCPP_BACKEND_PRESENT // KSATODO: min_element/max_element/minmax_element only require std::indirect_strict_weak_order // on the projected iterator, so the element type itself has to stay non-copyable and @@ -204,11 +204,13 @@ main() [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "min_element"); #endif +#if TEST_DPCPP_BACKEND_PRESENT run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::min_element(std::forward(policy), view, read_comp{}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "min_element"); +#endif // TEST_DPCPP_BACKEND_PRESENT #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_ELEMENT_HOST run_algo_host_policies( @@ -219,12 +221,14 @@ main() "max_element"); #endif +#if TEST_DPCPP_BACKEND_PRESENT run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::max_element(std::forward(policy), view, read_comp{}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view) - 1; }, "max_element"); +#endif // TEST_DPCPP_BACKEND_PRESENT #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_ELEMENT_HOST run_algo_host_policies( @@ -237,7 +241,8 @@ main() }, "minmax_element"); #endif - + +#if TEST_DPCPP_BACKEND_PRESENT run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::minmax_element(std::forward(policy), view, read_comp{}); @@ -247,6 +252,7 @@ main() res.max == std::ranges::begin(view) + std::ranges::size(view) - 1; }, "minmax_element"); +#endif // TEST_DPCPP_BACKEND_PRESENT run_algo_host_policies( [](auto&& policy, auto&& view) { @@ -260,7 +266,7 @@ main() return dpl_ranges::is_sorted(std::forward(policy), view, read_comp{}); }, [](auto&&, bool res) { return res; }, "is_sorted"); -#endif //TEST_DPCPP_BACKEND_PRESENT +#endif // TEST_DPCPP_BACKEND_PRESENT run_algo_host_policies( [](auto&& policy, auto&& view) { @@ -276,7 +282,7 @@ main() }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, "adjacent_find"); -#endif //TEST_DPCPP_BACKEND_PRESENT +#endif // TEST_DPCPP_BACKEND_PRESENT // The search value type is unrelated to the element type. run_algo_host_policies( @@ -291,7 +297,7 @@ main() return dpl_ranges::find(std::forward(policy), view, search_value{7}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + 7; }, "find"); -#endif //TEST_DPCPP_BACKEND_PRESENT +#endif // TEST_DPCPP_BACKEND_PRESENT run_algo_host_policies( [](auto&& policy, auto&& view) { @@ -305,7 +311,7 @@ main() return dpl_ranges::count(std::forward(policy), view, search_value{7}); }, [](auto&&, auto res) { return res == 1; }, "count"); -#endif //TEST_DPCPP_BACKEND_PRESENT +#endif // TEST_DPCPP_BACKEND_PRESENT // Two ranges of unrelated element types, compared only through the user predicate. run_algo2_host_policies( @@ -320,7 +326,7 @@ main() return dpl_ranges::equal(std::forward(policy), view1, view2, cross_pred{}); }, [](auto&&, auto&&, bool res) { return res; }, "equal"); -#endif //TEST_DPCPP_BACKEND_PRESENT +#endif // TEST_DPCPP_BACKEND_PRESENT run_algo2_host_policies( [](auto&& policy, auto&& view1, auto&& view2) { @@ -342,7 +348,7 @@ main() res.in2 == std::ranges::begin(view2) + std::ranges::size(view2); }, "mismatch"); -#endif //TEST_DPCPP_BACKEND_PRESENT +#endif // TEST_DPCPP_BACKEND_PRESENT #endif //_ENABLE_STD_RANGES_TESTING diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h b/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h index aa54efa7973..055c8eb0030 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h @@ -126,7 +126,7 @@ run_algo2_hetero_policies(_Algo __algo, _Checker __checker, const char* __algo_n sycl::usm_allocator<_Elem2, sycl::usm::alloc::shared> __q_alloc2{__policy.queue()}; run_algo2<_Elem1, _Elem2>(__q_alloc1, __q_alloc2, __policy, __algo, __checker, __algo_name); } -#endif +#endif // TEST_DPCPP_BACKEND_PRESENT } //namespace test_std_ranges diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp index 028a3ac8ff5..5d040621ba4 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp @@ -93,7 +93,7 @@ main() return dpl_ranges::find(std::forward(policy), view, search_value{searched}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find"); -#endif //TEST_DPCPP_BACKEND_PRESENT +#endif // TEST_DPCPP_BACKEND_PRESENT run_algo_host_policies( [](auto&& policy, auto&& view) { @@ -102,12 +102,14 @@ main() [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, "find_last"); +#if TEST_DPCPP_BACKEND_PRESENT run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::find_last(std::forward(policy), view, search_value{searched}); }, [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, "find_last"); +#endif run_algo_host_policies( [](auto&& policy, auto&& view) { @@ -121,7 +123,7 @@ main() return dpl_ranges::count(std::forward(policy), view, search_value{searched}); }, [](auto&&, auto res) { return res == 1; }, "count"); -#endif //TEST_DPCPP_BACKEND_PRESENT +#endif // TEST_DPCPP_BACKEND_PRESENT run_algo_host_policies( [](auto&& policy, auto&& view) { @@ -135,7 +137,7 @@ main() return dpl_ranges::contains(std::forward(policy), view, search_value{searched}); }, [](auto&&, auto res) { return res; }, "contains"); -#endif //TEST_DPCPP_BACKEND_PRESENT +#endif // TEST_DPCPP_BACKEND_PRESENT // removable_archetype is movable but not device copyable, so remove() is checked on the host // policies only. @@ -145,6 +147,7 @@ main() }, [](auto&& view, auto res) { return std::ranges::size(res) == std::ranges::size(view) - 1; }, "remove"); +#if TEST_DPCPP_BACKEND_PRESENT // removable_archetype is movable but not device copyable, so remove() is checked on the host // policies only. run_algo_hetero_policies( @@ -152,6 +155,7 @@ main() return dpl_ranges::remove(std::forward(policy), view, search_value{searched}); }, [](auto&& view, auto res) { return std::ranges::size(res) == std::ranges::size(view) - 1; }, "remove"); +#endif // TEST_DPCPP_BACKEND_PRESENT // nocopy_search_value is neither copyable nor movable: the host implementations must refer to // the value passed by the user instead of storing a copy of it. It cannot be captured by a @@ -162,6 +166,7 @@ main() }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find, noncopyable value"); +#if TEST_DPCPP_BACKEND_PRESENT // A device policy copies the value into the kernel, so the hetero runs use the device copyable // counterpart of the value: it is still neither default constructible nor ordered. run_algo_hetero_policies( @@ -169,6 +174,7 @@ main() return dpl_ranges::find(std::forward(policy), view, nocopy_search_value_dc{searched}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find, noncopyable value"); +#endif // TEST_DPCPP_BACKEND_PRESENT run_algo_host_policies( [](auto&& policy, auto&& view) { @@ -177,6 +183,7 @@ main() [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, "find_last, noncopyable value"); +#if TEST_DPCPP_BACKEND_PRESENT run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::find_last(std::forward(policy), view, @@ -184,6 +191,7 @@ main() }, [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, "find_last, noncopyable value"); +#endif // TEST_DPCPP_BACKEND_PRESENT // count() must refer to the value instead of storing a copy of it: the requires-clause never // asks for a copyable value type. @@ -193,11 +201,13 @@ main() }, [](auto&&, auto res) { return res == 1; }, "count, noncopyable value"); +#if TEST_DPCPP_BACKEND_PRESENT run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::count(std::forward(policy), view, nocopy_search_value_dc{searched}); }, [](auto&&, auto res) { return res == 1; }, "count, noncopyable value"); +#endif // TEST_DPCPP_BACKEND_PRESENT run_algo_host_policies( [](auto&& policy, auto&& view) { @@ -205,11 +215,13 @@ main() }, [](auto&&, auto res) { return res; }, "contains, noncopyable value"); +#if TEST_DPCPP_BACKEND_PRESENT run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::contains(std::forward(policy), view, nocopy_search_value_dc{searched}); }, [](auto&&, auto res) { return res; }, "contains, noncopyable value"); +#endif // TEST_DPCPP_BACKEND_PRESENT // Same for remove(): the predicate it builds internally must hold a reference to the value for // the host policies. @@ -220,12 +232,14 @@ main() [](auto&& view, auto res) { return std::ranges::size(res) == std::ranges::size(view) - 1; }, "remove, noncopyable value"); +#if TEST_DPCPP_BACKEND_PRESENT run_algo_hetero_policies( [](auto&& policy, auto&& view) { return dpl_ranges::remove(std::forward(policy), view, nocopy_search_value_dc{searched}); }, [](auto&& view, auto res) { return std::ranges::size(res) == std::ranges::size(view) - 1; }, "remove, noncopyable value"); +#endif // TEST_DPCPP_BACKEND_PRESENT #endif //_ENABLE_STD_RANGES_TESTING diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp index a35cb285b1d..8f69fd102c5 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp @@ -84,6 +84,7 @@ main() }, "fill"); +#if TEST_DPCPP_BACKEND_PRESENT // None of the archetypes below is device copyable, so the host policies are the only ones the // constraints of these algorithms allow. run_algo_hetero_policies( @@ -96,6 +97,7 @@ main() std::ranges::begin(view)[std::ranges::size(view) - 1].val == 42; }, "fill"); +#endif // TEST_DPCPP_BACKEND_PRESENT run_algo2_host_policies( [](auto&& policy, auto&& in_view, auto&& out_view) { @@ -115,7 +117,7 @@ main() return std::ranges::begin(out_view)[7].val == std::ranges::begin(in_view)[7].val; }, "copy"); -#endif //TEST_DPCPP_BACKEND_PRESENT +#endif // TEST_DPCPP_BACKEND_PRESENT run_algo2_host_policies( [](auto&& policy, auto&& in_view, auto&& out_view) { @@ -129,7 +131,7 @@ main() return dpl_ranges::move(std::forward(policy), in_view, out_view); }, [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 7; }, "move"); -#endif //TEST_DPCPP_BACKEND_PRESENT +#endif // TEST_DPCPP_BACKEND_PRESENT run_algo2_host_policies( [](auto&& policy, auto&& view1, auto&& view2) { @@ -149,7 +151,7 @@ main() return std::ranges::begin(view1)[7].val == 7 && std::ranges::begin(view2)[7].val == 7; }, "swap_ranges"); -#endif //TEST_DPCPP_BACKEND_PRESENT +#endif // TEST_DPCPP_BACKEND_PRESENT run_algo2_host_policies( [](auto&& policy, auto&& in_view, auto&& out_view) { @@ -165,7 +167,7 @@ main() transform_unary_op{}); }, [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 14; }, "transform"); -#endif //TEST_DPCPP_BACKEND_PRESENT +#endif // TEST_DPCPP_BACKEND_PRESENT #endif //_ENABLE_STD_RANGES_TESTING return TestUtils::done(_ENABLE_STD_RANGES_TESTING); From 07451e52b9cbd30c4ffcc13f476e3ef87fe0fb98 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Wed, 9 Sep 2026 11:23:04 +0200 Subject: [PATCH 072/148] Extend test coverage --- .../std_ranges_algo_archetypes_merge.pass.cpp | 523 ++++++---- ..._algo_archetypes_mutable_callable.pass.cpp | 987 ++++++++++++++++++ ...td_ranges_algo_archetypes_permute.pass.cpp | 401 +++---- .../std_ranges_algo_archetypes_read.pass.cpp | 912 +++++++++------- .../ranges/std_ranges_algo_archetypes_test.h | 25 + .../std_ranges_algo_archetypes_value.pass.cpp | 496 ++++----- .../ranges/std_ranges_archetypes.h | 150 +++ test/support/test_config.h | 26 + 8 files changed, 2506 insertions(+), 1014 deletions(-) create mode 100644 test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp index 01d79f8c870..d404275e39f 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp @@ -1,220 +1,303 @@ -// -*- C++ -*- -//===----------------------------------------------------------------------===// -// -// Copyright (C) Intel Corporation -// -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -// This file incorporates work covered by the following copyright and permission -// notice: -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// -//===----------------------------------------------------------------------===// - -#include -#include - -#include "support/test_config.h" -#include "support/test_macros.h" -#include "support/utils.h" - -#if _ENABLE_STD_RANGES_TESTING -#include "std_ranges_archetypes.h" -#include "std_ranges_algo_archetypes_test.h" - -namespace test_std_ranges -{ -namespace dpl_ranges = oneapi::dpl::ranges; - -using seq_policy = decltype(oneapi::dpl::execution::seq); - -using merge_in_view = archetypes::archetype_view; -using merge_out_view = archetypes::archetype_view; -using storable_view = archetypes::archetype_view; - -// The merge family is constrained by std::mergeable, which asks for indirectly_copyable from both -// inputs into the output plus a strict weak order: the output element stays non-copyable itself and -// the ordering never comes from an operator< on the element. -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); - -// min / max / minmax additionally require std::indirectly_copyable_storable, -// range_value_t<_R>*>, which does need a copy constructor and copy assignment, but still no default -// constructor and no ordering operator on the element. -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); - -} //namespace test_std_ranges -#endif //_ENABLE_STD_RANGES_TESTING - -int -main() -{ -#if _ENABLE_STD_RANGES_TESTING - using namespace test_std_ranges; - using namespace test_std_ranges::archetypes; - namespace dpl_ranges = oneapi::dpl::ranges; - - // Neither archetype is device copyable, so the host policies are the only ones their - // constraints allow. - - // Both inputs hold the very same sorted sequence 0, 1, 2, ... - run_algo2_host_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - using __out_elem = - typename std::ranges::range_value_t>::out_type; - archetype_storage<__out_elem, std::allocator<__out_elem>> out_storage( - std::allocator<__out_elem>{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); - auto out_view = out_storage.view(); - auto res = dpl_ranges::merge(std::forward(policy), view1, view2, out_view, merge_comp{}); - return std::ranges::begin(out_view)[0].val == 0 && std::ranges::begin(out_view)[1].val == 0 && - std::ranges::begin(out_view)[2].val == 1 && res.out == std::ranges::end(out_view); - }, - [](auto&&, auto&&, auto res) { return res; }, "merge"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - using __out_elem = - typename std::ranges::range_value_t>::out_type; - archetype_storage<__out_elem, std::allocator<__out_elem>> out_storage( - std::allocator<__out_elem>{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); - auto out_view = out_storage.view(); - auto res = dpl_ranges::merge(std::forward(policy), view1, view2, out_view, merge_comp{}); - return std::ranges::begin(out_view)[0].val == 0 && std::ranges::begin(out_view)[1].val == 0 && - std::ranges::begin(out_view)[2].val == 1 && res.out == std::ranges::end(out_view); - }, - [](auto&&, auto&&, auto res) { return res; }, "merge"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // KSATODO: the set operations only require std::mergeable, i.e. indirectly_copyable from either - // input into the output, which is an assignment and not a construction. The implementation - // instead constructs the output element into raw memory, so the calls below do not compile: - // - set_algorithms_utils.h:91 - placement new of _OutValueType from *__it_in, which also takes - // the address of the element through std::addressof; - // - set_algorithms_utils.h:127,133,206 / memory_impl.h:96,111 - __uninitialized_copy_or_discard - // default constructs and copy constructs the output element type. - // Fixing this means assigning through the output iterator instead of constructing in place. -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HOST - run_algo2_host_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - archetype_storage> out_storage( - std::allocator{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); - auto out_view = out_storage.view(); - auto res = - dpl_ranges::set_union(std::forward(policy), view1, view2, out_view, merge_comp{}); - // The two inputs hold the very same sequence, so the union is that sequence itself. - return std::ranges::begin(out_view)[7].val == 7 && - (std::size_t)(res.out - std::ranges::begin(out_view)) == archetype_test_size; - }, - [](auto&&, auto&&, auto res) { return res; }, "set_union"); -#endif - -#if TEST_DPCPP_BACKEND_PRESENT -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HETERO - run_algo2_hetero_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - using __out_elem = - typename std::ranges::range_value_t>::out_type; - archetype_storage<__out_elem, std::allocator<__out_elem>> out_storage( - std::allocator<__out_elem>{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); - auto out_view = out_storage.view(); - auto res = - dpl_ranges::set_union(std::forward(policy), view1, view2, out_view, merge_comp{}); - // The two inputs hold the very same sequence, so the union is that sequence itself. - return std::ranges::begin(out_view)[7].val == 7 && - (std::size_t)(res.out - std::ranges::begin(out_view)) == archetype_test_size; - }, - [](auto&&, auto&&, auto res) { return res; }, "set_union"); -#endif // !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HETERO -#endif // TEST_DPCPP_BACKEND_PRESENT - -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HOST - run_algo2_host_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - archetype_storage> out_storage( - std::allocator{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); - auto out_view = out_storage.view(); - auto res = dpl_ranges::set_difference(std::forward(policy), view1, view2, out_view, - merge_comp{}); - // The two inputs are equal, so the difference is empty. - return res.out == std::ranges::begin(out_view); - }, - [](auto&&, auto&&, auto res) { return res; }, "set_difference"); -#endif - -#if TEST_DPCPP_BACKEND_PRESENT -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HETERO - run_algo2_hetero_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - using __out_elem = - typename std::ranges::range_value_t>::out_type; - archetype_storage<__out_elem, std::allocator<__out_elem>> out_storage( - std::allocator<__out_elem>{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); - auto out_view = out_storage.view(); - auto res = dpl_ranges::set_difference(std::forward(policy), view1, view2, out_view, - merge_comp{}); - // The two inputs are equal, so the difference is empty. - return res.out == std::ranges::begin(out_view); - }, - [](auto&&, auto&&, auto res) { return res; }, "set_difference"); -#endif // !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HETERO -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::min(std::forward(policy), view, storable_comp{}); - }, - [](auto&&, auto res) { return res.val == 0; }, "min"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::min(std::forward(policy), view, storable_comp{}); - }, - [](auto&&, auto res) { return res.val == 0; }, "min"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::max(std::forward(policy), view, storable_comp{}); - }, - [](auto&&, auto res) { return res.val == (int)archetype_test_size - 1; }, "max"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::max(std::forward(policy), view, storable_comp{}); - }, - [](auto&&, auto res) { return res.val == (int)archetype_test_size - 1; }, "max"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::minmax(std::forward(policy), view, storable_comp{}); - }, - [](auto&&, auto&& res) { return res.min.val == 0 && res.max.val == (int)archetype_test_size - 1; }, "minmax"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::minmax(std::forward(policy), view, storable_comp{}); - }, - [](auto&&, auto&& res) { return res.min.val == 0 && res.max.val == (int)archetype_test_size - 1; }, "minmax"); -#endif // TEST_DPCPP_BACKEND_PRESENT - -#endif //_ENABLE_STD_RANGES_TESTING - - return TestUtils::done(_ENABLE_STD_RANGES_TESTING); -} +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#include +#include + +#include "support/test_config.h" +#include "support/test_macros.h" +#include "support/utils.h" + +#if _ENABLE_STD_RANGES_TESTING +#include "std_ranges_archetypes.h" +#include "std_ranges_algo_archetypes_test.h" + +namespace test_std_ranges +{ +namespace dpl_ranges = oneapi::dpl::ranges; + +using seq_policy = decltype(oneapi::dpl::execution::seq); + +using merge_in_view = archetypes::archetype_view; +using merge_out_view = archetypes::archetype_view; +using storable_view = archetypes::archetype_view; + +// The merge family is constrained by std::mergeable, which asks for indirectly_copyable from both +// inputs into the output plus a strict weak order: the output element stays non-copyable itself and +// the ordering never comes from an operator< on the element. +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); + +// min / max / minmax additionally require std::indirectly_copyable_storable, +// range_value_t<_R>*>, which does need a copy constructor and copy assignment, but still no default +// constructor and no ordering operator on the element. +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); + +} //namespace test_std_ranges +#endif //_ENABLE_STD_RANGES_TESTING + +int +main() +{ +#if _ENABLE_STD_RANGES_TESTING + using namespace test_std_ranges; + using namespace test_std_ranges::archetypes; + namespace dpl_ranges = oneapi::dpl::ranges; + + // Neither archetype is device copyable, so the host policies are the only ones their + // constraints allow. + + // Both inputs hold the very same sorted sequence 0, 1, 2, ... + run_algo2_host_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + using __out_elem = + typename std::ranges::range_value_t>::out_type; + archetype_storage<__out_elem, std::allocator<__out_elem>> out_storage( + std::allocator<__out_elem>{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = dpl_ranges::merge(std::forward(policy), view1, view2, out_view, merge_comp{}); + return std::ranges::begin(out_view)[0].val == 0 && std::ranges::begin(out_view)[1].val == 0 && + std::ranges::begin(out_view)[2].val == 1 && res.out == std::ranges::end(out_view); + }, + [](auto&&, auto&&, auto res) { return res; }, "merge"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + using __out_elem = + typename std::ranges::range_value_t>::out_type; + // The output range is written by a device kernel, so its storage has to be device + // accessible: host memory from std::allocator would be dereferenced on the device. + sycl::usm_allocator<__out_elem, sycl::usm::alloc::shared> out_alloc{policy.queue()}; + archetype_storage<__out_elem, decltype(out_alloc)> out_storage(out_alloc, 2 * archetype_test_size, + [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = dpl_ranges::merge(std::forward(policy), view1, view2, out_view, merge_comp{}); + return std::ranges::begin(out_view)[0].val == 0 && std::ranges::begin(out_view)[1].val == 0 && + std::ranges::begin(out_view)[2].val == 1 && res.out == std::ranges::end(out_view); + }, + [](auto&&, auto&&, auto res) { return res; }, "merge"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // KSATODO: the set operations only require std::mergeable, i.e. indirectly_copyable from either + // input into the output, which is an assignment and not a construction. The implementation + // instead constructs the output element into raw memory, so the calls below do not compile: + // - set_algorithms_utils.h:91 - placement new of _OutValueType from *__it_in, which also takes + // the address of the element through std::addressof; + // - set_algorithms_utils.h:127,133,206,250,259 / memory_impl.h:96,111 - + // __uninitialized_copy_or_discard default constructs and copy constructs the output element + // type; + // - utils.h:1124 - the device path does the same through __lazy_ctor_storage::__setup, which + // placement news the output element and takes its address as well; it is reached from + // parallel_backend_sycl_reduce_then_scan.h:67,571,1049 for every set operation. + // Fixing this means assigning through the output iterator instead of constructing in place. +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HOST + run_algo2_host_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + archetype_storage> out_storage( + std::allocator{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = + dpl_ranges::set_union(std::forward(policy), view1, view2, out_view, merge_comp{}); + // The two inputs hold the very same sequence, so the union is that sequence itself. + return std::ranges::begin(out_view)[7].val == 7 && + (std::size_t)(res.out - std::ranges::begin(out_view)) == archetype_test_size; + }, + [](auto&&, auto&&, auto res) { return res; }, "set_union"); +#endif + +#if TEST_DPCPP_BACKEND_PRESENT +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HETERO + run_algo2_hetero_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + using __out_elem = + typename std::ranges::range_value_t>::out_type; + // The output range is written by a device kernel, so its storage has to be device + // accessible: host memory from std::allocator would be dereferenced on the device. + sycl::usm_allocator<__out_elem, sycl::usm::alloc::shared> out_alloc{policy.queue()}; + archetype_storage<__out_elem, decltype(out_alloc)> out_storage(out_alloc, 2 * archetype_test_size, + [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = + dpl_ranges::set_union(std::forward(policy), view1, view2, out_view, merge_comp{}); + // The two inputs hold the very same sequence, so the union is that sequence itself. + return std::ranges::begin(out_view)[7].val == 7 && + (std::size_t)(res.out - std::ranges::begin(out_view)) == archetype_test_size; + }, + [](auto&&, auto&&, auto res) { return res; }, "set_union"); +#endif // !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HETERO +#endif // TEST_DPCPP_BACKEND_PRESENT + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HOST + run_algo2_host_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + archetype_storage> out_storage( + std::allocator{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = dpl_ranges::set_difference(std::forward(policy), view1, view2, out_view, + merge_comp{}); + // The two inputs are equal, so the difference is empty. + return res.out == std::ranges::begin(out_view); + }, + [](auto&&, auto&&, auto res) { return res; }, "set_difference"); +#endif + +#if TEST_DPCPP_BACKEND_PRESENT +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HETERO + run_algo2_hetero_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + using __out_elem = + typename std::ranges::range_value_t>::out_type; + // The output range is written by a device kernel, so its storage has to be device + // accessible: host memory from std::allocator would be dereferenced on the device. + sycl::usm_allocator<__out_elem, sycl::usm::alloc::shared> out_alloc{policy.queue()}; + archetype_storage<__out_elem, decltype(out_alloc)> out_storage(out_alloc, 2 * archetype_test_size, + [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = dpl_ranges::set_difference(std::forward(policy), view1, view2, out_view, + merge_comp{}); + // The two inputs are equal, so the difference is empty. + return res.out == std::ranges::begin(out_view); + }, + [](auto&&, auto&&, auto res) { return res; }, "set_difference"); +#endif // !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HETERO +#endif // TEST_DPCPP_BACKEND_PRESENT + + // set_intersection and set_symmetric_difference construct the output element the very same way, + // see the note above set_union. +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_INTERSECTION_HOST + run_algo2_host_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + archetype_storage> out_storage( + std::allocator{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = dpl_ranges::set_intersection(std::forward(policy), view1, view2, out_view, + merge_comp{}); + // The two inputs hold the very same sequence, so the intersection is that sequence itself. + return std::ranges::begin(out_view)[7].val == 7 && + (std::size_t)(res.out - std::ranges::begin(out_view)) == archetype_test_size; + }, + [](auto&&, auto&&, auto res) { return res; }, "set_intersection"); +#endif + +#if TEST_DPCPP_BACKEND_PRESENT +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_INTERSECTION_HETERO + run_algo2_hetero_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + using __out_elem = + typename std::ranges::range_value_t>::out_type; + sycl::usm_allocator<__out_elem, sycl::usm::alloc::shared> out_alloc{policy.queue()}; + archetype_storage<__out_elem, decltype(out_alloc)> out_storage(out_alloc, 2 * archetype_test_size, + [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = dpl_ranges::set_intersection(std::forward(policy), view1, view2, out_view, + merge_comp{}); + // The two inputs hold the very same sequence, so the intersection is that sequence itself. + return std::ranges::begin(out_view)[7].val == 7 && + (std::size_t)(res.out - std::ranges::begin(out_view)) == archetype_test_size; + }, + [](auto&&, auto&&, auto res) { return res; }, "set_intersection"); +#endif // !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_INTERSECTION_HETERO +#endif // TEST_DPCPP_BACKEND_PRESENT + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_SYMMETRIC_DIFFERENCE_HOST + run_algo2_host_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + archetype_storage> out_storage( + std::allocator{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = dpl_ranges::set_symmetric_difference(std::forward(policy), view1, view2, + out_view, merge_comp{}); + // The two inputs are equal, so the symmetric difference is empty. + return res.out == std::ranges::begin(out_view); + }, + [](auto&&, auto&&, auto res) { return res; }, "set_symmetric_difference"); +#endif + +#if TEST_DPCPP_BACKEND_PRESENT +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_SYMMETRIC_DIFFERENCE_HETERO + run_algo2_hetero_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + using __out_elem = + typename std::ranges::range_value_t>::out_type; + sycl::usm_allocator<__out_elem, sycl::usm::alloc::shared> out_alloc{policy.queue()}; + archetype_storage<__out_elem, decltype(out_alloc)> out_storage(out_alloc, 2 * archetype_test_size, + [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = dpl_ranges::set_symmetric_difference(std::forward(policy), view1, view2, + out_view, merge_comp{}); + // The two inputs are equal, so the symmetric difference is empty. + return res.out == std::ranges::begin(out_view); + }, + [](auto&&, auto&&, auto res) { return res; }, "set_symmetric_difference"); +#endif // !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_SYMMETRIC_DIFFERENCE_HETERO +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::min(std::forward(policy), view, storable_comp{}); + }, + [](auto&&, auto res) { return res.val == 0; }, "min"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::min(std::forward(policy), view, storable_comp{}); + }, + [](auto&&, auto res) { return res.val == 0; }, "min"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::max(std::forward(policy), view, storable_comp{}); + }, + [](auto&&, auto res) { return res.val == (int)archetype_test_size - 1; }, "max"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::max(std::forward(policy), view, storable_comp{}); + }, + [](auto&&, auto res) { return res.val == (int)archetype_test_size - 1; }, "max"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::minmax(std::forward(policy), view, storable_comp{}); + }, + [](auto&&, auto&& res) { return res.min.val == 0 && res.max.val == (int)archetype_test_size - 1; }, "minmax"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::minmax(std::forward(policy), view, storable_comp{}); + }, + [](auto&&, auto&& res) { return res.min.val == 0 && res.max.val == (int)archetype_test_size - 1; }, "minmax"); +#endif // TEST_DPCPP_BACKEND_PRESENT + +#endif //_ENABLE_STD_RANGES_TESTING + + return TestUtils::done(_ENABLE_STD_RANGES_TESTING); +} diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp new file mode 100644 index 00000000000..972c6d932f6 --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp @@ -0,0 +1,987 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#include +#include + +#include "support/test_config.h" +#include "support/test_macros.h" +#include "support/utils.h" + +#if _ENABLE_STD_RANGES_TESTING +#include "std_ranges_archetypes.h" +#include "std_ranges_algo_archetypes_test.h" + +// The indirect callable concepts (std::indirectly_unary_invocable, std::indirect_unary_predicate, +// std::indirect_binary_predicate, std::indirect_strict_weak_order) and std::projected are spelled in +// terms of iter_value_t<_It>&, iter_reference_t<_It> and iter_common_reference_t<_It>, all three of +// which are a non-const lvalue reference for archetype_view. A projection, a predicate or a +// comparator taking its arguments by non-const reference therefore satisfies the requires-clauses of +// the algorithms below, and the implementation must pass the element to the user callable as a +// non-const lvalue: a const lvalue, an rvalue or a copy does not compile here. +// +// Every case below is an actual call: instantiating the implementation is the only way to prove that +// it compiles, a check of the requires-clause alone never leaves the declaration of the algorithm. +#endif //_ENABLE_STD_RANGES_TESTING + +int +main() +{ +#if _ENABLE_STD_RANGES_TESTING + using namespace test_std_ranges; + using namespace test_std_ranges::archetypes; + namespace dpl_ranges = oneapi::dpl::ranges; + + // The storage is filled with the values 0, 1, 2, ... + constexpr int searched = 3; + + //---------------------------------------------------------------------------------------------- + // Read-only algorithms with a callable taking the element by non-const reference. + //---------------------------------------------------------------------------------------------- + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::for_each(std::forward(policy), view, read_unary_fun_mut{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, + "for_each, non-const callable"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::for_each(std::forward(policy), view, read_unary_fun_mut{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, + "for_each, non-const callable"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_if(std::forward(policy), view, read_unary_pred_mut{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "find_if, non-const callable"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_if(std::forward(policy), view, read_unary_pred_mut{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "find_if, non-const callable"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_if_not(std::forward(policy), view, read_unary_pred_mut{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + 1; }, "find_if_not, non-const callable"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_if_not(std::forward(policy), view, read_unary_pred_mut{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + 1; }, "find_if_not, non-const callable"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // The last element whose value is divisible by three, and the last one whose value is not. + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_last_if(std::forward(policy), view, read_unary_pred_mut{}); + }, + [](auto&& view, auto res) { + auto __n = (int)std::ranges::size(view); + return std::ranges::begin(res) == std::ranges::begin(view) + (__n - 1) / 3 * 3; + }, + "find_last_if, non-const callable"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_last_if(std::forward(policy), view, read_unary_pred_mut{}); + }, + [](auto&& view, auto res) { + auto __n = (int)std::ranges::size(view); + return std::ranges::begin(res) == std::ranges::begin(view) + (__n - 1) / 3 * 3; + }, + "find_last_if, non-const callable"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_last_if_not(std::forward(policy), view, read_unary_pred_mut{}); + }, + [](auto&& view, auto res) { + auto __n = (int)std::ranges::size(view); + return std::ranges::begin(res) == std::ranges::begin(view) + ((__n - 1) % 3 == 0 ? __n - 2 : __n - 1); + }, + "find_last_if_not, non-const callable"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_last_if_not(std::forward(policy), view, read_unary_pred_mut{}); + }, + [](auto&& view, auto res) { + auto __n = (int)std::ranges::size(view); + return std::ranges::begin(res) == std::ranges::begin(view) + ((__n - 1) % 3 == 0 ? __n - 2 : __n - 1); + }, + "find_last_if_not, non-const callable"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::any_of(std::forward(policy), view, read_unary_pred_mut{}); + }, + [](auto&&, bool res) { return res; }, "any_of, non-const callable"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::any_of(std::forward(policy), view, read_unary_pred_mut{}); + }, + [](auto&&, bool res) { return res; }, "any_of, non-const callable"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // Every third element satisfies the predicate, so the range is neither all nor none of it, and it + // is not partitioned either. + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::all_of(std::forward(policy), view, read_unary_pred_mut{}); + }, + [](auto&&, bool res) { return !res; }, "all_of, non-const callable"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::all_of(std::forward(policy), view, read_unary_pred_mut{}); + }, + [](auto&&, bool res) { return !res; }, "all_of, non-const callable"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::none_of(std::forward(policy), view, read_unary_pred_mut{}); + }, + [](auto&&, bool res) { return !res; }, "none_of, non-const callable"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::none_of(std::forward(policy), view, read_unary_pred_mut{}); + }, + [](auto&&, bool res) { return !res; }, "none_of, non-const callable"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_partitioned(std::forward(policy), view, read_unary_pred_mut{}); + }, + [](auto&&, bool res) { return !res; }, "is_partitioned, non-const callable"); + + // KSATODO: std::indirect_unary_predicate only requires the predicate to be invocable with + // iter_reference_t<_It>, a non-const lvalue here, but the device path applies it to a const + // lvalue, so the call does not compile: + // - algorithm_impl_hetero.h:1078,1080 - __pattern_is_partitioned_transform_fn::operator() is + // const and takes the accessor by value, so __acc[__gidx] yields a const reference which is + // passed straight into the predicate. +#if TEST_DPCPP_BACKEND_PRESENT +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_IS_PARTITIONED_HETERO + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_partitioned(std::forward(policy), view, read_unary_pred_mut{}); + }, + [](auto&&, bool res) { return !res; }, "is_partitioned, non-const callable"); +#endif // !_TEST_CPP20_RANGES_BROKEN_REQUIRES_IS_PARTITIONED_HETERO +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count_if(std::forward(policy), view, read_unary_pred_mut{}); + }, + [](auto&& view, auto res) { + return res == (std::ranges::range_difference_t)((std::ranges::size(view) + 2) / 3); + }, + "count_if, non-const callable"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count_if(std::forward(policy), view, read_unary_pred_mut{}); + }, + [](auto&& view, auto res) { + return res == (std::ranges::range_difference_t)((std::ranges::size(view) + 2) / 3); + }, + "count_if, non-const callable"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // The projection takes the element by non-const reference; the predicate sees its prvalue result. + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_if(std::forward(policy), view, read_proj_pred{}, read_proj_mut{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "find_if, non-const projection"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_if(std::forward(policy), view, read_proj_pred{}, read_proj_mut{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "find_if, non-const projection"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count_if(std::forward(policy), view, read_proj_pred{}, + read_proj_mut{}); + }, + [](auto&& view, auto res) { + return res == (std::ranges::range_difference_t)((std::ranges::size(view) + 2) / 3); + }, + "count_if, non-const projection"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count_if(std::forward(policy), view, read_proj_pred{}, + read_proj_mut{}); + }, + [](auto&& view, auto res) { + return res == (std::ranges::range_difference_t)((std::ranges::size(view) + 2) / 3); + }, + "count_if, non-const projection"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::adjacent_find(std::forward(policy), view, read_binary_pred_mut{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, + "adjacent_find, non-const callable"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::adjacent_find(std::forward(policy), view, read_binary_pred_mut{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, + "adjacent_find, non-const callable"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_sorted(std::forward(policy), view, read_comp_mut{}); + }, + [](auto&&, bool res) { return res; }, "is_sorted, non-const comparator"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_sorted(std::forward(policy), view, read_comp_mut{}); + }, + [](auto&&, bool res) { return res; }, "is_sorted, non-const comparator"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // The whole range is sorted, so the scan stops at its end. + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_sorted_until(std::forward(policy), view, read_comp_mut{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, + "is_sorted_until, non-const comparator"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_sorted_until(std::forward(policy), view, read_comp_mut{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, + "is_sorted_until, non-const comparator"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::min_element(std::forward(policy), view, read_comp_mut{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "min_element, non-const comparator"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::min_element(std::forward(policy), view, read_comp_mut{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "min_element, non-const comparator"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::max_element(std::forward(policy), view, read_comp_mut{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view) - 1; }, + "max_element, non-const comparator"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::max_element(std::forward(policy), view, read_comp_mut{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view) - 1; }, + "max_element, non-const comparator"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::minmax_element(std::forward(policy), view, read_comp_mut{}); + }, + [](auto&& view, auto res) { + return res.min == std::ranges::begin(view) && + res.max == std::ranges::begin(view) + std::ranges::size(view) - 1; + }, + "minmax_element, non-const comparator"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::minmax_element(std::forward(policy), view, read_comp_mut{}); + }, + [](auto&& view, auto res) { + return res.min == std::ranges::begin(view) && + res.max == std::ranges::begin(view) + std::ranges::size(view) - 1; + }, + "minmax_element, non-const comparator"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + //---------------------------------------------------------------------------------------------- + // The value based algorithms with a projection taking the element by non-const reference. + //---------------------------------------------------------------------------------------------- + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find(std::forward(policy), view, search_value{searched}, + search_proj_mut{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, + "find, non-const projection"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find(std::forward(policy), view, search_value{searched}, + search_proj_mut{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, + "find, non-const projection"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_last(std::forward(policy), view, search_value{searched}, + search_proj_mut{}); + }, + [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, + "find_last, non-const projection"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_last(std::forward(policy), view, search_value{searched}, + search_proj_mut{}); + }, + [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, + "find_last, non-const projection"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count(std::forward(policy), view, search_value{searched}, + search_proj_mut{}); + }, + [](auto&&, auto res) { return res == 1; }, "count, non-const projection"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count(std::forward(policy), view, search_value{searched}, + search_proj_mut{}); + }, + [](auto&&, auto res) { return res == 1; }, "count, non-const projection"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::contains(std::forward(policy), view, search_value{searched}, + search_proj_mut{}); + }, + [](auto&&, auto res) { return res; }, "contains, non-const projection"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::contains(std::forward(policy), view, search_value{searched}, + search_proj_mut{}); + }, + [](auto&&, auto res) { return res; }, "contains, non-const projection"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::remove(std::forward(policy), view, search_value{searched}, + search_proj_mut{}); + }, + // remove() returns the tail holding the removed elements, and the value 3 occurs exactly once. + [](auto&&, auto res) { return std::ranges::size(res) == 1; }, "remove, non-const projection"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::remove(std::forward(policy), view, search_value{searched}, + search_proj_mut{}); + }, + // remove() returns the tail holding the removed elements, and the value 3 occurs exactly once. + [](auto&&, auto res) { return std::ranges::size(res) == 1; }, "remove, non-const projection"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + //---------------------------------------------------------------------------------------------- + // Two-range algorithms with a predicate taking both elements by non-const reference. + //---------------------------------------------------------------------------------------------- + run_algo2_host_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::equal(std::forward(policy), view1, view2, cross_pred_mut{}); + }, + [](auto&&, auto&&, bool res) { return res; }, "equal, non-const callable"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::equal(std::forward(policy), view1, view2, cross_pred_mut{}); + }, + [](auto&&, auto&&, bool res) { return res; }, "equal, non-const callable"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo2_host_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::mismatch(std::forward(policy), view1, view2, cross_pred_mut{}); + }, + [](auto&& view1, auto&& view2, auto res) { + return res.in1 == std::ranges::begin(view1) + std::ranges::size(view1) && + res.in2 == std::ranges::begin(view2) + std::ranges::size(view2); + }, + "mismatch, non-const callable"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::mismatch(std::forward(policy), view1, view2, cross_pred_mut{}); + }, + [](auto&& view1, auto&& view2, auto res) { + return res.in1 == std::ranges::begin(view1) + std::ranges::size(view1) && + res.in2 == std::ranges::begin(view2) + std::ranges::size(view2); + }, + "mismatch, non-const callable"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // The two ranges hold the very same sequence, so the second one occurs in the first one exactly + // once, at its very beginning. + run_algo2_host_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::search(std::forward(policy), view1, view2, cross_pred_mut{}); + }, + [](auto&& view1, auto&& view2, auto res) { + return std::ranges::begin(res) == std::ranges::begin(view1) && + std::ranges::size(res) == std::ranges::size(view2); + }, + "search, non-const callable"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::search(std::forward(policy), view1, view2, cross_pred_mut{}); + }, + [](auto&& view1, auto&& view2, auto res) { + return std::ranges::begin(res) == std::ranges::begin(view1) && + std::ranges::size(res) == std::ranges::size(view2); + }, + "search, non-const callable"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo2_host_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::find_end(std::forward(policy), view1, view2, cross_pred_mut{}); + }, + [](auto&& view1, auto&& view2, auto res) { + return std::ranges::begin(res) == std::ranges::begin(view1) && + std::ranges::size(res) == std::ranges::size(view2); + }, + "find_end, non-const callable"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::find_end(std::forward(policy), view1, view2, cross_pred_mut{}); + }, + [](auto&& view1, auto&& view2, auto res) { + return std::ranges::begin(res) == std::ranges::begin(view1) && + std::ranges::size(res) == std::ranges::size(view2); + }, + "find_end, non-const callable"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // KSATODO: std::indirectly_comparable<_It1, _It2, _Pred> only requires the predicate to be + // invocable as __pred(*__it1, *__it2), never the other way round. The SIMD brick swaps the two + // arguments, so the vectorized host policies unseq and par_unseq do not compile: + // - unseq_backend_simd.h:827 - __simd_find_first_of builds __u_pred as + // __pred(__val, *__first) with __val taken from the second range and *__first from the first + // one; the branch is a plain if, so it is instantiated whatever the sizes of the ranges are. + // Fixing this means keeping the argument order of the two ranges in both branches. + auto find_first_of_algo = [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::find_first_of(std::forward(policy), view1, view2, cross_pred_mut{}); + }; + auto find_first_of_checker = [](auto&& view1, auto&&, auto res) { return res == std::ranges::begin(view1); }; + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_FIRST_OF_HOST_VEC + run_algo2_host_policies(find_first_of_algo, find_first_of_checker, + "find_first_of, non-const callable"); +#else + run_algo2_novec_policies(find_first_of_algo, find_first_of_checker, + "find_first_of, non-const callable"); +#endif + + // KSATODO: the device path of find_first_of copies the element of the first range into a const + // local, which std::indirectly_comparable neither asks for nor allows to require, so the call does + // not compile: + // - unseq_backend_sycl.h:632,636 - first_match_pred::operator() writes + // const auto __elem = __acc[__shifted_idx]; and passes __elem to the predicate. A forwarding + // reference instead of the const copy fixes both the const-ness and the extra copy. +#if TEST_DPCPP_BACKEND_PRESENT +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_FIRST_OF_HETERO + run_algo2_hetero_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::find_first_of(std::forward(policy), view1, view2, cross_pred_mut{}); + }, + [](auto&& view1, auto&&, auto res) { return res == std::ranges::begin(view1); }, + "find_first_of, non-const callable"); +#endif // !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_FIRST_OF_HETERO +#endif // TEST_DPCPP_BACKEND_PRESENT + + //---------------------------------------------------------------------------------------------- + // transform with a functor taking the input element by non-const reference. + //---------------------------------------------------------------------------------------------- + run_algo2_host_policies( + [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::transform(std::forward(policy), in_view, out_view, + transform_unary_op_mut{}); + }, + [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 14; }, + "transform, non-const callable"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies( + [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::transform(std::forward(policy), in_view, out_view, + transform_unary_op_mut{}); + }, + [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 14; }, + "transform, non-const callable"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + //---------------------------------------------------------------------------------------------- + // The permuting and the sorting algorithms. + //---------------------------------------------------------------------------------------------- + // Every third element is removed; the returned subrange is the tail holding the removed elements. + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::remove_if(std::forward(policy), view, permutable_pred_mut{}); + }, + [](auto&& view, auto res) { return std::ranges::size(res) == (std::ranges::size(view) + 2) / 3; }, + "remove_if, non-const callable"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::remove_if(std::forward(policy), view, permutable_pred_mut{}); + }, + [](auto&& view, auto res) { return std::ranges::size(res) == (std::ranges::size(view) + 2) / 3; }, + "remove_if, non-const callable"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // All the elements are unique, so nothing is dropped. + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::unique(std::forward(policy), view, permutable_equiv_mut{}); + }, + [](auto&&, auto res) { return std::ranges::size(res) == 0; }, "unique, non-const callable"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::unique(std::forward(policy), view, permutable_equiv_mut{}); + }, + [](auto&&, auto res) { return std::ranges::size(res) == 0; }, "unique, non-const callable"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // partition returns the tail of the elements which do not satisfy the predicate. + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::partition(std::forward(policy), view, permutable_pred_mut{}); + }, + [](auto&& view, auto res) { + return std::ranges::size(res) == std::ranges::size(view) - (std::ranges::size(view) + 2) / 3; + }, + "partition, non-const callable"); + + // KSATODO: the device path of partition applies the predicate to a const lvalue, which + // std::indirect_unary_predicate over a permutable iterator does not ask for, so it does not + // compile: + // - unseq_backend_sycl.h:122 - walk_n::operator() is const and calls __f(__rngs[__idx]...) on + // the const range members of single_match_pred_by_idx. +#if TEST_DPCPP_BACKEND_PRESENT +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_PARTITION_HETERO + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::partition(std::forward(policy), view, permutable_pred_mut{}); + }, + [](auto&& view, auto res) { + return std::ranges::size(res) == std::ranges::size(view) - (std::ranges::size(view) + 2) / 3; + }, + "partition, non-const callable"); +#endif // !_TEST_CPP20_RANGES_BROKEN_REQUIRES_PARTITION_HETERO +#endif // TEST_DPCPP_BACKEND_PRESENT + + // The very same comparator as the one the sorting algorithms below are called with. + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_sorted(std::forward(policy), view, permutable_comp_mut{}); + }, + [](auto&&, bool res) { return res; }, "is_sorted of a permutable range, non-const comparator"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_sorted(std::forward(policy), view, permutable_comp_mut{}); + }, + [](auto&&, bool res) { return res; }, "is_sorted of a permutable range, non-const comparator"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // KSATODO: std::sortable<_It, _Comp> only requires the comparator to be invocable with + // iter_reference_t<_It>, which is a non-const lvalue for archetype_view, so a comparator taking + // its arguments by non-const reference is enough. The parallel host merge sort compares against a + // const lvalue instead, so par and par_unseq do not compile: + // - parallel_backend_tbb.h:1037 - std::lower_bound(..., _M_comp) passes the const lvalue _Val + // of the merge split point to the comparator; + // - utils.h:203 - __binary_op::operator() forwards that const lvalue into std::invoke. + // seq and unseq keep the element non-const all the way down and are exercised below. + auto sort_algo = [](auto&& policy, auto&& view) { + return dpl_ranges::sort(std::forward(policy), view, permutable_comp_mut{}); + }; + auto sorted_checker = [](auto&& view, auto) { + return std::ranges::begin(view)[0].val == 0 && + std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; + }; + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT_HOST_PAR + run_algo_host_policies(sort_algo, sorted_checker, "sort, non-const comparator"); +#else + run_algo_seq_policies(sort_algo, sorted_checker, "sort, non-const comparator"); +#endif + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::sort(std::forward(policy), view, permutable_comp_mut{}); + }, + [](auto&& view, auto) { + return std::ranges::begin(view)[0].val == 0 && + std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; + }, + "sort, non-const comparator"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // KSATODO: stable_sort shares the merge sort of the parallel host policies with sort, so it is + // broken for par and par_unseq in exactly the same way, see the note above. + auto stable_sort_algo = [](auto&& policy, auto&& view) { + return dpl_ranges::stable_sort(std::forward(policy), view, permutable_comp_mut{}); + }; + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT_HOST_PAR + run_algo_host_policies(stable_sort_algo, sorted_checker, "stable_sort, non-const comparator"); +#else + run_algo_seq_policies(stable_sort_algo, sorted_checker, "stable_sort, non-const comparator"); +#endif + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::stable_sort(std::forward(policy), view, permutable_comp_mut{}); + }, + [](auto&& view, auto) { + return std::ranges::begin(view)[0].val == 0 && + std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; + }, + "stable_sort, non-const comparator"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + //---------------------------------------------------------------------------------------------- + // merge and min / max / minmax. + //---------------------------------------------------------------------------------------------- + // Both inputs hold the very same sorted sequence 0, 1, 2, ... + run_algo2_host_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + using __out_elem = typename std::ranges::range_value_t>::out_type; + archetype_storage<__out_elem, std::allocator<__out_elem>> out_storage( + std::allocator<__out_elem>{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = + dpl_ranges::merge(std::forward(policy), view1, view2, out_view, merge_comp_mut{}); + return std::ranges::begin(out_view)[0].val == 0 && std::ranges::begin(out_view)[1].val == 0 && + std::ranges::begin(out_view)[2].val == 1 && res.out == std::ranges::end(out_view); + }, + [](auto&&, auto&&, auto res) { return res; }, "merge, non-const comparator"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + using __out_elem = typename std::ranges::range_value_t>::out_type; + // The output range is passed to a kernel just like the inputs, so it has to live in USM. + sycl::usm_allocator<__out_elem, sycl::usm::alloc::shared> __out_alloc{policy.queue()}; + archetype_storage<__out_elem, decltype(__out_alloc)> out_storage( + __out_alloc, 2 * archetype_test_size, [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = + dpl_ranges::merge(std::forward(policy), view1, view2, out_view, merge_comp_mut{}); + return std::ranges::begin(out_view)[0].val == 0 && std::ranges::begin(out_view)[1].val == 0 && + std::ranges::begin(out_view)[2].val == 1 && res.out == std::ranges::end(out_view); + }, + [](auto&&, auto&&, auto res) { return res; }, "merge, non-const comparator"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::min(std::forward(policy), view, storable_comp_mut{}); + }, + [](auto&&, auto res) { return res.val == 0; }, "min, non-const comparator"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::min(std::forward(policy), view, storable_comp_mut{}); + }, + [](auto&&, auto res) { return res.val == 0; }, "min, non-const comparator"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::max(std::forward(policy), view, storable_comp_mut{}); + }, + [](auto&&, auto res) { return res.val == (int)archetype_test_size - 1; }, "max, non-const comparator"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::max(std::forward(policy), view, storable_comp_mut{}); + }, + [](auto&&, auto res) { return res.val == (int)archetype_test_size - 1; }, "max, non-const comparator"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::minmax(std::forward(policy), view, storable_comp_mut{}); + }, + [](auto&&, auto&& res) { return res.min.val == 0 && res.max.val == (int)archetype_test_size - 1; }, + "minmax, non-const comparator"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::minmax(std::forward(policy), view, storable_comp_mut{}); + }, + [](auto&&, auto&& res) { return res.min.val == 0 && res.max.val == (int)archetype_test_size - 1; }, + "minmax, non-const comparator"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + //---------------------------------------------------------------------------------------------- + // The remaining algorithms constrained by std::sortable, i.e. by the very same comparator + // requirement as sort: partial_sort, nth_element and inplace_merge. + //---------------------------------------------------------------------------------------------- + // KSATODO: partial_sort shares the parallel merge sort with sort, so the parallel host policies + // hand a const lvalue to the comparator here as well and par / par_unseq do not compile: + // - parallel_backend_tbb.h:1023,1026,1034 - __merge_func::split_merging passes *(_M_x_beg + __ym) + // to std::upper_bound / std::lower_bound, which compares against their const lvalue parameter; + // - utils.h:203 - __binary_op::operator() forwards that const lvalue into std::invoke. + // seq and unseq keep the element non-const all the way down and are exercised below. + // The range is ascending already, so the first ten elements are 0 ... 9 afterwards. + auto partial_sort_algo = [](auto&& policy, auto&& view) { + return dpl_ranges::partial_sort(std::forward(policy), view, std::ranges::begin(view) + 10, + permutable_comp_mut{}); + }; + auto partial_sort_checker = [](auto&& view, auto) { + return std::ranges::begin(view)[0].val == 0 && std::ranges::begin(view)[9].val == 9; + }; + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_PARTIAL_SORT_HOST_PAR + run_algo_host_policies(partial_sort_algo, partial_sort_checker, + "partial_sort, non-const comparator"); +#else + run_algo_seq_policies(partial_sort_algo, partial_sort_checker, + "partial_sort, non-const comparator"); +#endif + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::partial_sort(std::forward(policy), view, + std::ranges::begin(view) + 10, permutable_comp_mut{}); + }, + [](auto&& view, auto) { return std::ranges::begin(view)[0].val == 0 && std::ranges::begin(view)[9].val == 9; }, + "partial_sort, non-const comparator"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // KSATODO: std::sortable only requires the comparator to be invocable with the non-const + // reference of the element, but the parallel path of nth_element compares against a const lvalue, + // so par and par_unseq do not compile: + // - algorithm_impl.h:2841 - the partition predicate of the quickselect loop takes const _Tp& and + // passes it into std::invoke(__comp, __x, *__first). + // Taking the element by reference in that lambda is enough to fix it; seq and unseq are fine. + auto nth_element_algo = [](auto&& policy, auto&& view) { + return dpl_ranges::nth_element(std::forward(policy), view, std::ranges::begin(view) + 10, + permutable_comp_mut{}); + }; + auto nth_element_checker = [](auto&& view, auto) { return std::ranges::begin(view)[10].val == 10; }; + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_NTH_ELEMENT_HOST_PAR + run_algo_host_policies(nth_element_algo, nth_element_checker, + "nth_element, non-const comparator"); +#else + run_algo_seq_policies(nth_element_algo, nth_element_checker, + "nth_element, non-const comparator"); +#endif + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::nth_element(std::forward(policy), view, std::ranges::begin(view) + 10, + permutable_comp_mut{}); + }, + [](auto&& view, auto) { return std::ranges::begin(view)[10].val == 10; }, "nth_element, non-const comparator"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // KSATODO: inplace_merge does not compile with any host policy, for two independent reasons: + // - algorithm_ranges_impl.h:848 - the serial path returns __end(__r), i.e. the sentinel of the + // range, while the declared return type is std::ranges::borrowed_iterator_t<_R>. For a range + // which is not a common_range the two types differ, so seq already fails to compile. This one + // is independent of the comparator and hits any user range with a distinct sentinel type; + // - the const lvalue of the merge split point is handed to the comparator, which std::sortable + // never asks for: std::inplace_merge compares against its const value parameter for unseq, and + // parallel_backend_tbb.h:1240,1245 does the same through std::upper_bound / std::lower_bound + // for par and par_unseq. + // Both halves of the ascending range are sorted, so merging them keeps it as it is. +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_INPLACE_MERGE_HOST + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::inplace_merge(std::forward(policy), view, + std::ranges::begin(view) + std::ranges::size(view) / 2, + permutable_comp_mut{}); + }, + sorted_checker, "inplace_merge, non-const comparator"); +#endif + + // KSATODO: the device path of inplace_merge compares two const lvalues, which std::sortable does + // not ask for, so the call does not compile: + // - parallel_backend_sycl_merge.h:128-133 - the lambda of __find_start_point captures __rng1 and + // __rng2 and subscripts them as const, and both results go into the comparator. +#if TEST_DPCPP_BACKEND_PRESENT +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_INPLACE_MERGE_HETERO + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::inplace_merge(std::forward(policy), view, + std::ranges::begin(view) + std::ranges::size(view) / 2, + permutable_comp_mut{}); + }, + [](auto&& view, auto) { + return std::ranges::begin(view)[0].val == 0 && + std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; + }, + "inplace_merge, non-const comparator"); +#endif // !_TEST_CPP20_RANGES_BROKEN_REQUIRES_INPLACE_MERGE_HETERO +#endif // TEST_DPCPP_BACKEND_PRESENT + + //---------------------------------------------------------------------------------------------- + // The set operations, whose comparator is constrained exactly like the one of merge. They are + // guarded by the very same macros as in std_ranges_algo_archetypes_merge.pass.cpp: the + // implementation constructs the output element instead of assigning to it, which std::mergeable + // never asks for, and that breaks the call before the comparator is ever reached. + //---------------------------------------------------------------------------------------------- +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HOST + run_algo2_host_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + archetype_storage> out_storage( + std::allocator{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = dpl_ranges::set_union(std::forward(policy), view1, view2, out_view, + merge_comp_mut{}); + // The two inputs hold the very same sequence, so the union is that sequence itself. + return std::ranges::begin(out_view)[7].val == 7 && + (std::size_t)(res.out - std::ranges::begin(out_view)) == archetype_test_size; + }, + [](auto&&, auto&&, auto res) { return res; }, "set_union, non-const comparator"); +#endif + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HOST + run_algo2_host_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + archetype_storage> out_storage( + std::allocator{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = dpl_ranges::set_difference(std::forward(policy), view1, view2, out_view, + merge_comp_mut{}); + // The two inputs are equal, so the difference is empty. + return res.out == std::ranges::begin(out_view); + }, + [](auto&&, auto&&, auto res) { return res; }, "set_difference, non-const comparator"); +#endif + +#if TEST_DPCPP_BACKEND_PRESENT +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HETERO + run_algo2_hetero_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + using __out_elem = typename std::ranges::range_value_t>::out_type; + sycl::usm_allocator<__out_elem, sycl::usm::alloc::shared> __out_alloc{policy.queue()}; + archetype_storage<__out_elem, decltype(__out_alloc)> out_storage(__out_alloc, 2 * archetype_test_size, + [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = dpl_ranges::set_union(std::forward(policy), view1, view2, out_view, + merge_comp_mut{}); + return std::ranges::begin(out_view)[7].val == 7 && + (std::size_t)(res.out - std::ranges::begin(out_view)) == archetype_test_size; + }, + [](auto&&, auto&&, auto res) { return res; }, "set_union, non-const comparator"); +#endif // !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HETERO + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HETERO + run_algo2_hetero_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + using __out_elem = typename std::ranges::range_value_t>::out_type; + sycl::usm_allocator<__out_elem, sycl::usm::alloc::shared> __out_alloc{policy.queue()}; + archetype_storage<__out_elem, decltype(__out_alloc)> out_storage(__out_alloc, 2 * archetype_test_size, + [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = dpl_ranges::set_difference(std::forward(policy), view1, view2, out_view, + merge_comp_mut{}); + return res.out == std::ranges::begin(out_view); + }, + [](auto&&, auto&&, auto res) { return res; }, "set_difference, non-const comparator"); +#endif // !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HETERO +#endif // TEST_DPCPP_BACKEND_PRESENT + +#endif //_ENABLE_STD_RANGES_TESTING + + return TestUtils::done(_ENABLE_STD_RANGES_TESTING); +} diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp index a9d77bb8ba0..25dedf6d755 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp @@ -1,191 +1,210 @@ -// -*- C++ -*- -//===----------------------------------------------------------------------===// -// -// Copyright (C) Intel Corporation -// -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -// This file incorporates work covered by the following copyright and permission -// notice: -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// -//===----------------------------------------------------------------------===// - -#include -#include - -#include "support/test_config.h" -#include "support/test_macros.h" -#include "support/utils.h" - -#if _ENABLE_STD_RANGES_TESTING -#include "std_ranges_archetypes.h" -#include "std_ranges_algo_archetypes_test.h" - -namespace test_std_ranges -{ -namespace dpl_ranges = oneapi::dpl::ranges; - -using seq_policy = decltype(oneapi::dpl::execution::seq); - -using permutable_view = archetypes::archetype_view; - -// The permuting algorithms are constrained by std::permutable> only, which requires -// the element to be movable, but not copyable, not default constructible and not comparable: any -// ordering or equality has to come from the comparator passed by the user. -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); - -// std::sortable == permutable && indirect_strict_weak_order<...>, so the very -// same element archetype works and the ordering never comes from an operator< on the element. -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); - -} //namespace test_std_ranges -#endif //_ENABLE_STD_RANGES_TESTING - -int -main() -{ -#if _ENABLE_STD_RANGES_TESTING - using namespace test_std_ranges; - using namespace test_std_ranges::archetypes; - namespace dpl_ranges = oneapi::dpl::ranges; - - // permutable_archetype is movable but not copyable, so it is not device copyable either: the - // host policies are the only ones its constraints allow. - run_algo_host_policies( - [](auto&& policy, auto&& view) { return dpl_ranges::reverse(std::forward(policy), view); }, - [](auto&& view, auto) { - const auto n = std::ranges::size(view); - return std::ranges::begin(view)[0].val == (int)n - 1 && std::ranges::begin(view)[n - 1].val == 0; - }, - "reverse"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { return dpl_ranges::reverse(std::forward(policy), view); }, - [](auto&& view, auto) { - const auto n = std::ranges::size(view); - return std::ranges::begin(view)[0].val == (int)n - 1 && std::ranges::begin(view)[n - 1].val == 0; - }, - "reverse"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // The storage is filled with 0, 1, 2, ... so every third element is removed. The returned - // subrange is the tail holding the removed elements. - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::remove_if(std::forward(policy), view, permutable_pred{}); - }, - [](auto&& view, auto res) { - const auto n = std::ranges::size(view); - return std::ranges::size(res) == (n + 2) / 3; - }, - "remove_if"); - - // The storage is filled with 0, 1, 2, ... so every third element is removed. The returned - // subrange is the tail holding the removed elements. -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::remove_if(std::forward(policy), view, permutable_pred{}); - }, - [](auto&& view, auto res) { - const auto n = std::ranges::size(view); - return std::ranges::size(res) == (n + 2) / 3; - }, - "remove_if"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // All the elements are unique, so nothing is dropped. - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::unique(std::forward(policy), view, permutable_equiv{}); - }, - [](auto&& view, auto res) { return std::ranges::size(res) == 0; }, "unique"); - -#if TEST_DPCPP_BACKEND_PRESENT - // All the elements are unique, so nothing is dropped. - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::unique(std::forward(policy), view, permutable_equiv{}); - }, - [](auto&& view, auto res) { return std::ranges::size(res) == 0; }, "unique"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // prpbably incorrect type applied - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::sort(std::forward(policy), view, permutable_comp{}); - }, - [](auto&& view, auto) { - return std::ranges::begin(view)[0].val == 0 && - std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; - }, - "sort"); - -#if TEST_DPCPP_BACKEND_PRESENT - // prpbably incorrect type applied - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::sort(std::forward(policy), view, permutable_comp{}); - }, - [](auto&& view, auto) { - return std::ranges::begin(view)[0].val == 0 && - std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; - }, - "sort"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // prpbably incorrect type applied - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::stable_sort(std::forward(policy), view, permutable_comp{}); - }, - [](auto&& view, auto) { - return std::ranges::begin(view)[0].val == 0 && - std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; - }, - "stable_sort"); - -#if TEST_DPCPP_BACKEND_PRESENT - // prpbably incorrect type applied - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::stable_sort(std::forward(policy), view, permutable_comp{}); - }, - [](auto&& view, auto) { - return std::ranges::begin(view)[0].val == 0 && - std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; - }, - "stable_sort"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::is_sorted(std::forward(policy), view, permutable_comp{}); - }, - [](auto&&, auto res) { return res; }, "is_sorted"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::is_sorted(std::forward(policy), view, permutable_comp{}); - }, - [](auto&&, auto res) { return res; }, "is_sorted"); -#endif // TEST_DPCPP_BACKEND_PRESENT - -#endif //_ENABLE_STD_RANGES_TESTING - - return TestUtils::done(_ENABLE_STD_RANGES_TESTING); -} +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#include +#include + +#include "support/test_config.h" +#include "support/test_macros.h" +#include "support/utils.h" + +#if _ENABLE_STD_RANGES_TESTING +#include "std_ranges_archetypes.h" +#include "std_ranges_algo_archetypes_test.h" + +namespace test_std_ranges +{ +namespace dpl_ranges = oneapi::dpl::ranges; + +using seq_policy = decltype(oneapi::dpl::execution::seq); + +using permutable_view = archetypes::archetype_view; + +// The permuting algorithms are constrained by std::permutable> only, which requires +// the element to be movable, but not copyable, not default constructible and not comparable: any +// ordering or equality has to come from the comparator passed by the user. +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); + +// std::sortable == permutable && indirect_strict_weak_order<...>, so the very +// same element archetype works and the ordering never comes from an operator< on the element. +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); + +} //namespace test_std_ranges +#endif //_ENABLE_STD_RANGES_TESTING + +int +main() +{ +#if _ENABLE_STD_RANGES_TESTING + using namespace test_std_ranges; + using namespace test_std_ranges::archetypes; + namespace dpl_ranges = oneapi::dpl::ranges; + + // permutable_archetype is movable but not copyable, so it is not device copyable either: the + // host policies are the only ones its constraints allow. + run_algo_host_policies( + [](auto&& policy, auto&& view) { return dpl_ranges::reverse(std::forward(policy), view); }, + [](auto&& view, auto) { + const auto n = std::ranges::size(view); + return std::ranges::begin(view)[0].val == (int)n - 1 && std::ranges::begin(view)[n - 1].val == 0; + }, + "reverse"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { return dpl_ranges::reverse(std::forward(policy), view); }, + [](auto&& view, auto) { + const auto n = std::ranges::size(view); + return std::ranges::begin(view)[0].val == (int)n - 1 && std::ranges::begin(view)[n - 1].val == 0; + }, + "reverse"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // The storage is filled with 0, 1, 2, ... so every third element is removed. The returned + // subrange is the tail holding the removed elements. + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::remove_if(std::forward(policy), view, permutable_pred{}); + }, + [](auto&& view, auto res) { + const auto n = std::ranges::size(view); + return std::ranges::size(res) == (n + 2) / 3; + }, + "remove_if"); + + // The storage is filled with 0, 1, 2, ... so every third element is removed. The returned + // subrange is the tail holding the removed elements. +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::remove_if(std::forward(policy), view, permutable_pred{}); + }, + [](auto&& view, auto res) { + const auto n = std::ranges::size(view); + return std::ranges::size(res) == (n + 2) / 3; + }, + "remove_if"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // All the elements are unique, so nothing is dropped. + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::unique(std::forward(policy), view, permutable_equiv{}); + }, + [](auto&& view, auto res) { return std::ranges::size(res) == 0; }, "unique"); + +#if TEST_DPCPP_BACKEND_PRESENT + // All the elements are unique, so nothing is dropped. + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::unique(std::forward(policy), view, permutable_equiv{}); + }, + [](auto&& view, auto res) { return std::ranges::size(res) == 0; }, "unique"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // partition returns the tail of the elements which do not satisfy the predicate. + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::partition(std::forward(policy), view, permutable_pred{}); + }, + [](auto&& view, auto res) { + return std::ranges::size(res) == std::ranges::size(view) - (std::ranges::size(view) + 2) / 3; + }, + "partition"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::partition(std::forward(policy), view, permutable_pred{}); + }, + [](auto&& view, auto res) { + return std::ranges::size(res) == std::ranges::size(view) - (std::ranges::size(view) + 2) / 3; + }, + "partition"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // The storage of the harness is filled in ascending order, so sorting it keeps it as it is: what + // these two cases check is that the call compiles and leaves the range intact, not the ordering. + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::sort(std::forward(policy), view, permutable_comp{}); + }, + [](auto&& view, auto) { + return std::ranges::begin(view)[0].val == 0 && + std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; + }, + "sort"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::sort(std::forward(policy), view, permutable_comp{}); + }, + [](auto&& view, auto) { + return std::ranges::begin(view)[0].val == 0 && + std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; + }, + "sort"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::stable_sort(std::forward(policy), view, permutable_comp{}); + }, + [](auto&& view, auto) { + return std::ranges::begin(view)[0].val == 0 && + std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; + }, + "stable_sort"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::stable_sort(std::forward(policy), view, permutable_comp{}); + }, + [](auto&& view, auto) { + return std::ranges::begin(view)[0].val == 0 && + std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; + }, + "stable_sort"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_sorted(std::forward(policy), view, permutable_comp{}); + }, + [](auto&&, auto res) { return res; }, "is_sorted"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_sorted(std::forward(policy), view, permutable_comp{}); + }, + [](auto&&, auto res) { return res; }, "is_sorted"); +#endif // TEST_DPCPP_BACKEND_PRESENT + +#endif //_ENABLE_STD_RANGES_TESTING + + return TestUtils::done(_ENABLE_STD_RANGES_TESTING); +} diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp index c5498fd0460..de784ee8407 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp @@ -1,356 +1,556 @@ -// -*- C++ -*- -//===----------------------------------------------------------------------===// -// -// Copyright (C) Intel Corporation -// -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -// This file incorporates work covered by the following copyright and permission -// notice: -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// -//===----------------------------------------------------------------------===// - -#include -#include - -#include "support/test_config.h" -#include "support/test_macros.h" -#include "support/utils.h" - -#if _ENABLE_STD_RANGES_TESTING -#include "std_ranges_archetypes.h" -#include "std_ranges_algo_archetypes_test.h" - -namespace test_std_ranges -{ -namespace dpl_ranges = oneapi::dpl::ranges; - -// Every algorithm below has to be callable with an archetype which satisfies exactly its declared -// constraints. A failure here means the implementation requires more from a user type than the -// requires-clause of the algorithm declares. -using read_view = archetypes::archetype_view; -using seq_policy = decltype(oneapi::dpl::execution::seq); - -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); - -// The projection is allowed to return a completely unrelated type, so the algorithm must never -// apply the predicate to the raw element. -static_assert(std::invocable); -static_assert(std::invocable); - -// The search value type of find/count/contains is unrelated to the element type. -using searchable_view = archetypes::archetype_view; - -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); - -// Two-range algorithms only require the predicate to accept the two projected references; the two -// element types stay unrelated and neither of them is comparable with itself. -using lhs_view = archetypes::archetype_view; -using rhs_view = archetypes::archetype_view; - -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); - -} //namespace test_std_ranges -#endif //_ENABLE_STD_RANGES_TESTING - -int -main() -{ -#if _ENABLE_STD_RANGES_TESTING - using namespace test_std_ranges; - using namespace test_std_ranges::archetypes; - namespace dpl_ranges = oneapi::dpl::ranges; - - // read_archetype is neither copyable, movable, default constructible nor comparable; the only - // operations available are the ones the callables of the algorithm provide. - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_if(std::forward(policy), view, read_unary_pred{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "find_if"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_if(std::forward(policy), view, read_unary_pred{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "find_if"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_if_not(std::forward(policy), view, read_unary_pred{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + 1; }, "find_if_not"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_if_not(std::forward(policy), view, read_unary_pred{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + 1; }, "find_if_not"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::any_of(std::forward(policy), view, read_unary_pred{}); - }, - [](auto&&, bool res) { return res; }, "any_of"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::any_of(std::forward(policy), view, read_unary_pred{}); - }, - [](auto&&, bool res) { return res; }, "any_of"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::all_of(std::forward(policy), view, read_unary_pred{}); - }, - [](auto&&, bool res) { return !res; }, "all_of"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::all_of(std::forward(policy), view, read_unary_pred{}); - }, - [](auto&&, bool res) { return !res; }, "all_of"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::count_if(std::forward(policy), view, read_unary_pred{}); - }, - [](auto&& view, auto res) { return res == (std::ranges::range_difference_t) - ((std::ranges::size(view) + 2) / 3); }, "count_if"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::count_if(std::forward(policy), view, read_unary_pred{}); - }, - [](auto&& view, auto res) { return res == (std::ranges::range_difference_t) - ((std::ranges::size(view) + 2) / 3); }, "count_if"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // The projection returns an unrelated prvalue type, so the predicate can only ever be applied to - // the projected value. - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::count_if(std::forward(policy), view, read_proj_pred{}, read_proj{}); - }, - [](auto&& view, auto res) { return res == (std::ranges::range_difference_t) - ((std::ranges::size(view) + 2) / 3); }, "count_if with proj"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::count_if(std::forward(policy), view, read_proj_pred{}, read_proj{}); - }, - [](auto&& view, auto res) { return res == (std::ranges::range_difference_t) - ((std::ranges::size(view) + 2) / 3); }, "count_if with proj"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // KSATODO: min_element/max_element/minmax_element only require std::indirect_strict_weak_order - // on the projected iterator, so the element type itself has to stay non-copyable and - // non-default-constructible. Both backends store the element by value instead of keeping an - // iterator to it, so the calls below do not compile: - // - unseq_backend_simd.h:635,637,649,662,663,666 - the _ComplexType helper of - // __simd_min_element holds a _ValueType member, value initializes it in its default - // constructor and copy assigns it while scanning; - // - algorithm_ranges_impl_hetero.h:1569 / utils_hetero.h:125 / tuple_impl.h:276 - the hetero - // path builds a std::pair and copies the element into it. - // Fixing this means carrying the index only and dereferencing the iterator for the comparison. -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MIN_ELEMENT_HOST - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::min_element(std::forward(policy), view, read_comp{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "min_element"); -#endif - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::min_element(std::forward(policy), view, read_comp{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "min_element"); -#endif // TEST_DPCPP_BACKEND_PRESENT - -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MAX_ELEMENT_HOST - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::max_element(std::forward(policy), view, read_comp{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view) - 1; }, - "max_element"); -#endif - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::max_element(std::forward(policy), view, read_comp{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view) - 1; }, - "max_element"); -#endif // TEST_DPCPP_BACKEND_PRESENT - -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_MINMAX_ELEMENT_HOST - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::minmax_element(std::forward(policy), view, read_comp{}); - }, - [](auto&& view, auto res) { - return res.min == std::ranges::begin(view) && - res.max == std::ranges::begin(view) + std::ranges::size(view) - 1; - }, - "minmax_element"); -#endif - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::minmax_element(std::forward(policy), view, read_comp{}); - }, - [](auto&& view, auto res) { - return res.min == std::ranges::begin(view) && - res.max == std::ranges::begin(view) + std::ranges::size(view) - 1; - }, - "minmax_element"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::is_sorted(std::forward(policy), view, read_comp{}); - }, - [](auto&&, bool res) { return res; }, "is_sorted"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::is_sorted(std::forward(policy), view, read_comp{}); - }, - [](auto&&, bool res) { return res; }, "is_sorted"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::adjacent_find(std::forward(policy), view, read_binary_pred{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, - "adjacent_find"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::adjacent_find(std::forward(policy), view, read_binary_pred{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, - "adjacent_find"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // The search value type is unrelated to the element type. - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find(std::forward(policy), view, search_value{7}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + 7; }, "find"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find(std::forward(policy), view, search_value{7}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + 7; }, "find"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::count(std::forward(policy), view, search_value{7}); - }, - [](auto&&, auto res) { return res == 1; }, "count"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::count(std::forward(policy), view, search_value{7}); - }, - [](auto&&, auto res) { return res == 1; }, "count"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // Two ranges of unrelated element types, compared only through the user predicate. - run_algo2_host_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::equal(std::forward(policy), view1, view2, cross_pred{}); - }, - [](auto&&, auto&&, bool res) { return res; }, "equal"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::equal(std::forward(policy), view1, view2, cross_pred{}); - }, - [](auto&&, auto&&, bool res) { return res; }, "equal"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo2_host_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::mismatch(std::forward(policy), view1, view2, cross_pred{}); - }, - [](auto&& view1, auto&& view2, auto res) { - return res.in1 == std::ranges::begin(view1) + std::ranges::size(view1) && - res.in2 == std::ranges::begin(view2) + std::ranges::size(view2); - }, - "mismatch"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::mismatch(std::forward(policy), view1, view2, cross_pred{}); - }, - [](auto&& view1, auto&& view2, auto res) { - return res.in1 == std::ranges::begin(view1) + std::ranges::size(view1) && - res.in2 == std::ranges::begin(view2) + std::ranges::size(view2); - }, - "mismatch"); -#endif // TEST_DPCPP_BACKEND_PRESENT - -#endif //_ENABLE_STD_RANGES_TESTING - - return TestUtils::done(_ENABLE_STD_RANGES_TESTING); -} +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#include +#include + +#include "support/test_config.h" +#include "support/test_macros.h" +#include "support/utils.h" + +#if _ENABLE_STD_RANGES_TESTING +#include "std_ranges_archetypes.h" +#include "std_ranges_algo_archetypes_test.h" + +namespace test_std_ranges +{ +namespace dpl_ranges = oneapi::dpl::ranges; + +// Every algorithm below has to be callable with an archetype which satisfies exactly its declared +// constraints. A failure here means the implementation requires more from a user type than the +// requires-clause of the algorithm declares. +using read_view = archetypes::archetype_view; +using seq_policy = decltype(oneapi::dpl::execution::seq); + +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); + +// The projection is allowed to return a completely unrelated type, so the algorithm must never +// apply the predicate to the raw element. +static_assert(std::invocable); +static_assert(std::invocable); + +// The search value type of find/count/contains is unrelated to the element type. +using searchable_view = archetypes::archetype_view; + +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); + +// Two-range algorithms only require the predicate to accept the two projected references; the two +// element types stay unrelated and neither of them is comparable with itself. +using lhs_view = archetypes::archetype_view; +using rhs_view = archetypes::archetype_view; + +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); + +} //namespace test_std_ranges +#endif //_ENABLE_STD_RANGES_TESTING + +int +main() +{ +#if _ENABLE_STD_RANGES_TESTING + using namespace test_std_ranges; + using namespace test_std_ranges::archetypes; + namespace dpl_ranges = oneapi::dpl::ranges; + + // read_archetype is neither copyable, movable, default constructible nor comparable; the only + // operations available are the ones the callables of the algorithm provide. + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::for_each(std::forward(policy), view, read_unary_fun{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, "for_each"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::for_each(std::forward(policy), view, read_unary_fun{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, "for_each"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_if(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "find_if"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_if(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "find_if"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_if_not(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + 1; }, "find_if_not"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_if_not(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + 1; }, "find_if_not"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // The last element whose value is divisible by three, and the last one whose value is not. + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_last_if(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&& view, auto res) { + auto __n = (int)std::ranges::size(view); + return std::ranges::begin(res) == std::ranges::begin(view) + (__n - 1) / 3 * 3; + }, + "find_last_if"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_last_if(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&& view, auto res) { + auto __n = (int)std::ranges::size(view); + return std::ranges::begin(res) == std::ranges::begin(view) + (__n - 1) / 3 * 3; + }, + "find_last_if"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_last_if_not(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&& view, auto res) { + auto __n = (int)std::ranges::size(view); + return std::ranges::begin(res) == std::ranges::begin(view) + ((__n - 1) % 3 == 0 ? __n - 2 : __n - 1); + }, + "find_last_if_not"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_last_if_not(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&& view, auto res) { + auto __n = (int)std::ranges::size(view); + return std::ranges::begin(res) == std::ranges::begin(view) + ((__n - 1) % 3 == 0 ? __n - 2 : __n - 1); + }, + "find_last_if_not"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::any_of(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&&, bool res) { return res; }, "any_of"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::any_of(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&&, bool res) { return res; }, "any_of"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::all_of(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&&, bool res) { return !res; }, "all_of"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::all_of(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&&, bool res) { return !res; }, "all_of"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::none_of(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&&, bool res) { return !res; }, "none_of"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::none_of(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&&, bool res) { return !res; }, "none_of"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // The predicate holds for 0, fails for 1 and holds again for 3, so the range is not partitioned. + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_partitioned(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&&, bool res) { return !res; }, "is_partitioned"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_partitioned(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&&, bool res) { return !res; }, "is_partitioned"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count_if(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&& view, auto res) { return res == (std::ranges::range_difference_t) + ((std::ranges::size(view) + 2) / 3); }, "count_if"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count_if(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&& view, auto res) { return res == (std::ranges::range_difference_t) + ((std::ranges::size(view) + 2) / 3); }, "count_if"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // The projection returns an unrelated prvalue type, so the predicate can only ever be applied to + // the projected value. + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_if(std::forward(policy), view, read_proj_pred{}, read_proj{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "find_if with proj"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_if(std::forward(policy), view, read_proj_pred{}, read_proj{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "find_if with proj"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count_if(std::forward(policy), view, read_proj_pred{}, read_proj{}); + }, + [](auto&& view, auto res) { return res == (std::ranges::range_difference_t) + ((std::ranges::size(view) + 2) / 3); }, "count_if with proj"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count_if(std::forward(policy), view, read_proj_pred{}, read_proj{}); + }, + [](auto&& view, auto res) { return res == (std::ranges::range_difference_t) + ((std::ranges::size(view) + 2) / 3); }, "count_if with proj"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // min_element/max_element/minmax_element only require std::indirect_strict_weak_order on the + // projected iterator, so the element type stays non-copyable and non-default-constructible: both + // backends carry an index and dereference the iterator for the comparison instead of storing the + // element by value. + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::min_element(std::forward(policy), view, read_comp{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "min_element"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::min_element(std::forward(policy), view, read_comp{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "min_element"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::max_element(std::forward(policy), view, read_comp{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view) - 1; }, + "max_element"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::max_element(std::forward(policy), view, read_comp{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view) - 1; }, + "max_element"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::minmax_element(std::forward(policy), view, read_comp{}); + }, + [](auto&& view, auto res) { + return res.min == std::ranges::begin(view) && + res.max == std::ranges::begin(view) + std::ranges::size(view) - 1; + }, + "minmax_element"); + + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::minmax_element(std::forward(policy), view, read_comp{}); + }, + [](auto&& view, auto res) { + return res.min == std::ranges::begin(view) && + res.max == std::ranges::begin(view) + std::ranges::size(view) - 1; + }, + "minmax_element"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_sorted(std::forward(policy), view, read_comp{}); + }, + [](auto&&, bool res) { return res; }, "is_sorted"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_sorted(std::forward(policy), view, read_comp{}); + }, + [](auto&&, bool res) { return res; }, "is_sorted"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // The whole range is sorted, so the scan stops at its end. + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_sorted_until(std::forward(policy), view, read_comp{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, + "is_sorted_until"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_sorted_until(std::forward(policy), view, read_comp{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, + "is_sorted_until"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::adjacent_find(std::forward(policy), view, read_binary_pred{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, + "adjacent_find"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::adjacent_find(std::forward(policy), view, read_binary_pred{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, + "adjacent_find"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // The search value type is unrelated to the element type. + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find(std::forward(policy), view, search_value{7}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + 7; }, "find"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find(std::forward(policy), view, search_value{7}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + 7; }, "find"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // find_last returns the tail of the range starting at the last occurrence of the value. + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_last(std::forward(policy), view, search_value{7}); + }, + [](auto&& view, auto res) { + return std::ranges::begin(res) == std::ranges::begin(view) + 7 && + std::ranges::size(res) == (std::ranges::range_difference_t)std::ranges::size(view) - 7; + }, + "find_last"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_last(std::forward(policy), view, search_value{7}); + }, + [](auto&& view, auto res) { + return std::ranges::begin(res) == std::ranges::begin(view) + 7 && + std::ranges::size(res) == (std::ranges::range_difference_t)std::ranges::size(view) - 7; + }, + "find_last"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count(std::forward(policy), view, search_value{7}); + }, + [](auto&&, auto res) { return res == 1; }, "count"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count(std::forward(policy), view, search_value{7}); + }, + [](auto&&, auto res) { return res == 1; }, "count"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // Two ranges of unrelated element types, compared only through the user predicate. + run_algo2_host_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::equal(std::forward(policy), view1, view2, cross_pred{}); + }, + [](auto&&, auto&&, bool res) { return res; }, "equal"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::equal(std::forward(policy), view1, view2, cross_pred{}); + }, + [](auto&&, auto&&, bool res) { return res; }, "equal"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo2_host_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::mismatch(std::forward(policy), view1, view2, cross_pred{}); + }, + [](auto&& view1, auto&& view2, auto res) { + return res.in1 == std::ranges::begin(view1) + std::ranges::size(view1) && + res.in2 == std::ranges::begin(view2) + std::ranges::size(view2); + }, + "mismatch"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::mismatch(std::forward(policy), view1, view2, cross_pred{}); + }, + [](auto&& view1, auto&& view2, auto res) { + return res.in1 == std::ranges::begin(view1) + std::ranges::size(view1) && + res.in2 == std::ranges::begin(view2) + std::ranges::size(view2); + }, + "mismatch"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // The two ranges hold the very same sequence, so the second one occurs in the first one exactly + // once, at its very beginning. + run_algo2_host_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::search(std::forward(policy), view1, view2, cross_pred{}); + }, + [](auto&& view1, auto&& view2, auto res) { + return std::ranges::begin(res) == std::ranges::begin(view1) && + std::ranges::size(res) == std::ranges::size(view2); + }, + "search"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::search(std::forward(policy), view1, view2, cross_pred{}); + }, + [](auto&& view1, auto&& view2, auto res) { + return std::ranges::begin(res) == std::ranges::begin(view1) && + std::ranges::size(res) == std::ranges::size(view2); + }, + "search"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo2_host_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::find_end(std::forward(policy), view1, view2, cross_pred{}); + }, + [](auto&& view1, auto&& view2, auto res) { + return std::ranges::begin(res) == std::ranges::begin(view1) && + std::ranges::size(res) == std::ranges::size(view2); + }, + "find_end"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::find_end(std::forward(policy), view1, view2, cross_pred{}); + }, + [](auto&& view1, auto&& view2, auto res) { + return std::ranges::begin(res) == std::ranges::begin(view1) && + std::ranges::size(res) == std::ranges::size(view2); + }, + "find_end"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // KSATODO: std::indirectly_comparable<_It1, _It2, _Pred> only requires the predicate to be + // invocable as __pred(*__it1, *__it2), never the other way round. The SIMD brick swaps the two + // arguments, so the vectorized host policies unseq and par_unseq do not compile: + // - unseq_backend_simd.h:827 - __simd_find_first_of builds __u_pred as + // __pred(__val, *__first) with __val taken from the second range and *__first from the first + // one; the branch is a plain if, so it is instantiated whatever the sizes of the ranges are. + // Fixing this means keeping the argument order of the two ranges in both branches. + auto find_first_of_algo = [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::find_first_of(std::forward(policy), view1, view2, cross_pred{}); + }; + auto find_first_of_checker = [](auto&& view1, auto&&, auto res) { return res == std::ranges::begin(view1); }; + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_FIRST_OF_HOST_VEC + run_algo2_host_policies(find_first_of_algo, find_first_of_checker, "find_first_of"); +#else + run_algo2_novec_policies(find_first_of_algo, find_first_of_checker, "find_first_of"); +#endif + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies(find_first_of_algo, find_first_of_checker, + "find_first_of"); +#endif // TEST_DPCPP_BACKEND_PRESENT + +#endif //_ENABLE_STD_RANGES_TESTING + + return TestUtils::done(_ENABLE_STD_RANGES_TESTING); +} diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h b/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h index 055c8eb0030..aaeaa902a9b 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h @@ -86,6 +86,31 @@ run_algo_host_policies(_Algo __algo, _Checker __checker, const char* __algo_name run_algo<_Elem>(__alloc, oneapi::dpl::execution::par_unseq, __algo, __checker, __algo_name); } +// Runs a one-range algorithm with the sequential host policies only. Used where the parallel host +// policies are known to require more from a user type than the algorithm declares, so that the +// coverage of seq and unseq is kept instead of switching the whole case off. +template +void +run_algo_seq_policies(_Algo __algo, _Checker __checker, const char* __algo_name) +{ + std::allocator<_Elem> __alloc; + run_algo<_Elem>(__alloc, oneapi::dpl::execution::seq, __algo, __checker, __algo_name); + run_algo<_Elem>(__alloc, oneapi::dpl::execution::unseq, __algo, __checker, __algo_name); +} + +// Runs a two-range algorithm with the non vectorized host policies only. Used where the SIMD brick +// of a two-range algorithm requires more from a user callable than the algorithm declares, so that +// the coverage of seq and par is kept instead of switching the whole case off. +template +void +run_algo2_novec_policies(_Algo __algo, _Checker __checker, const char* __algo_name) +{ + std::allocator<_Elem1> __alloc1; + std::allocator<_Elem2> __alloc2; + run_algo2<_Elem1, _Elem2>(__alloc1, __alloc2, oneapi::dpl::execution::seq, __algo, __checker, __algo_name); + run_algo2<_Elem1, _Elem2>(__alloc1, __alloc2, oneapi::dpl::execution::par, __algo, __checker, __algo_name); +} + // Runs a two-range algorithm with the host policies only, see run_algo_host_policies. template void diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp index 5d040621ba4..22b8825c440 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp @@ -1,247 +1,249 @@ -// -*- C++ -*- -//===----------------------------------------------------------------------===// -// -// Copyright (C) Intel Corporation -// -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -// This file incorporates work covered by the following copyright and permission -// notice: -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// -//===----------------------------------------------------------------------===// - -#include -#include - -#include "support/test_config.h" -#include "support/test_macros.h" -#include "support/utils.h" - -#if _ENABLE_STD_RANGES_TESTING -#include "std_ranges_archetypes.h" -#include "std_ranges_algo_archetypes_test.h" - -namespace test_std_ranges -{ -namespace dpl_ranges = oneapi::dpl::ranges; - -// The value based algorithms are constrained by -// std::indirect_binary_predicate, _Proj>, -// const _T*> -// only. In particular the value type is not required to be copyable, to be comparable with itself -// with anything but std::ranges::equal_to, or to be related to the element type in any other way, -// and the element type is not required to be comparable with itself either. -using searchable_view = archetypes::archetype_view; -using removable_view = archetypes::archetype_view; -using seq_policy = decltype(oneapi::dpl::execution::seq); - -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); - -// The device copyable counterpart of the value satisfies the very same constraints, and it really is -// accepted by SYCL without an explicit sycl::is_device_copyable specialization. -using searchable_dc_view = archetypes::archetype_view; -using removable_dc_view = archetypes::archetype_view; - -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); - -} //namespace test_std_ranges -#endif //_ENABLE_STD_RANGES_TESTING - -int -main() -{ -#if _ENABLE_STD_RANGES_TESTING - using namespace test_std_ranges; - using namespace test_std_ranges::archetypes; - namespace dpl_ranges = oneapi::dpl::ranges; - - // The storage is filled with the values 0, 1, 2, ... so the value 3 is found exactly once. - constexpr int searched = 3; - - // search_value is trivially copyable and thus device copyable, so it can be used with all the - // policies including the device ones. - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find(std::forward(policy), view, search_value{searched}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find(std::forward(policy), view, search_value{searched}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_last(std::forward(policy), view, search_value{searched}); - }, - [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, - "find_last"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_last(std::forward(policy), view, search_value{searched}); - }, - [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, - "find_last"); -#endif - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::count(std::forward(policy), view, search_value{searched}); - }, - [](auto&&, auto res) { return res == 1; }, "count"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::count(std::forward(policy), view, search_value{searched}); - }, - [](auto&&, auto res) { return res == 1; }, "count"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::contains(std::forward(policy), view, search_value{searched}); - }, - [](auto&&, auto res) { return res; }, "contains"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::contains(std::forward(policy), view, search_value{searched}); - }, - [](auto&&, auto res) { return res; }, "contains"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // removable_archetype is movable but not device copyable, so remove() is checked on the host - // policies only. - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::remove(std::forward(policy), view, search_value{searched}); - }, - [](auto&& view, auto res) { return std::ranges::size(res) == std::ranges::size(view) - 1; }, "remove"); - -#if TEST_DPCPP_BACKEND_PRESENT - // removable_archetype is movable but not device copyable, so remove() is checked on the host - // policies only. - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::remove(std::forward(policy), view, search_value{searched}); - }, - [](auto&& view, auto res) { return std::ranges::size(res) == std::ranges::size(view) - 1; }, "remove"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // nocopy_search_value is neither copyable nor movable: the host implementations must refer to - // the value passed by the user instead of storing a copy of it. It cannot be captured by a - // device kernel, hence the host policies only. - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find(std::forward(policy), view, nocopy_search_value{searched}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find, noncopyable value"); - -#if TEST_DPCPP_BACKEND_PRESENT - // A device policy copies the value into the kernel, so the hetero runs use the device copyable - // counterpart of the value: it is still neither default constructible nor ordered. - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find(std::forward(policy), view, nocopy_search_value_dc{searched}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find, noncopyable value"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_last(std::forward(policy), view, nocopy_search_value{searched}); - }, - [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, - "find_last, noncopyable value"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_last(std::forward(policy), view, - nocopy_search_value_dc{searched}); - }, - [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, - "find_last, noncopyable value"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // count() must refer to the value instead of storing a copy of it: the requires-clause never - // asks for a copyable value type. - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::count(std::forward(policy), view, nocopy_search_value{searched}); - }, - [](auto&&, auto res) { return res == 1; }, "count, noncopyable value"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::count(std::forward(policy), view, nocopy_search_value_dc{searched}); - }, - [](auto&&, auto res) { return res == 1; }, "count, noncopyable value"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::contains(std::forward(policy), view, nocopy_search_value{searched}); - }, - [](auto&&, auto res) { return res; }, "contains, noncopyable value"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::contains(std::forward(policy), view, nocopy_search_value_dc{searched}); - }, - [](auto&&, auto res) { return res; }, "contains, noncopyable value"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // Same for remove(): the predicate it builds internally must hold a reference to the value for - // the host policies. - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::remove(std::forward(policy), view, nocopy_search_value{searched}); - }, - [](auto&& view, auto res) { return std::ranges::size(res) == std::ranges::size(view) - 1; }, - "remove, noncopyable value"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::remove(std::forward(policy), view, nocopy_search_value_dc{searched}); - }, - [](auto&& view, auto res) { return std::ranges::size(res) == std::ranges::size(view) - 1; }, - "remove, noncopyable value"); -#endif // TEST_DPCPP_BACKEND_PRESENT - -#endif //_ENABLE_STD_RANGES_TESTING - - return TestUtils::done(_ENABLE_STD_RANGES_TESTING); -} +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#include +#include + +#include "support/test_config.h" +#include "support/test_macros.h" +#include "support/utils.h" + +#if _ENABLE_STD_RANGES_TESTING +#include "std_ranges_archetypes.h" +#include "std_ranges_algo_archetypes_test.h" + +namespace test_std_ranges +{ +namespace dpl_ranges = oneapi::dpl::ranges; + +// The value based algorithms are constrained by +// std::indirect_binary_predicate, _Proj>, +// const _T*> +// only. In particular the value type is not required to be copyable, to be comparable with itself +// with anything but std::ranges::equal_to, or to be related to the element type in any other way, +// and the element type is not required to be comparable with itself either. +using searchable_view = archetypes::archetype_view; +using removable_view = archetypes::archetype_view; +using seq_policy = decltype(oneapi::dpl::execution::seq); + +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); + +// The device copyable counterpart of the value satisfies the very same constraints, and it really is +// accepted by SYCL without an explicit sycl::is_device_copyable specialization. +using searchable_dc_view = archetypes::archetype_view; +using removable_dc_view = archetypes::archetype_view; + +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); +static_assert(std::invocable); + +} //namespace test_std_ranges +#endif //_ENABLE_STD_RANGES_TESTING + +int +main() +{ +#if _ENABLE_STD_RANGES_TESTING + using namespace test_std_ranges; + using namespace test_std_ranges::archetypes; + namespace dpl_ranges = oneapi::dpl::ranges; + + // The storage is filled with the values 0, 1, 2, ... so the value 3 is found exactly once. + constexpr int searched = 3; + + // search_value is trivially copyable and thus device copyable, so it can be used with all the + // policies including the device ones. + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find(std::forward(policy), view, search_value{searched}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find(std::forward(policy), view, search_value{searched}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_last(std::forward(policy), view, search_value{searched}); + }, + [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, + "find_last"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_last(std::forward(policy), view, search_value{searched}); + }, + [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, + "find_last"); +#endif + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count(std::forward(policy), view, search_value{searched}); + }, + [](auto&&, auto res) { return res == 1; }, "count"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count(std::forward(policy), view, search_value{searched}); + }, + [](auto&&, auto res) { return res == 1; }, "count"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::contains(std::forward(policy), view, search_value{searched}); + }, + [](auto&&, auto res) { return res; }, "contains"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::contains(std::forward(policy), view, search_value{searched}); + }, + [](auto&&, auto res) { return res; }, "contains"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // removable_archetype is movable but not device copyable, so remove() is checked on the host + // policies only. + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::remove(std::forward(policy), view, search_value{searched}); + }, + // remove() returns the tail holding the removed elements, and the value occurs exactly once. + [](auto&&, auto res) { return std::ranges::size(res) == 1; }, "remove"); + +#if TEST_DPCPP_BACKEND_PRESENT + // removable_archetype is movable but not device copyable, so remove() is checked on the host + // policies only. + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::remove(std::forward(policy), view, search_value{searched}); + }, + // remove() returns the tail holding the removed elements, and the value occurs exactly once. + [](auto&&, auto res) { return std::ranges::size(res) == 1; }, "remove"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // nocopy_search_value is neither copyable nor movable: the host implementations must refer to + // the value passed by the user instead of storing a copy of it. It cannot be captured by a + // device kernel, hence the host policies only. + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find(std::forward(policy), view, nocopy_search_value{searched}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find, noncopyable value"); + +#if TEST_DPCPP_BACKEND_PRESENT + // A device policy copies the value into the kernel, so the hetero runs use the device copyable + // counterpart of the value: it is still neither default constructible nor ordered. + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find(std::forward(policy), view, nocopy_search_value_dc{searched}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find, noncopyable value"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_last(std::forward(policy), view, nocopy_search_value{searched}); + }, + [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, + "find_last, noncopyable value"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_last(std::forward(policy), view, + nocopy_search_value_dc{searched}); + }, + [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, + "find_last, noncopyable value"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // count() must refer to the value instead of storing a copy of it: the requires-clause never + // asks for a copyable value type. + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count(std::forward(policy), view, nocopy_search_value{searched}); + }, + [](auto&&, auto res) { return res == 1; }, "count, noncopyable value"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count(std::forward(policy), view, nocopy_search_value_dc{searched}); + }, + [](auto&&, auto res) { return res == 1; }, "count, noncopyable value"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::contains(std::forward(policy), view, nocopy_search_value{searched}); + }, + [](auto&&, auto res) { return res; }, "contains, noncopyable value"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::contains(std::forward(policy), view, nocopy_search_value_dc{searched}); + }, + [](auto&&, auto res) { return res; }, "contains, noncopyable value"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // Same for remove(): the predicate it builds internally must hold a reference to the value for + // the host policies. + run_algo_host_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::remove(std::forward(policy), view, nocopy_search_value{searched}); + }, + // remove() returns the tail holding the removed elements, and the value occurs exactly once. + [](auto&&, auto res) { return std::ranges::size(res) == 1; }, "remove, noncopyable value"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_hetero_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::remove(std::forward(policy), view, nocopy_search_value_dc{searched}); + }, + // remove() returns the tail holding the removed elements, and the value occurs exactly once. + [](auto&&, auto res) { return std::ranges::size(res) == 1; }, "remove, noncopyable value"); +#endif // TEST_DPCPP_BACKEND_PRESENT + +#endif //_ENABLE_STD_RANGES_TESTING + + return TestUtils::done(_ENABLE_STD_RANGES_TESTING); +} diff --git a/test/parallel_api/ranges/std_ranges_archetypes.h b/test/parallel_api/ranges/std_ranges_archetypes.h index 013ca390a9a..9bd521a2cb2 100644 --- a/test/parallel_api/ranges/std_ranges_archetypes.h +++ b/test/parallel_api/ranges/std_ranges_archetypes.h @@ -1548,6 +1548,156 @@ static_assert(!std::totally_ordered); static_assert(std::indirectly_copyable_storable>, storable_archetype_dc*>); +//------------------------------------------------------------------------------------------------ +// Callables taking their arguments by non-const reference. +// +// std::indirectly_unary_invocable, std::indirect_unary_predicate, std::indirect_binary_predicate, +// std::indirect_strict_weak_order and std::projected are all spelled in terms of iter_value_t<_It>&, +// iter_reference_t<_It> and iter_common_reference_t<_It>. For archetype_view<_T> all three of them +// are _T&, i.e. a non-const lvalue reference, so a callable which accepts nothing but _T& satisfies +// those concepts. The requires-clauses of the algorithms therefore allow such a callable, and an +// implementation which hands a const lvalue, an rvalue or a copy of the element to the user callable +// does not compile with the types below. +// +// The _mut counterparts only add the non-const parameter list; the element archetypes and the +// expected results stay exactly the ones of the corresponding family above. +//------------------------------------------------------------------------------------------------ + +// Family 1: read-only algorithms parameterized by a callable. +struct read_unary_fun_mut +{ + void operator()(read_archetype&) const {} + void operator()(read_archetype_dc&) const {} +}; + +struct read_unary_pred_mut +{ + bool operator()(read_archetype& __v) const { return __v.val % 3 == 0; } + bool operator()(read_archetype_dc& __v) const { return __v.val % 3 == 0; } +}; + +struct read_binary_pred_mut +{ + bool operator()(read_archetype& __v1, read_archetype& __v2) const { return __v1.val == __v2.val; } + bool operator()(read_archetype_dc& __v1, read_archetype_dc& __v2) const { return __v1.val == __v2.val; } +}; + +struct read_comp_mut +{ + bool operator()(read_archetype& __v1, read_archetype& __v2) const { return __v1.val < __v2.val; } + bool operator()(read_archetype_dc& __v1, read_archetype_dc& __v2) const { return __v1.val < __v2.val; } +}; + +// A projection taking the element by non-const reference. Its result is a prvalue of an unrelated +// type, which the pre-existing read_proj_pred consumes: a predicate over a projection cannot take a +// non-const reference itself, because indirect_unary_predicate also requires it to be invocable with +// iter_reference_t of the projected iterator, which is that prvalue. +struct read_proj_mut +{ + read_proj_result operator()(read_archetype& __v) const { return read_proj_result{__v.val}; } + read_proj_result operator()(read_archetype_dc& __v) const { return read_proj_result{__v.val}; } +}; + +static_assert(std::indirectly_unary_invocable); +static_assert(std::indirect_unary_predicate); +static_assert(std::indirect_binary_predicate); +static_assert(std::indirect_strict_weak_order); +static_assert(std::indirect_unary_predicate>); +// The callables really do reject anything but a non-const lvalue of the element type. +static_assert(!std::invocable); +static_assert(!std::invocable); +static_assert(!std::invocable); +static_assert(!std::invocable); + +// Family 2: algorithms taking a search value. The value itself is compared with +// std::ranges::equal_to, so only the projection is a user callable here. The projection returns the +// element by reference, which keeps the equality with the search value as it is in the family above. +struct search_proj_mut +{ + searchable_archetype& operator()(searchable_archetype& __v) const { return __v; } + searchable_archetype_dc& operator()(searchable_archetype_dc& __v) const { return __v; } + removable_archetype& operator()(removable_archetype& __v) const { return __v; } + removable_archetype_dc& operator()(removable_archetype_dc& __v) const { return __v; } +}; + +static_assert(std::indirect_binary_predicate, + const search_value*>); +static_assert(std::indirect_binary_predicate, + const search_value*>); +static_assert(!std::invocable); + +// Family 3: two-range algorithms constrained by std::indirectly_comparable. Both references are +// non-const lvalues, so the predicate may take both of its arguments that way. +struct cross_pred_mut +{ + bool operator()(lhs_archetype& __v1, rhs_archetype& __v2) const { return __v1.val == __v2.val; } + bool operator()(lhs_archetype_dc& __v1, rhs_archetype_dc& __v2) const { return __v1.val == __v2.val; } +}; + +static_assert(std::indirectly_comparable); +static_assert(!std::invocable); + +// Family 8: transform. The functor is only required to be std::copy_constructible and invocable with +// the projected reference, which is a non-const lvalue. +struct transform_unary_op_mut +{ + transform_result operator()(transform_in_archetype& __v) const { return transform_result{__v.val * 2}; } + transform_result operator()(transform_in_archetype_dc& __v) const { return transform_result{__v.val * 2}; } +}; + +static_assert(std::indirectly_writable>); +static_assert(!std::invocable); + +// Family 9: permuting and sorting algorithms. The element is mutable by definition here, so the +// predicate and the comparator may take it by non-const reference as well. +struct permutable_pred_mut +{ + bool operator()(permutable_archetype& __v) const { return __v.val % 3 == 0; } + bool operator()(permutable_archetype_dc& __v) const { return __v.val % 3 == 0; } +}; + +struct permutable_equiv_mut +{ + bool operator()(permutable_archetype& __v1, permutable_archetype& __v2) const { return __v1.val == __v2.val; } + bool operator()(permutable_archetype_dc& __v1, permutable_archetype_dc& __v2) const + { + return __v1.val == __v2.val; + } +}; + +struct permutable_comp_mut +{ + bool operator()(permutable_archetype& __v1, permutable_archetype& __v2) const { return __v1.val < __v2.val; } + bool operator()(permutable_archetype_dc& __v1, permutable_archetype_dc& __v2) const { return __v1.val < __v2.val; } +}; + +static_assert(std::indirect_unary_predicate); +static_assert(std::indirect_binary_predicate); +static_assert(std::sortable); +static_assert(std::sortable); +static_assert(!std::invocable); + +// The merge family and min / max / minmax, whose comparators are constrained the very same way. +struct merge_comp_mut +{ + bool operator()(merge_in_archetype& __v1, merge_in_archetype& __v2) const { return __v1.val < __v2.val; } + bool operator()(merge_in_archetype_dc& __v1, merge_in_archetype_dc& __v2) const { return __v1.val < __v2.val; } +}; + +struct storable_comp_mut +{ + bool operator()(storable_archetype& __v1, storable_archetype& __v2) const { return __v1.val < __v2.val; } + bool operator()(storable_archetype_dc& __v1, storable_archetype_dc& __v2) const { return __v1.val < __v2.val; } +}; + +static_assert(std::mergeable); +static_assert(std::indirect_strict_weak_order); +static_assert(!std::invocable); +static_assert(!std::invocable); + } // namespace archetypes } // namespace test_std_ranges diff --git a/test/support/test_config.h b/test/support/test_config.h index 3bef8fb23b1..ea7be00a1d2 100644 --- a/test/support/test_config.h +++ b/test/support/test_config.h @@ -368,5 +368,31 @@ #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HETERO 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HOST 1 #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HETERO 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_INTERSECTION_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_INTERSECTION_HETERO 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_SYMMETRIC_DIFFERENCE_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_SYMMETRIC_DIFFERENCE_HETERO 1 + +// The parallel host policies of sort, stable_sort, partial_sort and nth_element hand a const lvalue +// to the comparator, which std::sortable never asks for; seq and unseq are fine. +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT_HOST_PAR 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT_HOST_PAR 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_PARTIAL_SORT_HOST_PAR 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_NTH_ELEMENT_HOST_PAR 1 + +// The SIMD brick of find_first_of calls the predicate with its two arguments swapped, which +// std::indirectly_comparable does not ask for: the vectorized host policies unseq and par_unseq are +// broken, seq and par are fine. +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_FIRST_OF_HOST_VEC 1 + +// inplace_merge is broken for every host policy, see the note at its call site. +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_INPLACE_MERGE_HOST 1 + +// The device path of these four algorithms hands a const lvalue (or a const copy) of the element to +// the user callable, which none of their requires-clauses asks for. See the notes at the call sites. +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_IS_PARTITIONED_HETERO 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_FIRST_OF_HETERO 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_PARTITION_HETERO 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_INPLACE_MERGE_HETERO 1 #endif // _TEST_CONFIG_H From a3ba6a27c34c05185b8d97881710eba0fc0ca0fd Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Wed, 9 Sep 2026 11:37:18 +0200 Subject: [PATCH 073/148] Remove redundant std::invocable checks from the archetype tests Every combination these static_asserts covered is now exercised by a real algorithm call, and a call is strictly stronger: it checks the same requires-clause (otherwise the overload would not be found) and additionally instantiates the implementation, which is the only way an extra requirement of the implementation shows up as a compile error. The requires-clause conformance of the archetypes themselves stays asserted in std_ranges_archetypes.h, on the concepts the algorithms are declared with (indirectly_unary_invocable, indirect_unary_predicate, indirect_binary_predicate, indirect_strict_weak_order, indirectly_comparable, indirectly_writable, indirectly_copyable, indirectly_movable, indirectly_swappable, permutable, sortable, mergeable, indirectly_copyable_storable) and on archetype_view being a random_access_range and a sized_range. That is the better place for them: those assertions are independent of the algorithm, so they keep covering the cases whose call is currently switched off by a _TEST_CPP20_RANGES_BROKEN_REQUIRES_* macro, which the per-algorithm checks could not. Beyond being redundant, the removed checks were also incomplete - they only ever named oneapi::dpl::execution::seq, never unseq, par, par_unseq or a device policy - and five of them in the value test asked for a device copyable archetype with a host policy, a combination nothing calls that way. The comments explaining what each archetype family deliberately does not provide are kept at the top of every test. Co-Authored-By: Claude Opus 5 --- .../std_ranges_algo_archetypes_merge.pass.cpp | 35 ++-------- ...td_ranges_algo_archetypes_permute.pass.cpp | 31 ++------- .../std_ranges_algo_archetypes_read.pass.cpp | 69 +++---------------- .../std_ranges_algo_archetypes_value.pass.cpp | 43 ++---------- .../std_ranges_algo_archetypes_write.pass.cpp | 46 +++---------- .../std_ranges_memory_archetypes.pass.cpp | 11 --- 6 files changed, 38 insertions(+), 197 deletions(-) diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp index d404275e39f..1fd472a7d79 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp @@ -24,38 +24,15 @@ #include "std_ranges_archetypes.h" #include "std_ranges_algo_archetypes_test.h" -namespace test_std_ranges -{ -namespace dpl_ranges = oneapi::dpl::ranges; - -using seq_policy = decltype(oneapi::dpl::execution::seq); - -using merge_in_view = archetypes::archetype_view; -using merge_out_view = archetypes::archetype_view; -using storable_view = archetypes::archetype_view; - // The merge family is constrained by std::mergeable, which asks for indirectly_copyable from both // inputs into the output plus a strict weak order: the output element stays non-copyable itself and -// the ordering never comes from an operator< on the element. -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); - -// min / max / minmax additionally require std::indirectly_copyable_storable, -// range_value_t<_R>*>, which does need a copy constructor and copy assignment, but still no default -// constructor and no ordering operator on the element. -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); +// the ordering never comes from an operator< on the element. min / max / minmax instead require +// std::indirectly_copyable_storable, range_value_t<_R>*>, which does need a copy +// constructor and copy assignment, but still no default constructor and no ordering operator on the +// element. Both requires-clauses are asserted on the archetypes themselves in +// std_ranges_archetypes.h; what the calls below add is the instantiation of the implementation, +// which is where an extra requirement shows up as a compile error. -} //namespace test_std_ranges #endif //_ENABLE_STD_RANGES_TESTING int diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp index 25dedf6d755..ee05d10d30f 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp @@ -24,33 +24,14 @@ #include "std_ranges_archetypes.h" #include "std_ranges_algo_archetypes_test.h" -namespace test_std_ranges -{ -namespace dpl_ranges = oneapi::dpl::ranges; - -using seq_policy = decltype(oneapi::dpl::execution::seq); - -using permutable_view = archetypes::archetype_view; - // The permuting algorithms are constrained by std::permutable> only, which requires // the element to be movable, but not copyable, not default constructible and not comparable: any -// ordering or equality has to come from the comparator passed by the user. -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); - -// std::sortable == permutable && indirect_strict_weak_order<...>, so the very -// same element archetype works and the ordering never comes from an operator< on the element. -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); - -} //namespace test_std_ranges +// ordering or equality has to come from the comparator passed by the user. std::sortable == permutable && indirect_strict_weak_order<...>, so the very same element archetype +// works for the sorting algorithms as well. Those requirements are asserted on the archetypes +// themselves in std_ranges_archetypes.h; what the calls below add is the instantiation of the +// implementation, which is where an extra requirement shows up as a compile error. + #endif //_ENABLE_STD_RANGES_TESTING int diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp index de784ee8407..9771cba5792 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp @@ -24,66 +24,15 @@ #include "std_ranges_archetypes.h" #include "std_ranges_algo_archetypes_test.h" -namespace test_std_ranges -{ -namespace dpl_ranges = oneapi::dpl::ranges; - -// Every algorithm below has to be callable with an archetype which satisfies exactly its declared -// constraints. A failure here means the implementation requires more from a user type than the -// requires-clause of the algorithm declares. -using read_view = archetypes::archetype_view; -using seq_policy = decltype(oneapi::dpl::execution::seq); - -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); - -// The projection is allowed to return a completely unrelated type, so the algorithm must never -// apply the predicate to the raw element. -static_assert(std::invocable); -static_assert(std::invocable); - -// The search value type of find/count/contains is unrelated to the element type. -using searchable_view = archetypes::archetype_view; - -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); - -// Two-range algorithms only require the predicate to accept the two projected references; the two -// element types stay unrelated and neither of them is comparable with itself. -using lhs_view = archetypes::archetype_view; -using rhs_view = archetypes::archetype_view; - -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); - -} //namespace test_std_ranges +// Every algorithm below is called with an archetype which satisfies exactly its declared +// constraints, so a compile error here means the implementation requires more from a user type than +// the requires-clause of the algorithm declares. The declared constraints themselves are asserted on +// the archetypes in std_ranges_archetypes.h: the element of the read family is neither copyable, +// movable, default constructible nor comparable, the projection of find_if / count_if returns a +// completely unrelated type, the search value of find / find_last / count is unrelated to the element +// type, and the two element types of the two-range algorithms are unrelated to each other and not +// even comparable with themselves. + #endif //_ENABLE_STD_RANGES_TESTING int diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp index 22b8825c440..7677d1b8c69 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp @@ -24,48 +24,17 @@ #include "std_ranges_archetypes.h" #include "std_ranges_algo_archetypes_test.h" -namespace test_std_ranges -{ -namespace dpl_ranges = oneapi::dpl::ranges; - // The value based algorithms are constrained by // std::indirect_binary_predicate, _Proj>, // const _T*> // only. In particular the value type is not required to be copyable, to be comparable with itself // with anything but std::ranges::equal_to, or to be related to the element type in any other way, -// and the element type is not required to be comparable with itself either. -using searchable_view = archetypes::archetype_view; -using removable_view = archetypes::archetype_view; -using seq_policy = decltype(oneapi::dpl::execution::seq); - -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); - -// The device copyable counterpart of the value satisfies the very same constraints, and it really is -// accepted by SYCL without an explicit sycl::is_device_copyable specialization. -using searchable_dc_view = archetypes::archetype_view; -using removable_dc_view = archetypes::archetype_view; - -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); -static_assert(std::invocable); - -} //namespace test_std_ranges +// and the element type is not required to be comparable with itself either; the device copyable +// counterpart of the value satisfies the very same constraints and really is accepted by SYCL without +// an explicit sycl::is_device_copyable specialization. That is asserted on the archetypes themselves +// in std_ranges_archetypes.h; what the calls below add is the instantiation of the implementation, +// which is where an extra requirement shows up as a compile error. + #endif //_ENABLE_STD_RANGES_TESTING int diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp index 8f69fd102c5..eb65163268b 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp @@ -24,43 +24,19 @@ #include "std_ranges_archetypes.h" #include "std_ranges_algo_archetypes_test.h" -namespace test_std_ranges -{ -namespace dpl_ranges = oneapi::dpl::ranges; - -using seq_policy = decltype(oneapi::dpl::execution::seq); - -using writable_view = archetypes::archetype_view; -using copy_in_view = archetypes::archetype_view; -using copy_out_view = archetypes::archetype_view; -using move_in_view = archetypes::archetype_view; -using move_out_view = archetypes::archetype_view; -using swap_view = archetypes::archetype_view; -using transform_in_view = archetypes::archetype_view; -using transform_out_view = archetypes::archetype_view; - // fill only requires std::indirectly_writable, const _T&>: the element type is not -// required to be copyable, movable or default constructible and _T stays unrelated to it. -static_assert(std::invocable); - -// The copying algorithms only require std::indirectly_copyable, so the output element is merely -// assignable from a non-const lvalue of the input element type. -static_assert(std::invocable); - -// move requires std::indirectly_movable, which is strictly weaker: assigning from an lvalue is -// deliberately rejected by move_out_archetype, so an implementation copying instead of moving fails. -static_assert(std::invocable); - -// swap_ranges requires std::indirectly_swappable only, which the hidden friend swap provides -// without the element being move constructible or move assignable. -static_assert(std::invocable); - -// transform writes the result of the functor, which is a third unrelated type; the functor itself -// only has to be std::copy_constructible. -static_assert(std::invocable); +// required to be copyable, movable or default constructible and _T stays unrelated to it. The copying +// algorithms only require std::indirectly_copyable, so the output element is merely assignable from a +// non-const lvalue of the input element type; move requires std::indirectly_movable, which is +// strictly weaker: assigning from an lvalue is deliberately rejected by move_out_archetype, so an +// implementation copying instead of moving fails. swap_ranges requires std::indirectly_swappable +// only, which the hidden friend swap provides without the element being move constructible or move +// assignable, and transform writes the result of the functor, which is a third unrelated type, while +// the functor itself only has to be std::copy_constructible. All of those requires-clauses are +// asserted on the archetypes themselves in std_ranges_archetypes.h; what the calls below add is the +// instantiation of the implementation, which is where an extra requirement shows up as a compile +// error. -} //namespace test_std_ranges #endif //_ENABLE_STD_RANGES_TESTING int diff --git a/test/parallel_api/ranges/std_ranges_memory_archetypes.pass.cpp b/test/parallel_api/ranges/std_ranges_memory_archetypes.pass.cpp index 0f87e3c91c0..336e31bde59 100644 --- a/test/parallel_api/ranges/std_ranges_memory_archetypes.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_memory_archetypes.pass.cpp @@ -33,17 +33,6 @@ constexpr int test_mode_id constexpr int test_mode_id> = 1; -// Every algorithm below must be callable with the archetype which satisfies exactly its declared -// constraints. A failure here means the constraints are not sufficient to call the algorithm. -static_assert(std::invocable&>); -static_assert(std::invocable&>); -static_assert(std::invocable&>); - // Runs a one-range algorithm over archetype_view, which is random access and sized but neither // contiguous nor common, so the implementation cannot fall back to raw pointer arithmetic. template From a58639cb8e7e172742b67cf5da80d6a5b38d66c6 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Wed, 9 Sep 2026 11:46:38 +0200 Subject: [PATCH 074/148] Drop the introductory comments of the archetype tests The blocks removed here restated the constraints of the algorithm families and the reasoning behind the archetypes right above main(). That reasoning belongs to std_ranges_archetypes.h, next to the archetypes and the static_asserts which state the very same requires-clauses formally, so keeping a prose copy in every test only meant two places to keep in sync. The comments explaining an individual case, an expected result or a known implementation gap stay at their call sites. Co-Authored-By: Claude Opus 5 --- .../std_ranges_algo_archetypes_merge.pass.cpp | 10 ---------- ...anges_algo_archetypes_mutable_callable.pass.cpp | 11 ----------- .../std_ranges_algo_archetypes_permute.pass.cpp | 9 --------- .../std_ranges_algo_archetypes_read.pass.cpp | 10 ---------- .../std_ranges_algo_archetypes_value.pass.cpp | 12 ------------ .../std_ranges_algo_archetypes_write.pass.cpp | 14 -------------- 6 files changed, 66 deletions(-) diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp index 1fd472a7d79..3399421400e 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp @@ -23,16 +23,6 @@ #if _ENABLE_STD_RANGES_TESTING #include "std_ranges_archetypes.h" #include "std_ranges_algo_archetypes_test.h" - -// The merge family is constrained by std::mergeable, which asks for indirectly_copyable from both -// inputs into the output plus a strict weak order: the output element stays non-copyable itself and -// the ordering never comes from an operator< on the element. min / max / minmax instead require -// std::indirectly_copyable_storable, range_value_t<_R>*>, which does need a copy -// constructor and copy assignment, but still no default constructor and no ordering operator on the -// element. Both requires-clauses are asserted on the archetypes themselves in -// std_ranges_archetypes.h; what the calls below add is the instantiation of the implementation, -// which is where an extra requirement shows up as a compile error. - #endif //_ENABLE_STD_RANGES_TESTING int diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp index 972c6d932f6..f68f6e3106b 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp @@ -23,17 +23,6 @@ #if _ENABLE_STD_RANGES_TESTING #include "std_ranges_archetypes.h" #include "std_ranges_algo_archetypes_test.h" - -// The indirect callable concepts (std::indirectly_unary_invocable, std::indirect_unary_predicate, -// std::indirect_binary_predicate, std::indirect_strict_weak_order) and std::projected are spelled in -// terms of iter_value_t<_It>&, iter_reference_t<_It> and iter_common_reference_t<_It>, all three of -// which are a non-const lvalue reference for archetype_view. A projection, a predicate or a -// comparator taking its arguments by non-const reference therefore satisfies the requires-clauses of -// the algorithms below, and the implementation must pass the element to the user callable as a -// non-const lvalue: a const lvalue, an rvalue or a copy does not compile here. -// -// Every case below is an actual call: instantiating the implementation is the only way to prove that -// it compiles, a check of the requires-clause alone never leaves the declaration of the algorithm. #endif //_ENABLE_STD_RANGES_TESTING int diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp index ee05d10d30f..4b3a15ebeea 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp @@ -23,15 +23,6 @@ #if _ENABLE_STD_RANGES_TESTING #include "std_ranges_archetypes.h" #include "std_ranges_algo_archetypes_test.h" - -// The permuting algorithms are constrained by std::permutable> only, which requires -// the element to be movable, but not copyable, not default constructible and not comparable: any -// ordering or equality has to come from the comparator passed by the user. std::sortable == permutable && indirect_strict_weak_order<...>, so the very same element archetype -// works for the sorting algorithms as well. Those requirements are asserted on the archetypes -// themselves in std_ranges_archetypes.h; what the calls below add is the instantiation of the -// implementation, which is where an extra requirement shows up as a compile error. - #endif //_ENABLE_STD_RANGES_TESTING int diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp index 9771cba5792..c97023d5aa3 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp @@ -23,16 +23,6 @@ #if _ENABLE_STD_RANGES_TESTING #include "std_ranges_archetypes.h" #include "std_ranges_algo_archetypes_test.h" - -// Every algorithm below is called with an archetype which satisfies exactly its declared -// constraints, so a compile error here means the implementation requires more from a user type than -// the requires-clause of the algorithm declares. The declared constraints themselves are asserted on -// the archetypes in std_ranges_archetypes.h: the element of the read family is neither copyable, -// movable, default constructible nor comparable, the projection of find_if / count_if returns a -// completely unrelated type, the search value of find / find_last / count is unrelated to the element -// type, and the two element types of the two-range algorithms are unrelated to each other and not -// even comparable with themselves. - #endif //_ENABLE_STD_RANGES_TESTING int diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp index 7677d1b8c69..ac0fbecf6a3 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp @@ -23,18 +23,6 @@ #if _ENABLE_STD_RANGES_TESTING #include "std_ranges_archetypes.h" #include "std_ranges_algo_archetypes_test.h" - -// The value based algorithms are constrained by -// std::indirect_binary_predicate, _Proj>, -// const _T*> -// only. In particular the value type is not required to be copyable, to be comparable with itself -// with anything but std::ranges::equal_to, or to be related to the element type in any other way, -// and the element type is not required to be comparable with itself either; the device copyable -// counterpart of the value satisfies the very same constraints and really is accepted by SYCL without -// an explicit sycl::is_device_copyable specialization. That is asserted on the archetypes themselves -// in std_ranges_archetypes.h; what the calls below add is the instantiation of the implementation, -// which is where an extra requirement shows up as a compile error. - #endif //_ENABLE_STD_RANGES_TESTING int diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp index eb65163268b..ee7f73da2d7 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp @@ -23,20 +23,6 @@ #if _ENABLE_STD_RANGES_TESTING #include "std_ranges_archetypes.h" #include "std_ranges_algo_archetypes_test.h" - -// fill only requires std::indirectly_writable, const _T&>: the element type is not -// required to be copyable, movable or default constructible and _T stays unrelated to it. The copying -// algorithms only require std::indirectly_copyable, so the output element is merely assignable from a -// non-const lvalue of the input element type; move requires std::indirectly_movable, which is -// strictly weaker: assigning from an lvalue is deliberately rejected by move_out_archetype, so an -// implementation copying instead of moving fails. swap_ranges requires std::indirectly_swappable -// only, which the hidden friend swap provides without the element being move constructible or move -// assignable, and transform writes the result of the functor, which is a third unrelated type, while -// the functor itself only has to be std::copy_constructible. All of those requires-clauses are -// asserted on the archetypes themselves in std_ranges_archetypes.h; what the calls below add is the -// instantiation of the implementation, which is where an extra requirement shows up as a compile -// error. - #endif //_ENABLE_STD_RANGES_TESTING int From b72cd9e92a812612a9a6eef26a3b6954a333f965 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Wed, 9 Sep 2026 11:52:33 +0200 Subject: [PATCH 075/148] Drop the reduced policy fallbacks of the archetype tests run_algo_seq_policies and run_algo2_novec_policies existed to keep running a case with the subset of the host policies which is known to work while the remaining ones are switched off by a _TEST_CPP20_RANGES_BROKEN_REQUIRES_* macro. That made every affected case an #if / #else pair repeating the same algorithm, checker and name twice, and it hid which policies a case actually covers behind the state of a macro. A case is now either run with all the host policies or not run at all, so the guard reads as a plain "#if the implementation is fixed", and the two helpers are gone together with their call sites for sort, stable_sort, partial_sort, nth_element and find_first_of. Co-Authored-By: Claude Opus 5 --- ..._algo_archetypes_mutable_callable.pass.cpp | 17 ++----------- .../std_ranges_algo_archetypes_read.pass.cpp | 2 -- .../ranges/std_ranges_algo_archetypes_test.h | 25 ------------------- 3 files changed, 2 insertions(+), 42 deletions(-) diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp index f68f6e3106b..362546a9c8c 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp @@ -539,9 +539,6 @@ main() #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_FIRST_OF_HOST_VEC run_algo2_host_policies(find_first_of_algo, find_first_of_checker, "find_first_of, non-const callable"); -#else - run_algo2_novec_policies(find_first_of_algo, find_first_of_checker, - "find_first_of, non-const callable"); #endif // KSATODO: the device path of find_first_of copies the element of the first range into a const @@ -667,7 +664,7 @@ main() // - parallel_backend_tbb.h:1037 - std::lower_bound(..., _M_comp) passes the const lvalue _Val // of the merge split point to the comparator; // - utils.h:203 - __binary_op::operator() forwards that const lvalue into std::invoke. - // seq and unseq keep the element non-const all the way down and are exercised below. + // seq and unseq keep the element non-const all the way down. auto sort_algo = [](auto&& policy, auto&& view) { return dpl_ranges::sort(std::forward(policy), view, permutable_comp_mut{}); }; @@ -678,8 +675,6 @@ main() #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT_HOST_PAR run_algo_host_policies(sort_algo, sorted_checker, "sort, non-const comparator"); -#else - run_algo_seq_policies(sort_algo, sorted_checker, "sort, non-const comparator"); #endif #if TEST_DPCPP_BACKEND_PRESENT @@ -702,8 +697,6 @@ main() #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT_HOST_PAR run_algo_host_policies(stable_sort_algo, sorted_checker, "stable_sort, non-const comparator"); -#else - run_algo_seq_policies(stable_sort_algo, sorted_checker, "stable_sort, non-const comparator"); #endif #if TEST_DPCPP_BACKEND_PRESENT @@ -805,7 +798,7 @@ main() // - parallel_backend_tbb.h:1023,1026,1034 - __merge_func::split_merging passes *(_M_x_beg + __ym) // to std::upper_bound / std::lower_bound, which compares against their const lvalue parameter; // - utils.h:203 - __binary_op::operator() forwards that const lvalue into std::invoke. - // seq and unseq keep the element non-const all the way down and are exercised below. + // seq and unseq keep the element non-const all the way down. // The range is ascending already, so the first ten elements are 0 ... 9 afterwards. auto partial_sort_algo = [](auto&& policy, auto&& view) { return dpl_ranges::partial_sort(std::forward(policy), view, std::ranges::begin(view) + 10, @@ -818,9 +811,6 @@ main() #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_PARTIAL_SORT_HOST_PAR run_algo_host_policies(partial_sort_algo, partial_sort_checker, "partial_sort, non-const comparator"); -#else - run_algo_seq_policies(partial_sort_algo, partial_sort_checker, - "partial_sort, non-const comparator"); #endif #if TEST_DPCPP_BACKEND_PRESENT @@ -848,9 +838,6 @@ main() #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_NTH_ELEMENT_HOST_PAR run_algo_host_policies(nth_element_algo, nth_element_checker, "nth_element, non-const comparator"); -#else - run_algo_seq_policies(nth_element_algo, nth_element_checker, - "nth_element, non-const comparator"); #endif #if TEST_DPCPP_BACKEND_PRESENT diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp index c97023d5aa3..f03feac591c 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp @@ -480,8 +480,6 @@ main() #if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_FIRST_OF_HOST_VEC run_algo2_host_policies(find_first_of_algo, find_first_of_checker, "find_first_of"); -#else - run_algo2_novec_policies(find_first_of_algo, find_first_of_checker, "find_first_of"); #endif #if TEST_DPCPP_BACKEND_PRESENT diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h b/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h index aaeaa902a9b..055c8eb0030 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h @@ -86,31 +86,6 @@ run_algo_host_policies(_Algo __algo, _Checker __checker, const char* __algo_name run_algo<_Elem>(__alloc, oneapi::dpl::execution::par_unseq, __algo, __checker, __algo_name); } -// Runs a one-range algorithm with the sequential host policies only. Used where the parallel host -// policies are known to require more from a user type than the algorithm declares, so that the -// coverage of seq and unseq is kept instead of switching the whole case off. -template -void -run_algo_seq_policies(_Algo __algo, _Checker __checker, const char* __algo_name) -{ - std::allocator<_Elem> __alloc; - run_algo<_Elem>(__alloc, oneapi::dpl::execution::seq, __algo, __checker, __algo_name); - run_algo<_Elem>(__alloc, oneapi::dpl::execution::unseq, __algo, __checker, __algo_name); -} - -// Runs a two-range algorithm with the non vectorized host policies only. Used where the SIMD brick -// of a two-range algorithm requires more from a user callable than the algorithm declares, so that -// the coverage of seq and par is kept instead of switching the whole case off. -template -void -run_algo2_novec_policies(_Algo __algo, _Checker __checker, const char* __algo_name) -{ - std::allocator<_Elem1> __alloc1; - std::allocator<_Elem2> __alloc2; - run_algo2<_Elem1, _Elem2>(__alloc1, __alloc2, oneapi::dpl::execution::seq, __algo, __checker, __algo_name); - run_algo2<_Elem1, _Elem2>(__alloc1, __alloc2, oneapi::dpl::execution::par, __algo, __checker, __algo_name); -} - // Runs a two-range algorithm with the host policies only, see run_algo_host_policies. template void From 6e7a6dbb65b2fc9b03de72ef0f7eb72998024cf5 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Wed, 9 Sep 2026 11:56:44 +0200 Subject: [PATCH 076/148] Drop the policy suffix from the broken requires macros of the host policies With the reduced policy fallbacks gone a case is either run with all the host policies or not run at all, so a macro naming the subset which is broken (_HOST_PAR for sort, stable_sort, partial_sort and nth_element, _HOST_VEC for find_first_of) no longer describes what it switches off. They are named _HOST now, like the macros of inplace_merge and of the set operations. The comments above them keep the detail of which policies actually hit the defect. Co-Authored-By: Claude Opus 5 --- ...td_ranges_algo_archetypes_mutable_callable.pass.cpp | 10 +++++----- .../ranges/std_ranges_algo_archetypes_read.pass.cpp | 2 +- test/support/test_config.h | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp index 362546a9c8c..1abcee62f59 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp @@ -536,7 +536,7 @@ main() }; auto find_first_of_checker = [](auto&& view1, auto&&, auto res) { return res == std::ranges::begin(view1); }; -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_FIRST_OF_HOST_VEC +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_FIRST_OF_HOST run_algo2_host_policies(find_first_of_algo, find_first_of_checker, "find_first_of, non-const callable"); #endif @@ -673,7 +673,7 @@ main() std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; }; -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT_HOST_PAR +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT_HOST run_algo_host_policies(sort_algo, sorted_checker, "sort, non-const comparator"); #endif @@ -695,7 +695,7 @@ main() return dpl_ranges::stable_sort(std::forward(policy), view, permutable_comp_mut{}); }; -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT_HOST_PAR +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT_HOST run_algo_host_policies(stable_sort_algo, sorted_checker, "stable_sort, non-const comparator"); #endif @@ -808,7 +808,7 @@ main() return std::ranges::begin(view)[0].val == 0 && std::ranges::begin(view)[9].val == 9; }; -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_PARTIAL_SORT_HOST_PAR +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_PARTIAL_SORT_HOST run_algo_host_policies(partial_sort_algo, partial_sort_checker, "partial_sort, non-const comparator"); #endif @@ -835,7 +835,7 @@ main() }; auto nth_element_checker = [](auto&& view, auto) { return std::ranges::begin(view)[10].val == 10; }; -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_NTH_ELEMENT_HOST_PAR +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_NTH_ELEMENT_HOST run_algo_host_policies(nth_element_algo, nth_element_checker, "nth_element, non-const comparator"); #endif diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp index f03feac591c..48d947f7242 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp @@ -478,7 +478,7 @@ main() }; auto find_first_of_checker = [](auto&& view1, auto&&, auto res) { return res == std::ranges::begin(view1); }; -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_FIRST_OF_HOST_VEC +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_FIRST_OF_HOST run_algo2_host_policies(find_first_of_algo, find_first_of_checker, "find_first_of"); #endif diff --git a/test/support/test_config.h b/test/support/test_config.h index ea7be00a1d2..7e39ebd38cd 100644 --- a/test/support/test_config.h +++ b/test/support/test_config.h @@ -375,15 +375,15 @@ // The parallel host policies of sort, stable_sort, partial_sort and nth_element hand a const lvalue // to the comparator, which std::sortable never asks for; seq and unseq are fine. -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT_HOST_PAR 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT_HOST_PAR 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_PARTIAL_SORT_HOST_PAR 1 -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_NTH_ELEMENT_HOST_PAR 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_PARTIAL_SORT_HOST 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_NTH_ELEMENT_HOST 1 // The SIMD brick of find_first_of calls the predicate with its two arguments swapped, which // std::indirectly_comparable does not ask for: the vectorized host policies unseq and par_unseq are // broken, seq and par are fine. -#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_FIRST_OF_HOST_VEC 1 +#define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_FIRST_OF_HOST 1 // inplace_merge is broken for every host policy, see the note at its call site. #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_INPLACE_MERGE_HOST 1 From b286b82ea218b2187b8fd70178a511e1e3a9c679 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Wed, 9 Sep 2026 11:56:56 +0200 Subject: [PATCH 077/148] Cover oneapi::dpl::ranges::includes with archetypes includes constrains its comparator with std::indirect_strict_weak_order over the two projected iterators, which subsumes std::relation and therefore asks for the two element types in all four combinations, not only for (lhs, rhs) the way std::indirectly_comparable does for equal, mismatch, search, find_end and find_first_of. cross_pred cannot be reused for it, which is why the algorithm had no archetype coverage so far. cross_comp provides those four combinations by const reference and cross_comp_mut by non-const reference: every reference std::indirect_strict_weak_order hands to the comparator over archetype_view is a non-const lvalue, so a comparator taking its arguments that way conforms as well and the implementation must not pass a const lvalue, an rvalue or a copy. Both ranges hold the very same ascending sequence, so the second one is included in the first one. All the host policies and the hetero ones compile and give the expected result with either comparator, so no broken requires macro is needed. Co-Authored-By: Claude Opus 5 --- ..._algo_archetypes_mutable_callable.pass.cpp | 17 +++++++++ .../std_ranges_algo_archetypes_read.pass.cpp | 17 +++++++++ .../ranges/std_ranges_archetypes.h | 37 +++++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp index 1abcee62f59..232770f51d1 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp @@ -558,6 +558,23 @@ main() #endif // !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_FIRST_OF_HETERO #endif // TEST_DPCPP_BACKEND_PRESENT + // includes needs a comparator accepting the two element types in all four combinations, see + // cross_comp_mut. Both ranges hold the very same ascending sequence, so the second one is included + // in the first one. + run_algo2_host_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::includes(std::forward(policy), view1, view2, cross_comp_mut{}); + }, + [](auto&&, auto&&, bool res) { return res; }, "includes, non-const comparator"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::includes(std::forward(policy), view1, view2, cross_comp_mut{}); + }, + [](auto&&, auto&&, bool res) { return res; }, "includes, non-const comparator"); +#endif // TEST_DPCPP_BACKEND_PRESENT + //---------------------------------------------------------------------------------------------- // transform with a functor taking the input element by non-const reference. //---------------------------------------------------------------------------------------------- diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp index 48d947f7242..be288a16bb0 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp @@ -487,6 +487,23 @@ main() "find_first_of"); #endif // TEST_DPCPP_BACKEND_PRESENT + // includes needs a comparator accepting the two element types in all four combinations, see + // cross_comp. Both ranges hold the very same ascending sequence, so the second one is included in + // the first one. + run_algo2_host_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::includes(std::forward(policy), view1, view2, cross_comp{}); + }, + [](auto&&, auto&&, bool res) { return res; }, "includes"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::includes(std::forward(policy), view1, view2, cross_comp{}); + }, + [](auto&&, auto&&, bool res) { return res; }, "includes"); +#endif // TEST_DPCPP_BACKEND_PRESENT + #endif //_ENABLE_STD_RANGES_TESTING return TestUtils::done(_ENABLE_STD_RANGES_TESTING); diff --git a/test/parallel_api/ranges/std_ranges_archetypes.h b/test/parallel_api/ranges/std_ranges_archetypes.h index 9bd521a2cb2..a23d5c1a55a 100644 --- a/test/parallel_api/ranges/std_ranges_archetypes.h +++ b/test/parallel_api/ranges/std_ranges_archetypes.h @@ -906,10 +906,29 @@ struct cross_pred bool operator()(const lhs_archetype_dc& __v1, const rhs_archetype_dc& __v2) const { return __v1.val == __v2.val; } }; +// includes is constrained by std::indirect_strict_weak_order over the two projected iterators, which +// subsumes std::relation and therefore asks for the two element types in all four combinations, not +// only for (lhs, rhs) the way std::indirectly_comparable does for cross_pred above. +struct cross_comp +{ + bool operator()(const lhs_archetype& __v1, const lhs_archetype& __v2) const { return __v1.val < __v2.val; } + bool operator()(const lhs_archetype& __v1, const rhs_archetype& __v2) const { return __v1.val < __v2.val; } + bool operator()(const rhs_archetype& __v1, const lhs_archetype& __v2) const { return __v1.val < __v2.val; } + bool operator()(const rhs_archetype& __v1, const rhs_archetype& __v2) const { return __v1.val < __v2.val; } + + bool operator()(const lhs_archetype_dc& __v1, const lhs_archetype_dc& __v2) const { return __v1.val < __v2.val; } + bool operator()(const lhs_archetype_dc& __v1, const rhs_archetype_dc& __v2) const { return __v1.val < __v2.val; } + bool operator()(const rhs_archetype_dc& __v1, const lhs_archetype_dc& __v2) const { return __v1.val < __v2.val; } + bool operator()(const rhs_archetype_dc& __v1, const rhs_archetype_dc& __v2) const { return __v1.val < __v2.val; } +}; + using lhs_iterator_t = std::ranges::iterator_t>; using rhs_iterator_t = std::ranges::iterator_t>; static_assert(std::indirectly_comparable); +static_assert(std::indirect_strict_weak_order); +static_assert(std::indirect_strict_weak_order>, + std::ranges::iterator_t>>); static_assert(std::indirectly_comparable>, std::ranges::iterator_t>, cross_pred>); static_assert(!std::equality_comparable); @@ -1639,6 +1658,24 @@ struct cross_pred_mut static_assert(std::indirectly_comparable); static_assert(!std::invocable); +// The four-combination comparator of includes, see cross_comp: every reference it is handed by +// std::indirect_strict_weak_order is a non-const lvalue as well. +struct cross_comp_mut +{ + bool operator()(lhs_archetype& __v1, lhs_archetype& __v2) const { return __v1.val < __v2.val; } + bool operator()(lhs_archetype& __v1, rhs_archetype& __v2) const { return __v1.val < __v2.val; } + bool operator()(rhs_archetype& __v1, lhs_archetype& __v2) const { return __v1.val < __v2.val; } + bool operator()(rhs_archetype& __v1, rhs_archetype& __v2) const { return __v1.val < __v2.val; } + + bool operator()(lhs_archetype_dc& __v1, lhs_archetype_dc& __v2) const { return __v1.val < __v2.val; } + bool operator()(lhs_archetype_dc& __v1, rhs_archetype_dc& __v2) const { return __v1.val < __v2.val; } + bool operator()(rhs_archetype_dc& __v1, lhs_archetype_dc& __v2) const { return __v1.val < __v2.val; } + bool operator()(rhs_archetype_dc& __v1, rhs_archetype_dc& __v2) const { return __v1.val < __v2.val; } +}; + +static_assert(std::indirect_strict_weak_order); +static_assert(!std::invocable); + // Family 8: transform. The functor is only required to be std::copy_constructible and invocable with // the projected reference, which is a non-const lvalue. struct transform_unary_op_mut From baa387996adee33d2ba939c91f7beeebc744809a Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Wed, 9 Sep 2026 12:15:57 +0200 Subject: [PATCH 078/148] Add transform archetypes for the binary overload and the projections The requires-clause of oneapi::dpl::ranges::transform spells the functor over std::projected, so the functor never sees the range element itself when a projection is given. The new projection archetype returns a third, unrelated type, and the projected functors reject the element type, so an implementation which applies the functor to the element or writes the projected value into the output does not compile. The mutable section gets the non-const-reference counterparts: a binary functor taking both input elements by non-const reference and a projection taking its argument the same way. A functor over a projection cannot do that, since the projection returns a prvalue. Co-Authored-By: Claude Opus 5 --- .../ranges/std_ranges_archetypes.h | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/test/parallel_api/ranges/std_ranges_archetypes.h b/test/parallel_api/ranges/std_ranges_archetypes.h index a23d5c1a55a..6024c01b028 100644 --- a/test/parallel_api/ranges/std_ranges_archetypes.h +++ b/test/parallel_api/ranges/std_ranges_archetypes.h @@ -1311,6 +1311,57 @@ static_assert(std::indirectly_writable< static_assert(!std::copyable); static_assert(!std::default_initializable); +// Both transform overloads project their input before invoking the functor, and the requires-clause +// spells the functor over std::projected, so the functor never sees the element itself. The +// projection returns yet another unrelated type: an implementation which applies the functor to the +// element, or writes the projected value into the output, does not compile. +struct transform_proj_result +{ + int val; +}; + +struct transform_proj +{ + transform_proj_result operator()(const transform_in_archetype& __v) const + { + return transform_proj_result{__v.val + 1}; + } + transform_proj_result operator()(const transform_in_archetype_dc& __v) const + { + return transform_proj_result{__v.val + 1}; + } +}; + +struct transform_projected_unary_op +{ + transform_result operator()(const transform_proj_result& __v) const { return transform_result{__v.val * 2}; } +}; + +struct transform_projected_binary_op +{ + transform_result operator()(const transform_proj_result& __v1, const transform_proj_result& __v2) const + { + return transform_result{__v1.val + __v2.val}; + } +}; + +using transform_projected_iterator_t = std::projected; + +static_assert(std::copy_constructible); +static_assert(std::indirectly_regular_unary_invocable); +static_assert(std::indirectly_writable< + transform_out_iterator_t, + std::indirect_result_t>); +static_assert(std::indirectly_writable>); +// The projected functors reject the element type, and the output element rejects the projected +// value, so neither the projection nor the functor can be skipped by the implementation. +static_assert(!std::invocable); +static_assert(!std::invocable); +static_assert(!std::indirectly_writable); + // Family 9: permuting algorithms. // std::permutable == forward_iterator && indirectly_movable_storable && // indirectly_swappable, which does require the element to be movable and move @@ -1684,9 +1735,44 @@ struct transform_unary_op_mut transform_result operator()(transform_in_archetype_dc& __v) const { return transform_result{__v.val * 2}; } }; +struct transform_binary_op_mut +{ + transform_result operator()(transform_in_archetype& __v1, transform_in_archetype& __v2) const + { + return transform_result{__v1.val + __v2.val}; + } + transform_result operator()(transform_in_archetype_dc& __v1, transform_in_archetype_dc& __v2) const + { + return transform_result{__v1.val + __v2.val}; + } +}; + +// A projection taking its argument by non-const reference. The functor invoked with the projected +// value cannot do the same: the projection returns a prvalue, which does not bind to a non-const +// lvalue reference, so the projected functors of the const section are reused with this projection. +struct transform_proj_mut +{ + transform_proj_result operator()(transform_in_archetype& __v) const { return transform_proj_result{__v.val + 1}; } + transform_proj_result operator()(transform_in_archetype_dc& __v) const + { + return transform_proj_result{__v.val + 1}; + } +}; + static_assert(std::indirectly_writable>); static_assert(!std::invocable); +static_assert(std::indirectly_writable< + transform_out_iterator_t, + std::indirect_result_t>); +static_assert( + !std::invocable); +static_assert(std::indirectly_regular_unary_invocable); +static_assert(!std::invocable); +static_assert(std::indirectly_writable< + transform_out_iterator_t, + std::indirect_result_t>>); // Family 9: permuting and sorting algorithms. The element is mutable by definition here, so the // predicate and the comparator may take it by non-const reference as well. From 45768843d1ff6f1ce2e67826425015bc905c7df5 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Wed, 9 Sep 2026 12:16:03 +0200 Subject: [PATCH 079/148] Cover the binary transform overload and its projections with real calls Only the unary overload without a projection was called so far, which left transform_binary_op covered by concept asserts alone. A requires-clause check never instantiates the implementation body, so the missing variants are now real calls: unary with a projection, binary, and binary with a projection for either input, each with the host and the hetero policies. The binary calls take two input ranges, so the output range is allocated inside the call, as the merge test does. Co-Authored-By: Claude Opus 5 --- .../std_ranges_algo_archetypes_write.pass.cpp | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp index ee7f73da2d7..a4cd5a5fa67 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp @@ -130,6 +130,81 @@ main() }, [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 14; }, "transform"); #endif // TEST_DPCPP_BACKEND_PRESENT + + // The same overload with a non-identity projection: the functor is invoked with the projected + // value, which is neither the element nor the output element type. + run_algo2_host_policies( + [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::transform(std::forward(policy), in_view, out_view, + transform_projected_unary_op{}, transform_proj{}); + }, + [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 16; }, + "transform, projection"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies( + [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::transform(std::forward(policy), in_view, out_view, + transform_projected_unary_op{}, transform_proj{}); + }, + [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 16; }, + "transform, projection"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // The binary overload takes two input ranges, so the output range is allocated inside the call + // and the check is done there as well. + run_algo2_host_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + archetype_storage> out_storage( + std::allocator{}, archetype_test_size, [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = dpl_ranges::transform(std::forward(policy), view1, view2, out_view, + transform_binary_op{}); + return std::ranges::begin(out_view)[7].val == 14 && res.out == std::ranges::end(out_view); + }, + [](auto&&, auto&&, auto res) { return res; }, "transform, binary"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + // The output range is written by a device kernel, so its storage has to be device + // accessible: host memory from std::allocator would be dereferenced on the device. + sycl::usm_allocator out_alloc{policy.queue()}; + archetype_storage out_storage( + out_alloc, archetype_test_size, [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = dpl_ranges::transform(std::forward(policy), view1, view2, out_view, + transform_binary_op{}); + return std::ranges::begin(out_view)[7].val == 14 && res.out == std::ranges::end(out_view); + }, + [](auto&&, auto&&, auto res) { return res; }, "transform, binary"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // The binary overload has a projection of its own for either input. + run_algo2_host_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + archetype_storage> out_storage( + std::allocator{}, archetype_test_size, [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = dpl_ranges::transform(std::forward(policy), view1, view2, out_view, + transform_projected_binary_op{}, transform_proj{}, transform_proj{}); + return std::ranges::begin(out_view)[7].val == 16 && res.out == std::ranges::end(out_view); + }, + [](auto&&, auto&&, auto res) { return res; }, "transform, binary, projections"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + sycl::usm_allocator out_alloc{policy.queue()}; + archetype_storage out_storage( + out_alloc, archetype_test_size, [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = dpl_ranges::transform(std::forward(policy), view1, view2, out_view, + transform_projected_binary_op{}, transform_proj{}, transform_proj{}); + return std::ranges::begin(out_view)[7].val == 16 && res.out == std::ranges::end(out_view); + }, + [](auto&&, auto&&, auto res) { return res; }, "transform, binary, projections"); +#endif // TEST_DPCPP_BACKEND_PRESENT #endif //_ENABLE_STD_RANGES_TESTING return TestUtils::done(_ENABLE_STD_RANGES_TESTING); From 12e357bbc1bc5b6ffcb73cdce1aafd5b231faa07 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Wed, 9 Sep 2026 12:16:09 +0200 Subject: [PATCH 080/148] Cover the transform variants with non-const callables and projections The same three call variants as in the write test, with the callable taking the input element by non-const reference. For the projected calls the non-const argument is the projection itself: the functor is invoked with the projected prvalue, which does not bind to a non-const lvalue reference. Co-Authored-By: Claude Opus 5 --- ..._algo_archetypes_mutable_callable.pass.cpp | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp index 232770f51d1..df6124cd11d 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp @@ -596,6 +596,83 @@ main() "transform, non-const callable"); #endif // TEST_DPCPP_BACKEND_PRESENT + // The projection is the one taking the element by non-const reference here: the functor is + // invoked with the projected prvalue and cannot take it by non-const reference at all. + run_algo2_host_policies( + [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::transform(std::forward(policy), in_view, out_view, + transform_projected_unary_op{}, transform_proj_mut{}); + }, + [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 16; }, + "transform, non-const projection"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies( + [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::transform(std::forward(policy), in_view, out_view, + transform_projected_unary_op{}, transform_proj_mut{}); + }, + [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 16; }, + "transform, non-const projection"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // The binary overload with a functor taking both input elements by non-const reference. It takes + // two input ranges, so the output range is allocated inside the call and checked there as well. + run_algo2_host_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + archetype_storage> out_storage( + std::allocator{}, archetype_test_size, [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = dpl_ranges::transform(std::forward(policy), view1, view2, out_view, + transform_binary_op_mut{}); + return std::ranges::begin(out_view)[7].val == 14 && res.out == std::ranges::end(out_view); + }, + [](auto&&, auto&&, auto res) { return res; }, "transform, binary, non-const callable"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + // The output range is written by a device kernel, so its storage has to be device + // accessible: host memory from std::allocator would be dereferenced on the device. + sycl::usm_allocator out_alloc{policy.queue()}; + archetype_storage out_storage( + out_alloc, archetype_test_size, [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = dpl_ranges::transform(std::forward(policy), view1, view2, out_view, + transform_binary_op_mut{}); + return std::ranges::begin(out_view)[7].val == 14 && res.out == std::ranges::end(out_view); + }, + [](auto&&, auto&&, auto res) { return res; }, "transform, binary, non-const callable"); +#endif // TEST_DPCPP_BACKEND_PRESENT + + // The binary overload with a non-const projection for either input. + run_algo2_host_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + archetype_storage> out_storage( + std::allocator{}, archetype_test_size, [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = + dpl_ranges::transform(std::forward(policy), view1, view2, out_view, + transform_projected_binary_op{}, transform_proj_mut{}, transform_proj_mut{}); + return std::ranges::begin(out_view)[7].val == 16 && res.out == std::ranges::end(out_view); + }, + [](auto&&, auto&&, auto res) { return res; }, "transform, binary, non-const projections"); + +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + sycl::usm_allocator out_alloc{policy.queue()}; + archetype_storage out_storage( + out_alloc, archetype_test_size, [](std::size_t) { return 0; }); + auto out_view = out_storage.view(); + auto res = + dpl_ranges::transform(std::forward(policy), view1, view2, out_view, + transform_projected_binary_op{}, transform_proj_mut{}, transform_proj_mut{}); + return std::ranges::begin(out_view)[7].val == 16 && res.out == std::ranges::end(out_view); + }, + [](auto&&, auto&&, auto res) { return res; }, "transform, binary, non-const projections"); +#endif // TEST_DPCPP_BACKEND_PRESENT + //---------------------------------------------------------------------------------------------- // The permuting and the sorting algorithms. //---------------------------------------------------------------------------------------------- From c7964d476506526e2ee28d2bcc109c2b933f6cbd Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Wed, 9 Sep 2026 13:26:48 +0200 Subject: [PATCH 081/148] Split the archetype definitions into per-family headers std_ranges_archetypes.h had grown to 1828 lines holding every archetype of every concept family, so finding the definitions belonging to one algorithm meant scrolling past all the others. The definitions are moved out verbatim, one header per concept family: base for archetype_view and archetype_storage, then memory, read, value, write, permute, merge and storable. The old file stays as a 41-line umbrella including the eight, so the include path of every test is unchanged and a test which needs a single family can still pick its header directly. std_ranges_archetypes_base.h now includes support/test_config.h itself instead of relying on the includer to have done it: that header defines both _ENABLE_STD_RANGES_TESTING and TEST_DPCPP_BACKEND_PRESENT, and without them the guarded body would silently compile to nothing. For the same reason SYCL is reached through support/utils_sycl_defs.h rather than directly, which is how the rest of the test suite spells it. std_ranges_archetypes_storable.h includes the merge header because storable_comp_mut is asserted over merge_in_iterator_t. Co-Authored-By: Claude Opus 5 --- .../ranges/std_ranges_archetypes.h | 1869 +---------------- .../ranges/std_ranges_archetypes_base.h | 254 +++ .../ranges/std_ranges_archetypes_memory.h | 171 ++ .../ranges/std_ranges_archetypes_merge.h | 151 ++ .../ranges/std_ranges_archetypes_permute.h | 149 ++ .../ranges/std_ranges_archetypes_read.h | 286 +++ .../ranges/std_ranges_archetypes_storable.h | 103 + .../ranges/std_ranges_archetypes_value.h | 445 ++++ .../ranges/std_ranges_archetypes_write.h | 515 +++++ 9 files changed, 2115 insertions(+), 1828 deletions(-) create mode 100644 test/parallel_api/ranges/std_ranges_archetypes_base.h create mode 100644 test/parallel_api/ranges/std_ranges_archetypes_memory.h create mode 100644 test/parallel_api/ranges/std_ranges_archetypes_merge.h create mode 100644 test/parallel_api/ranges/std_ranges_archetypes_permute.h create mode 100644 test/parallel_api/ranges/std_ranges_archetypes_read.h create mode 100644 test/parallel_api/ranges/std_ranges_archetypes_storable.h create mode 100644 test/parallel_api/ranges/std_ranges_archetypes_value.h create mode 100644 test/parallel_api/ranges/std_ranges_archetypes_write.h diff --git a/test/parallel_api/ranges/std_ranges_archetypes.h b/test/parallel_api/ranges/std_ranges_archetypes.h index 6024c01b028..8642af5785a 100644 --- a/test/parallel_api/ranges/std_ranges_archetypes.h +++ b/test/parallel_api/ranges/std_ranges_archetypes.h @@ -1,1828 +1,41 @@ -// -*- C++ -*- -//===----------------------------------------------------------------------===// -// -// Copyright (C) Intel Corporation -// -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -// This file incorporates work covered by the following copyright and permission -// notice: -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// -//===----------------------------------------------------------------------===// - -#ifndef _STD_RANGES_ARCHETYPES_H -#define _STD_RANGES_ARCHETYPES_H - -#if _ENABLE_STD_RANGES_TESTING - -#include -#include -#include -#include -#include -#include -#include - -#if TEST_DPCPP_BACKEND_PRESENT -# include -#endif - -// The types below are "archetypes": each of them satisfies exactly the constraints written in the -// requires-clause of the corresponding oneapi::dpl::ranges algorithm and nothing more. Every -// operation which is not implied by those constraints is explicitly deleted. If an algorithm -// compiles and works with an archetype, the implementation does not silently require more from a -// user type than it declares; otherwise the extra requirement shows up as a compilation error. -// -// Each archetype keeps two observable fields, val1 and val2, so that a test can check which part of -// the raw memory has been written, exactly as the pre-existing Elem/Elem_0 types do. - -// Unary operator& is not required by any constraint, so a conforming implementation has to use -// std::addressof instead of taking the address directly. Define this macro to 0 to relax the -// archetypes if the deleted operator& hides other findings. -#ifndef TEST_ARCHETYPE_DELETE_ADDRESSOF -# define TEST_ARCHETYPE_DELETE_ADDRESSOF 1 -#endif - -#if TEST_ARCHETYPE_DELETE_ADDRESSOF -# define TEST_ARCHETYPE_DELETED_ADDRESSOF void operator&() const = delete; -#else -# define TEST_ARCHETYPE_DELETED_ADDRESSOF -#endif - -// Deletes everything a "regular" type would provide but no constraint of the tested algorithms asks -// for: copying, moving, assignment and taking the address. -#define TEST_ARCHETYPE_DELETED_OPERATIONS(_Name) \ - _Name(const _Name&) = delete; \ - _Name(_Name&&) = delete; \ - _Name& operator=(const _Name&) = delete; \ - _Name& operator=(_Name&&) = delete; \ - TEST_ARCHETYPE_DELETED_ADDRESSOF - -// The device copyable counterpart of TEST_ARCHETYPE_DELETED_OPERATIONS: the copy and the move -// operations are trivial, which makes the type trivially copyable and thus device copyable by -// default, while everything else stays exactly as restricted as in the host only archetype. -#define TEST_ARCHETYPE_DEFAULTED_OPERATIONS(_Name) \ - _Name(const _Name&) = default; \ - _Name(_Name&&) = default; \ - _Name& operator=(const _Name&) = default; \ - _Name& operator=(_Name&&) = default; \ - TEST_ARCHETYPE_DELETED_ADDRESSOF - -// Checks that a device copyable archetype really is accepted by SYCL without an explicit -// sycl::is_device_copyable specialization. -#if TEST_DPCPP_BACKEND_PRESENT -# define TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(_Name) \ - static_assert(std::is_trivially_copyable_v<_Name>); \ - static_assert(sycl::is_device_copyable_v<_Name>); -#else -# define TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(_Name) static_assert(std::is_trivially_copyable_v<_Name>); -#endif - -namespace test_std_ranges -{ -namespace archetypes -{ - -// std::default_initializable, required by uninitialized_default_construct. -// The default constructor is user-provided, so default- and value-initialization are the same and -// val2 is left untouched by the algorithm. -struct default_construct_archetype -{ - int val1; - int val2; - - default_construct_archetype() { val1 = 1; } - - TEST_ARCHETYPE_DELETED_OPERATIONS(default_construct_archetype) -}; - -static_assert(std::default_initializable); -static_assert(std::destructible); -static_assert(!std::copy_constructible); -static_assert(!std::move_constructible); -static_assert(!std::equality_comparable); -static_assert(!std::swappable); - -// std::default_initializable, required by uninitialized_value_construct. -// The default constructor is defaulted on its first declaration and therefore is not user-provided: -// value-initialization zero-initializes the whole object, which lets the test tell value -// construction apart from default construction. -struct value_construct_archetype -{ - int val1; - int val2; - - value_construct_archetype() = default; - - TEST_ARCHETYPE_DELETED_OPERATIONS(value_construct_archetype) -}; - -static_assert(std::default_initializable); -static_assert(std::destructible); -static_assert(!std::copy_constructible); -static_assert(!std::move_constructible); -static_assert(!std::equality_comparable); -static_assert(!std::swappable); - -// The _T template parameter of uninitialized_fill is deduced from the value argument, so the filler -// type is deliberately different from the range value type: the only required conversion is -// std::constructible_from, const fill_source&>. -struct fill_source -{ - int val; -}; - -struct fill_archetype -{ - int val1; - int val2; - - explicit fill_archetype(const fill_source& src) { val2 = src.val; } - - TEST_ARCHETYPE_DELETED_OPERATIONS(fill_archetype) -}; - -static_assert(std::constructible_from); -static_assert(std::destructible); -static_assert(!std::default_initializable); -static_assert(!std::copy_constructible); -static_assert(!std::move_constructible); - -// Input element type of uninitialized_copy and uninitialized_move. No constraint is imposed on it -// besides forming a random access range, so it is only constructible from an int, which is what the -// test harness uses to prepare the input data. -struct transfer_source -{ - int val1; - int val2; - - explicit transfer_source(int v) { val2 = v; } - - TEST_ARCHETYPE_DELETED_OPERATIONS(transfer_source) -}; - -static_assert(std::destructible); -static_assert(!std::default_initializable); -static_assert(!std::copy_constructible); -static_assert(!std::move_constructible); - -// std::constructible_from, range_reference_t<_InRange>>, required by -// uninitialized_copy. range_reference_t of a range of transfer_source is exactly transfer_source&, -// so the implementation must not pass a const lvalue or an rvalue instead. -struct copy_archetype -{ - int val1; - int val2; - - explicit copy_archetype(transfer_source& src) { val2 = src.val2; } - - TEST_ARCHETYPE_DELETED_OPERATIONS(copy_archetype) -}; - -static_assert(std::constructible_from); -static_assert(std::destructible); -static_assert(!std::constructible_from); -static_assert(!std::constructible_from); -static_assert(!std::default_initializable); -static_assert(!std::copy_constructible); - -// std::constructible_from, range_rvalue_reference_t<_InRange>>, required by -// uninitialized_move. Only an rvalue is accepted, so the implementation has to move the source -// element (std::ranges::iter_move) rather than copy it. -struct move_archetype -{ - int val1; - int val2; - - explicit move_archetype(transfer_source&& src) { val2 = src.val2; } - - TEST_ARCHETYPE_DELETED_OPERATIONS(move_archetype) -}; - -static_assert(std::constructible_from); -static_assert(std::destructible); -static_assert(!std::constructible_from); -static_assert(!std::default_initializable); -static_assert(!std::copy_constructible); - -// std::destructible, required by destroy. No constructor at all is declared, which is enough for the -// test: the harness works on raw memory and only observes the effect of the destructor. -struct destroy_archetype -{ - int val1; - volatile int val2; // volatile prevents optimization of the destructor observed with g++ - - ~destroy_archetype() { val2 = 3; } - - TEST_ARCHETYPE_DELETED_OPERATIONS(destroy_archetype) -}; - -static_assert(std::destructible); -static_assert(!std::default_initializable); -static_assert(!std::copy_constructible); -static_assert(!std::move_constructible); - -// A random access iterator which is deliberately not a contiguous one. Unlike a pointer, a span -// iterator or a subrange over pointers, it gives the implementation no way to fall back to raw -// pointer arithmetic on the underlying storage. -template -class archetype_iterator -{ - T* ptr = nullptr; - - public: - using iterator_concept = std::random_access_iterator_tag; - using iterator_category = std::random_access_iterator_tag; - using value_type = T; - using difference_type = std::ptrdiff_t; - using reference = T&; - using pointer = T*; - - archetype_iterator() = default; - explicit archetype_iterator(T* p) : ptr(p) {} - - T* base() const { return ptr; } - - reference operator*() const { return *ptr; } - pointer operator->() const { return ptr; } - reference operator[](difference_type n) const { return ptr[n]; } - - archetype_iterator& operator++() { ++ptr; return *this; } - archetype_iterator operator++(int) { auto tmp = *this; ++ptr; return tmp; } - archetype_iterator& operator--() { --ptr; return *this; } - archetype_iterator operator--(int) { auto tmp = *this; --ptr; return tmp; } - - archetype_iterator& operator+=(difference_type n) { ptr += n; return *this; } - archetype_iterator& operator-=(difference_type n) { ptr -= n; return *this; } - - friend archetype_iterator operator+(archetype_iterator i, difference_type n) { return i += n; } - friend archetype_iterator operator+(difference_type n, archetype_iterator i) { return i += n; } - friend archetype_iterator operator-(archetype_iterator i, difference_type n) { return i -= n; } - friend difference_type operator-(archetype_iterator i, archetype_iterator j) { return i.ptr - j.ptr; } - - friend bool operator==(archetype_iterator i, archetype_iterator j) { return i.ptr == j.ptr; } - friend auto operator<=>(archetype_iterator i, archetype_iterator j) { return i.ptr <=> j.ptr; } -}; - -// A sentinel type distinct from the iterator, which makes the range non-common while keeping it -// sized via the sized_sentinel_for requirement. -template -class archetype_sentinel -{ - T* ptr = nullptr; - - public: - archetype_sentinel() = default; - explicit archetype_sentinel(T* p) : ptr(p) {} - - T* base() const { return ptr; } - - friend bool operator==(archetype_iterator i, archetype_sentinel s) { return i.base() == s.ptr; } - friend std::ptrdiff_t operator-(archetype_iterator i, archetype_sentinel s) { return i.base() - s.ptr; } - friend std::ptrdiff_t operator-(archetype_sentinel s, archetype_iterator i) { return s.ptr - i.base(); } -}; - -// A view over raw storage which satisfies __nothrow_random_access_range and sized_range, but is -// neither contiguous nor common. It is marked as a borrowed range so that the algorithms keep -// returning a real iterator rather than std::ranges::dangling. -template -class archetype_view : public std::ranges::view_interface> -{ - T* first = nullptr; - T* last = nullptr; - - public: - archetype_view() = default; - archetype_view(T* p, std::size_t n) : first(p), last(p + n) {} - - archetype_iterator begin() const { return archetype_iterator(first); } - archetype_sentinel end() const { return archetype_sentinel(last); } -}; - -} // namespace archetypes -} // namespace test_std_ranges - -template -inline constexpr bool std::ranges::enable_borrowed_range> = true; - -namespace test_std_ranges -{ -namespace archetypes -{ - -static_assert(std::random_access_iterator>); -static_assert(!std::contiguous_iterator>); -static_assert(std::sized_sentinel_for, archetype_iterator>); - -static_assert(std::ranges::random_access_range>); -static_assert(std::ranges::sized_range>); -static_assert(std::ranges::borrowed_range>); -static_assert(!std::ranges::contiguous_range>); -static_assert(!std::ranges::common_range>); - -// The two extra requirements of __nothrow_random_access_range beyond random_access_range. -static_assert(std::is_lvalue_reference_v>>); -static_assert(std::same_as>>, - std::ranges::range_value_t>>); - -// Owns raw storage and constructs the elements in place. The archetypes are neither copyable nor -// movable, so they cannot be kept in a standard container; the allocator is a template parameter so -// that the very same storage works with std::allocator on the host and with sycl::usm_allocator on -// a device. -template -class archetype_storage -{ - Alloc alloc; - std::size_t count = 0; - T* data = nullptr; - - public: - // _Factory is called as __factory(i) for every index and has to return the arguments of the - // element constructor. - template - archetype_storage(Alloc __alloc, std::size_t __n, _Factory __factory) : alloc(__alloc), count(__n) - { - data = alloc.allocate(count); - for (std::size_t __i = 0; __i < count; ++__i) - std::construct_at(data + __i, __factory(__i)); - } - - archetype_storage(const archetype_storage&) = delete; - archetype_storage& operator=(const archetype_storage&) = delete; - - ~archetype_storage() - { - for (std::size_t __i = 0; __i < count; ++__i) - std::destroy_at(data + __i); - alloc.deallocate(data, count); - } - - std::size_t size() const { return count; } - T* begin_ptr() const { return data; } - - archetype_view view() const { return archetype_view(data, count); } -}; - -//------------------------------------------------------------------------------------------------ -// Archetypes for the algorithms of glue_algorithm_ranges_impl.h -// -// Every algorithm there constrains its range parameters with std::ranges::random_access_range and -// std::ranges::sized_range only; all the remaining requirements are expressed as indirect concepts -// on the iterators. The element archetypes below therefore drop everything a "regular" type would -// have and add back exactly the operations one concept family needs. archetype_view is reused as the -// range, so the ranges are random access and sized but neither contiguous nor common. -//------------------------------------------------------------------------------------------------ - -// Family 1: read-only algorithms parameterized by a callable. -// std::indirectly_unary_invocable / std::indirect_unary_predicate / std::indirect_strict_weak_order / -// std::indirect_equivalence_relation only require the callable to be invocable with the projected -// value; they impose nothing at all on the element type itself. -// Used by: for_each, find_if, find_if_not, find_last_if, find_last_if_not, any_of, all_of, none_of, -// count_if, is_partitioned, adjacent_find, is_sorted, is_sorted_until, is_heap, is_heap_until, -// min_element, max_element, minmax_element, lexicographical_compare, includes. -struct read_archetype -{ - int val; - - explicit read_archetype(int __v) : val(__v) {} - - TEST_ARCHETYPE_DELETED_OPERATIONS(read_archetype) -}; - -static_assert(std::destructible); -static_assert(!std::default_initializable); -static_assert(!std::copy_constructible); -static_assert(!std::move_constructible); -static_assert(!std::equality_comparable); -static_assert(!std::totally_ordered); - -// The device copyable counterpart of read_archetype, used with the hetero policies: it is trivially -// copyable, so a device kernel may take it by value, but it is still not default constructible and -// not comparable. -struct read_archetype_dc -{ - int val; - - explicit read_archetype_dc(int __v) : val(__v) {} - - TEST_ARCHETYPE_DEFAULTED_OPERATIONS(read_archetype_dc) -}; - -TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(read_archetype_dc) -static_assert(!std::default_initializable); -static_assert(!std::equality_comparable); -static_assert(!std::totally_ordered); - -// The callables take exactly const _T& and return exactly the required type, so an implementation -// cannot pass an rvalue, a copy, or expect a wider return type. -struct read_unary_fun -{ - void operator()(const read_archetype&) const {} - void operator()(const read_archetype_dc&) const {} -}; - -struct read_unary_pred -{ - bool operator()(const read_archetype& __v) const { return __v.val % 3 == 0; } - bool operator()(const read_archetype_dc& __v) const { return __v.val % 3 == 0; } -}; - -struct read_binary_pred -{ - bool operator()(const read_archetype& __v1, const read_archetype& __v2) const { return __v1.val == __v2.val; } - bool operator()(const read_archetype_dc& __v1, const read_archetype_dc& __v2) const - { - return __v1.val == __v2.val; - } -}; - -struct read_comp -{ - bool operator()(const read_archetype& __v1, const read_archetype& __v2) const { return __v1.val < __v2.val; } - bool operator()(const read_archetype_dc& __v1, const read_archetype_dc& __v2) const { return __v1.val < __v2.val; } -}; - -// A projection which returns a prvalue of an unrelated type, so nothing links the projected type -// back to the element type. -struct read_proj_result -{ - int val; -}; - -struct read_proj -{ - read_proj_result operator()(const read_archetype& __v) const { return read_proj_result{__v.val}; } - read_proj_result operator()(const read_archetype_dc& __v) const { return read_proj_result{__v.val}; } -}; - -struct read_proj_pred -{ - bool operator()(const read_proj_result& __v) const { return __v.val % 3 == 0; } -}; - -using read_iterator_t = std::ranges::iterator_t>; - -static_assert(std::indirectly_unary_invocable); -static_assert(std::indirect_unary_predicate); -static_assert(std::indirect_binary_predicate); -static_assert(std::indirect_strict_weak_order); -static_assert(std::indirect_unary_predicate>); - -// Family 2: algorithms taking a search value. -// The constraint is -// std::indirect_binary_predicate, _Proj>, -// const _T*> -// std::ranges::equal_to is itself constrained by std::equality_comparable_with, which is much -// stronger than a bare `element == value`: both types have to be equality comparable with -// themselves and to share a common reference type. The archetypes below provide exactly that and -// nothing else, in particular they are still neither copyable nor movable. -// Used by: find, find_last, count, contains, remove, remove_copy, replace, replace_copy. -// The value is passed to a device kernel by copy, so, unlike the other archetypes, it has to be -// trivially copyable and thus device copyable. Everything else a "regular" type provides is still -// missing: no default constructor, no ordering, no relation to the element type but equality. -struct nocopy_search_value; - -struct search_value -{ - int val; - - explicit search_value(int __v) : val(__v) {} - - search_value(const search_value&) = default; - search_value& operator=(const search_value&) = default; - - friend bool operator==(const search_value& __v1, const search_value& __v2) { return __v1.val == __v2.val; } -}; - -struct searchable_archetype -{ - int val; - - explicit searchable_archetype(int __v) : val(__v) {} - - TEST_ARCHETYPE_DELETED_OPERATIONS(searchable_archetype) - - friend bool operator==(const searchable_archetype& __e1, const searchable_archetype& __e2) - { - return __e1.val == __e2.val; - } - - friend bool operator==(const searchable_archetype& __e, const search_value& __v) { return __e.val == __v.val; } - - friend bool operator==(const searchable_archetype& __e, const nocopy_search_value& __v); -}; - -// Family 2b: the very same constraint, but the search value is neither copyable nor movable. -// std::indirect_binary_predicate, _Proj>, -// const _T*> says nothing about copying _T, so a host policy must keep a reference to the value -// instead of storing a copy of it. A device policy legitimately copies the value into the kernel, -// so this archetype is only ever used with the host policies. -struct nocopy_search_value -{ - int val; - - explicit nocopy_search_value(int __v) : val(__v) {} - - TEST_ARCHETYPE_DELETED_OPERATIONS(nocopy_search_value) - - friend bool operator==(const nocopy_search_value& __v1, const nocopy_search_value& __v2) - { - return __v1.val == __v2.val; - } -}; - -inline bool -operator==(const searchable_archetype& __e, const nocopy_search_value& __v) -{ - return __e.val == __v.val; -} - -// The device copyable counterpart of nocopy_search_value: a device policy copies the value into the -// kernel, so the value used with the hetero policies has to be trivially copyable. Everything else -// stays as restricted as in the host only type: no default constructor, no ordering, no relation to -// the element type but equality. -struct nocopy_search_value_dc -{ - int val; - - explicit nocopy_search_value_dc(int __v) : val(__v) {} - - nocopy_search_value_dc(const nocopy_search_value_dc&) = default; - nocopy_search_value_dc& operator=(const nocopy_search_value_dc&) = default; - - friend bool operator==(const nocopy_search_value_dc& __v1, const nocopy_search_value_dc& __v2) - { - return __v1.val == __v2.val; - } -}; - -TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(nocopy_search_value_dc) -static_assert(!std::default_initializable); -static_assert(!std::totally_ordered); - -// The element archetype of the removing algorithms. remove() requires -// std::permutable> && indirect_binary_predicate -// so the element has to be movable, but still not copyable and not default constructible. -struct removable_archetype -{ - int val; - - explicit removable_archetype(int __v) : val(__v) {} - - removable_archetype(removable_archetype&& __other) : val(__other.val) {} - - removable_archetype& - operator=(removable_archetype&& __other) - { - val = __other.val; - return *this; - } - - removable_archetype(const removable_archetype&) = delete; - removable_archetype& operator=(const removable_archetype&) = delete; - TEST_ARCHETYPE_DELETED_ADDRESSOF - - friend bool operator==(const removable_archetype& __e1, const removable_archetype& __e2) - { - return __e1.val == __e2.val; - } - - friend bool operator==(const removable_archetype& __e, const nocopy_search_value& __v) - { - return __e.val == __v.val; - } - - friend bool operator==(const removable_archetype& __e, const search_value& __v) { return __e.val == __v.val; } -}; - -// The device copyable counterparts of the two archetypes above, used with the hetero policies. -// They are trivially copyable and thus device copyable by default; nothing else is added. -struct searchable_archetype_dc -{ - int val; - - explicit searchable_archetype_dc(int __v) : val(__v) {} - - TEST_ARCHETYPE_DEFAULTED_OPERATIONS(searchable_archetype_dc) - - friend bool operator==(const searchable_archetype_dc& __e1, const searchable_archetype_dc& __e2) - { - return __e1.val == __e2.val; - } - - friend bool operator==(const searchable_archetype_dc& __e, const search_value& __v) { return __e.val == __v.val; } - - friend bool operator==(const searchable_archetype_dc& __e, const nocopy_search_value& __v) - { - return __e.val == __v.val; - } - - friend bool operator==(const searchable_archetype_dc& __e, const nocopy_search_value_dc& __v) - { - return __e.val == __v.val; - } -}; - -struct removable_archetype_dc -{ - int val; - - explicit removable_archetype_dc(int __v) : val(__v) {} - - TEST_ARCHETYPE_DEFAULTED_OPERATIONS(removable_archetype_dc) - - friend bool operator==(const removable_archetype_dc& __e1, const removable_archetype_dc& __e2) - { - return __e1.val == __e2.val; - } - - friend bool operator==(const removable_archetype_dc& __e, const search_value& __v) { return __e.val == __v.val; } - - friend bool operator==(const removable_archetype_dc& __e, const nocopy_search_value& __v) - { - return __e.val == __v.val; - } - - friend bool operator==(const removable_archetype_dc& __e, const nocopy_search_value_dc& __v) - { - return __e.val == __v.val; - } -}; - -// The common reference required by std::equality_comparable_with. It is only ever formed as a -// reference by the concept machinery, so a minimal type which both archetypes convert to is enough. -struct search_common -{ - int val; - - search_common(const searchable_archetype& __e) : val(__e.val) {} - search_common(const removable_archetype& __e) : val(__e.val) {} - search_common(const searchable_archetype_dc& __e) : val(__e.val) {} - search_common(const removable_archetype_dc& __e) : val(__e.val) {} - search_common(const search_value& __v) : val(__v.val) {} - search_common(const nocopy_search_value& __v) : val(__v.val) {} - search_common(const nocopy_search_value_dc& __v) : val(__v.val) {} - - friend bool operator==(const search_common& __v1, const search_common& __v2) { return __v1.val == __v2.val; } -}; - -} // namespace archetypes -} // namespace test_std_ranges - -namespace std -{ -template <> -struct common_type -{ - using type = test_std_ranges::archetypes::search_common; -}; - -template <> -struct common_type -{ - using type = test_std_ranges::archetypes::search_common; -}; - -template <> -struct common_type -{ - using type = test_std_ranges::archetypes::search_common; -}; - -template <> -struct common_type -{ - using type = test_std_ranges::archetypes::search_common; -}; - -template <> -struct common_type -{ - using type = test_std_ranges::archetypes::search_common; -}; - -template <> -struct common_type -{ - using type = test_std_ranges::archetypes::search_common; -}; - -template <> -struct common_type -{ - using type = test_std_ranges::archetypes::search_common; -}; - -template <> -struct common_type -{ - using type = test_std_ranges::archetypes::search_common; -}; - -template <> -struct common_type -{ - using type = test_std_ranges::archetypes::search_common; -}; - -template <> -struct common_type -{ - using type = test_std_ranges::archetypes::search_common; -}; - -template <> -struct common_type -{ - using type = test_std_ranges::archetypes::search_common; -}; - -template <> -struct common_type -{ - using type = test_std_ranges::archetypes::search_common; -}; - -template <> -struct common_type -{ - using type = test_std_ranges::archetypes::search_common; -}; - -template <> -struct common_type -{ - using type = test_std_ranges::archetypes::search_common; -}; - -template <> -struct common_type -{ - using type = test_std_ranges::archetypes::search_common; -}; - -template <> -struct common_type -{ - using type = test_std_ranges::archetypes::search_common; -}; - -template <> -struct common_type -{ - using type = test_std_ranges::archetypes::search_common; -}; - -template <> -struct common_type -{ - using type = test_std_ranges::archetypes::search_common; -}; - -template <> -struct common_type -{ - using type = test_std_ranges::archetypes::search_common; -}; - -template <> -struct common_type -{ - using type = test_std_ranges::archetypes::search_common; -}; -} // namespace std - -namespace test_std_ranges -{ -namespace archetypes -{ - -using searchable_iterator_t = std::ranges::iterator_t>; - -static_assert(std::indirect_binary_predicate); -static_assert( - std::indirect_binary_predicate); -static_assert(!std::copy_constructible); -static_assert(!std::move_constructible); -static_assert(!std::default_initializable); - -using removable_iterator_t = std::ranges::iterator_t>; - -static_assert(std::permutable); -static_assert(std::indirect_binary_predicate); -static_assert(std::indirect_binary_predicate); -static_assert(!std::copy_constructible); -static_assert(!std::default_initializable); -static_assert(!std::totally_ordered); -static_assert(!std::copy_constructible); -static_assert(!std::move_constructible); -static_assert(std::is_trivially_copyable_v); -static_assert(!std::default_initializable); -static_assert(!std::totally_ordered); -static_assert(!std::default_initializable); -static_assert(!std::totally_ordered); - -using searchable_dc_iterator_t = std::ranges::iterator_t>; -using removable_dc_iterator_t = std::ranges::iterator_t>; - -TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(searchable_archetype_dc) -TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(removable_archetype_dc) -static_assert(std::indirect_binary_predicate); -static_assert( - std::indirect_binary_predicate); -static_assert( - std::indirect_binary_predicate); -static_assert(std::permutable); -static_assert(std::indirect_binary_predicate); -static_assert( - std::indirect_binary_predicate); -static_assert(!std::default_initializable); -static_assert(!std::default_initializable); -static_assert(!std::totally_ordered); -static_assert(!std::totally_ordered); - -// Family 3: two-range algorithms constrained by std::indirectly_comparable. -// std::indirectly_comparable only asks for the predicate to be -// invocable on the two projected references, so the two element types stay unrelated and neither of -// them is comparable with itself. -// Used by: equal, mismatch, search, find_end, find_first_of, contains_subrange, starts_with, -// ends_with. -struct lhs_archetype -{ - int val; - - explicit lhs_archetype(int __v) : val(__v) {} - - TEST_ARCHETYPE_DELETED_OPERATIONS(lhs_archetype) -}; - -struct rhs_archetype -{ - int val; - - explicit rhs_archetype(int __v) : val(__v) {} - - TEST_ARCHETYPE_DELETED_OPERATIONS(rhs_archetype) -}; - -// The device copyable counterparts of the two archetypes above, used with the hetero policies. -struct lhs_archetype_dc -{ - int val; - - explicit lhs_archetype_dc(int __v) : val(__v) {} - - TEST_ARCHETYPE_DEFAULTED_OPERATIONS(lhs_archetype_dc) -}; - -struct rhs_archetype_dc -{ - int val; - - explicit rhs_archetype_dc(int __v) : val(__v) {} - - TEST_ARCHETYPE_DEFAULTED_OPERATIONS(rhs_archetype_dc) -}; - -TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(lhs_archetype_dc) -TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(rhs_archetype_dc) -static_assert(!std::equality_comparable); -static_assert(!std::equality_comparable); - -struct cross_pred -{ - bool operator()(const lhs_archetype& __v1, const rhs_archetype& __v2) const { return __v1.val == __v2.val; } - bool operator()(const lhs_archetype_dc& __v1, const rhs_archetype_dc& __v2) const { return __v1.val == __v2.val; } -}; - -// includes is constrained by std::indirect_strict_weak_order over the two projected iterators, which -// subsumes std::relation and therefore asks for the two element types in all four combinations, not -// only for (lhs, rhs) the way std::indirectly_comparable does for cross_pred above. -struct cross_comp -{ - bool operator()(const lhs_archetype& __v1, const lhs_archetype& __v2) const { return __v1.val < __v2.val; } - bool operator()(const lhs_archetype& __v1, const rhs_archetype& __v2) const { return __v1.val < __v2.val; } - bool operator()(const rhs_archetype& __v1, const lhs_archetype& __v2) const { return __v1.val < __v2.val; } - bool operator()(const rhs_archetype& __v1, const rhs_archetype& __v2) const { return __v1.val < __v2.val; } - - bool operator()(const lhs_archetype_dc& __v1, const lhs_archetype_dc& __v2) const { return __v1.val < __v2.val; } - bool operator()(const lhs_archetype_dc& __v1, const rhs_archetype_dc& __v2) const { return __v1.val < __v2.val; } - bool operator()(const rhs_archetype_dc& __v1, const lhs_archetype_dc& __v2) const { return __v1.val < __v2.val; } - bool operator()(const rhs_archetype_dc& __v1, const rhs_archetype_dc& __v2) const { return __v1.val < __v2.val; } -}; - -using lhs_iterator_t = std::ranges::iterator_t>; -using rhs_iterator_t = std::ranges::iterator_t>; - -static_assert(std::indirectly_comparable); -static_assert(std::indirect_strict_weak_order); -static_assert(std::indirect_strict_weak_order>, - std::ranges::iterator_t>>); -static_assert(std::indirectly_comparable>, - std::ranges::iterator_t>, cross_pred>); -static_assert(!std::equality_comparable); -static_assert(!std::equality_comparable); -static_assert(!std::copy_constructible); -static_assert(!std::copy_constructible); - -// Family 4: algorithms writing a value into the range itself. -// The constraint is std::indirectly_writable, const _T&>, which needs `*it = value` -// for a const lvalue value and nothing else: the element still does not have to be copyable, -// movable or default constructible, and _T stays an unrelated type. -// Used by: fill, replace_if, replace (new value), replace_copy_if / replace_copy (new value). -struct write_value -{ - int val; - - explicit write_value(int __v) : val(__v) {} - - TEST_ARCHETYPE_DELETED_OPERATIONS(write_value) -}; - -// The device copyable counterpart of write_value: a value argument is passed to a device kernel by -// copy, so the hetero policies need a trivially copyable one. -struct write_value_dc -{ - int val; - - explicit write_value_dc(int __v) : val(__v) {} - - TEST_ARCHETYPE_DEFAULTED_OPERATIONS(write_value_dc) -}; - -TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(write_value_dc) - -struct writable_archetype -{ - int val; - - // The value type the algorithm has to be called with, so that a generic test body may pick the - // right one for the element type it works on. - using value_arg = write_value; - - explicit writable_archetype(int __v) : val(__v) {} - - writable_archetype(const writable_archetype&) = delete; - writable_archetype(writable_archetype&&) = delete; - writable_archetype& operator=(const writable_archetype&) = delete; - writable_archetype& operator=(writable_archetype&&) = delete; - TEST_ARCHETYPE_DELETED_ADDRESSOF - - writable_archetype& operator=(const write_value& __v) - { - val = __v.val; - return *this; - } -}; - -struct writable_archetype_dc -{ - int val; - - using value_arg = write_value_dc; - - explicit writable_archetype_dc(int __v) : val(__v) {} - - TEST_ARCHETYPE_DEFAULTED_OPERATIONS(writable_archetype_dc) - - writable_archetype_dc& operator=(const write_value_dc& __v) - { - val = __v.val; - return *this; - } -}; - -TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(writable_archetype_dc) - -using writable_iterator_t = std::ranges::iterator_t>; - -static_assert(std::indirectly_writable); -static_assert(std::indirectly_writable>, - const write_value_dc&>); -static_assert(!std::default_initializable); -static_assert(!std::copyable); -static_assert(!std::movable); -static_assert(!std::default_initializable); - -// Family 5: copying algorithms. -// std::indirectly_copyable == indirectly_readable && indirectly_writable>, so the output element only has to be assignable from a non-const lvalue of -// the input element type. Neither element type has to be copyable, movable or default -// constructible, and the two types are deliberately different. -// Used by: copy, copy_if, reverse_copy, rotate_copy, remove_copy, remove_copy_if, unique_copy, -// replace_copy, replace_copy_if, partition_copy, partial_sort_copy. -struct copy_in_archetype -{ - int val; - - explicit copy_in_archetype(int __v) : val(__v) {} - - TEST_ARCHETYPE_DELETED_OPERATIONS(copy_in_archetype) -}; - -struct copy_out_archetype -{ - int val; - - explicit copy_out_archetype(int __v) : val(__v) {} - - copy_out_archetype(const copy_out_archetype&) = delete; - copy_out_archetype(copy_out_archetype&&) = delete; - copy_out_archetype& operator=(const copy_out_archetype&) = delete; - copy_out_archetype& operator=(copy_out_archetype&&) = delete; - TEST_ARCHETYPE_DELETED_ADDRESSOF - - copy_out_archetype& operator=(copy_in_archetype& __v) - { - val = __v.val; - return *this; - } -}; - -// The device copyable counterparts of the two archetypes above, used with the hetero policies. -struct copy_in_archetype_dc -{ - int val; - - explicit copy_in_archetype_dc(int __v) : val(__v) {} - - TEST_ARCHETYPE_DEFAULTED_OPERATIONS(copy_in_archetype_dc) -}; - -struct copy_out_archetype_dc -{ - int val; - - explicit copy_out_archetype_dc(int __v) : val(__v) {} - - TEST_ARCHETYPE_DEFAULTED_OPERATIONS(copy_out_archetype_dc) - - copy_out_archetype_dc& operator=(copy_in_archetype_dc& __v) - { - val = __v.val; - return *this; - } -}; - -TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(copy_in_archetype_dc) -TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(copy_out_archetype_dc) -static_assert(std::indirectly_copyable>, - std::ranges::iterator_t>>); -static_assert(!std::default_initializable); - -using copy_in_iterator_t = std::ranges::iterator_t>; -using copy_out_iterator_t = std::ranges::iterator_t>; - -static_assert(std::indirectly_copyable); -static_assert(!std::copyable); -static_assert(!std::copyable); -static_assert(!std::default_initializable); - -// Family 6: the move algorithm. -// std::indirectly_movable asks for indirectly_writable>, -// so the output element is only assignable from an rvalue of the input element type: an -// implementation which copies instead of moving does not compile. -struct move_in_archetype -{ - int val; - - explicit move_in_archetype(int __v) : val(__v) {} - - TEST_ARCHETYPE_DELETED_OPERATIONS(move_in_archetype) -}; - -struct move_out_archetype -{ - int val; - - explicit move_out_archetype(int __v) : val(__v) {} - - move_out_archetype(const move_out_archetype&) = delete; - move_out_archetype(move_out_archetype&&) = delete; - move_out_archetype& operator=(const move_out_archetype&) = delete; - move_out_archetype& operator=(move_out_archetype&&) = delete; - TEST_ARCHETYPE_DELETED_ADDRESSOF - - move_out_archetype& operator=(move_in_archetype&& __v) - { - val = __v.val; - return *this; - } -}; - -// The device copyable counterparts of the two archetypes above, used with the hetero policies. The -// assignment from a non-const lvalue of the input type is still missing, so an implementation which -// copies instead of moving does not compile either. -struct move_in_archetype_dc -{ - int val; - - explicit move_in_archetype_dc(int __v) : val(__v) {} - - TEST_ARCHETYPE_DEFAULTED_OPERATIONS(move_in_archetype_dc) -}; - -struct move_out_archetype_dc -{ - int val; - - explicit move_out_archetype_dc(int __v) : val(__v) {} - - TEST_ARCHETYPE_DEFAULTED_OPERATIONS(move_out_archetype_dc) - - move_out_archetype_dc& operator=(move_in_archetype_dc&& __v) - { - val = __v.val; - return *this; - } -}; - -TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(move_in_archetype_dc) -TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(move_out_archetype_dc) -static_assert(std::indirectly_movable>, - std::ranges::iterator_t>>); -static_assert(!std::indirectly_copyable>, - std::ranges::iterator_t>>); - -using move_in_iterator_t = std::ranges::iterator_t>; -using move_out_iterator_t = std::ranges::iterator_t>; - -static_assert(std::indirectly_movable); -// An lvalue is explicitly rejected, so copying instead of moving is a compilation error. -static_assert(!std::indirectly_copyable); -static_assert(!std::movable); - -// Family 7: swap_ranges. -// std::indirectly_swappable needs std::ranges::swap on the two references, both ways. A -// dedicated hidden-friend swap is provided, so the element does not have to be move constructible -// or move assignable, which is what the fallback std::swap would require. -struct swap_archetype -{ - int val; - - explicit swap_archetype(int __v) : val(__v) {} - - TEST_ARCHETYPE_DELETED_OPERATIONS(swap_archetype) - - friend void swap(swap_archetype& __v1, swap_archetype& __v2) - { - const int __tmp = __v1.val; - __v1.val = __v2.val; - __v2.val = __tmp; - } -}; - -// The device copyable counterpart of the archetype above, used with the hetero policies. The -// dedicated swap is kept, so the algorithm still has to go through std::ranges::swap. -struct swap_archetype_dc -{ - int val; - - explicit swap_archetype_dc(int __v) : val(__v) {} - - TEST_ARCHETYPE_DEFAULTED_OPERATIONS(swap_archetype_dc) - - friend void swap(swap_archetype_dc& __v1, swap_archetype_dc& __v2) - { - const int __tmp = __v1.val; - __v1.val = __v2.val; - __v2.val = __tmp; - } -}; - -TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(swap_archetype_dc) -static_assert(!std::default_initializable); - -using swap_iterator_t = std::ranges::iterator_t>; -static_assert(std::indirectly_swappable); -static_assert(!std::movable); -static_assert(!std::move_constructible); -static_assert(!std::default_initializable); - -// Family 8: transform. -// The output constraint is -// std::indirectly_writable, std::indirect_result_t<_F&, projected...>> -// so the output element is only assignable from the result of the functor, which is a third, -// unrelated type. _F itself is only required to be std::copy_constructible. -struct transform_in_archetype -{ - int val; - - explicit transform_in_archetype(int __v) : val(__v) {} - - TEST_ARCHETYPE_DELETED_OPERATIONS(transform_in_archetype) -}; - -// The result of the functor. indirectly_writable requires the assignment to work for the prvalue, -// the const lvalue and the const rvalue forms of the result type, which a prvalue-returning functor -// naturally provides. -struct transform_result -{ - int val; -}; - -struct transform_out_archetype -{ - int val; - - explicit transform_out_archetype(int __v) : val(__v) {} - - transform_out_archetype(const transform_out_archetype&) = delete; - transform_out_archetype(transform_out_archetype&&) = delete; - transform_out_archetype& operator=(const transform_out_archetype&) = delete; - transform_out_archetype& operator=(transform_out_archetype&&) = delete; - TEST_ARCHETYPE_DELETED_ADDRESSOF - - transform_out_archetype& operator=(const transform_result& __v) - { - val = __v.val; - return *this; - } -}; - -// The device copyable counterparts of the two archetypes above, used with the hetero policies. -struct transform_in_archetype_dc -{ - int val; - - explicit transform_in_archetype_dc(int __v) : val(__v) {} - - TEST_ARCHETYPE_DEFAULTED_OPERATIONS(transform_in_archetype_dc) -}; - -struct transform_out_archetype_dc -{ - int val; - - explicit transform_out_archetype_dc(int __v) : val(__v) {} - - TEST_ARCHETYPE_DEFAULTED_OPERATIONS(transform_out_archetype_dc) - - transform_out_archetype_dc& operator=(const transform_result& __v) - { - val = __v.val; - return *this; - } -}; - -TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(transform_in_archetype_dc) -TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(transform_out_archetype_dc) -static_assert(!std::default_initializable); - -struct transform_unary_op -{ - transform_result operator()(const transform_in_archetype& __v) const { return transform_result{__v.val * 2}; } - transform_result operator()(const transform_in_archetype_dc& __v) const { return transform_result{__v.val * 2}; } -}; - -struct transform_binary_op -{ - transform_result operator()(const transform_in_archetype& __v1, const transform_in_archetype& __v2) const - { - return transform_result{__v1.val + __v2.val}; - } - transform_result operator()(const transform_in_archetype_dc& __v1, const transform_in_archetype_dc& __v2) const - { - return transform_result{__v1.val + __v2.val}; - } -}; - -using transform_in_iterator_t = std::ranges::iterator_t>; -using transform_out_iterator_t = std::ranges::iterator_t>; - -static_assert(std::copy_constructible); -static_assert(std::copy_constructible); -static_assert(std::indirectly_writable>); -static_assert(std::indirectly_writable< - transform_out_iterator_t, - std::indirect_result_t>); -static_assert(!std::copyable); -static_assert(!std::default_initializable); - -// Both transform overloads project their input before invoking the functor, and the requires-clause -// spells the functor over std::projected, so the functor never sees the element itself. The -// projection returns yet another unrelated type: an implementation which applies the functor to the -// element, or writes the projected value into the output, does not compile. -struct transform_proj_result -{ - int val; -}; - -struct transform_proj -{ - transform_proj_result operator()(const transform_in_archetype& __v) const - { - return transform_proj_result{__v.val + 1}; - } - transform_proj_result operator()(const transform_in_archetype_dc& __v) const - { - return transform_proj_result{__v.val + 1}; - } -}; - -struct transform_projected_unary_op -{ - transform_result operator()(const transform_proj_result& __v) const { return transform_result{__v.val * 2}; } -}; - -struct transform_projected_binary_op -{ - transform_result operator()(const transform_proj_result& __v1, const transform_proj_result& __v2) const - { - return transform_result{__v1.val + __v2.val}; - } -}; - -using transform_projected_iterator_t = std::projected; - -static_assert(std::copy_constructible); -static_assert(std::indirectly_regular_unary_invocable); -static_assert(std::indirectly_writable< - transform_out_iterator_t, - std::indirect_result_t>); -static_assert(std::indirectly_writable>); -// The projected functors reject the element type, and the output element rejects the projected -// value, so neither the projection nor the functor can be skipped by the implementation. -static_assert(!std::invocable); -static_assert(!std::invocable); -static_assert(!std::indirectly_writable); - -// Family 9: permuting algorithms. -// std::permutable == forward_iterator && indirectly_movable_storable && -// indirectly_swappable, which does require the element to be movable and move -// constructible, but still not copyable, not default constructible and not comparable. -// Used by: reverse, rotate, shift_left, shift_right, remove_if, remove, unique, partition, -// stable_partition. -struct permutable_archetype -{ - int val; - - explicit permutable_archetype(int __v) : val(__v) {} - - permutable_archetype(permutable_archetype&& __other) : val(__other.val) {} - - permutable_archetype& operator=(permutable_archetype&& __other) - { - val = __other.val; - return *this; - } - - permutable_archetype(const permutable_archetype&) = delete; - permutable_archetype& operator=(const permutable_archetype&) = delete; - TEST_ARCHETYPE_DELETED_ADDRESSOF -}; - -// The device copyable counterpart of the archetype above, used with the hetero policies. -struct permutable_archetype_dc -{ - int val; - - explicit permutable_archetype_dc(int __v) : val(__v) {} - - TEST_ARCHETYPE_DEFAULTED_OPERATIONS(permutable_archetype_dc) -}; - -TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(permutable_archetype_dc) -static_assert(!std::default_initializable); -static_assert(!std::equality_comparable); -static_assert(!std::totally_ordered); - -using permutable_iterator_t = std::ranges::iterator_t>; -using permutable_dc_iterator_t = std::ranges::iterator_t>; - -static_assert(std::permutable); -static_assert(!std::copy_constructible); -static_assert(!std::default_initializable); -static_assert(!std::equality_comparable); -static_assert(!std::totally_ordered); - -// The predicate and the comparator of the permuting algorithms only see the projected reference. -struct permutable_pred -{ - bool operator()(const permutable_archetype& __v) const { return __v.val % 3 == 0; } - bool operator()(const permutable_archetype_dc& __v) const { return __v.val % 3 == 0; } -}; - -struct permutable_equiv -{ - bool operator()(const permutable_archetype& __v1, const permutable_archetype& __v2) const - { - return __v1.val == __v2.val; - } - bool operator()(const permutable_archetype_dc& __v1, const permutable_archetype_dc& __v2) const - { - return __v1.val == __v2.val; - } -}; - -// std::sortable == permutable && indirect_strict_weak_order<_Comp, -// projected>, so the very same element archetype works and the ordering has to come from -// the comparator, never from an operator< on the element. -// Used by: sort, stable_sort, partial_sort, inplace_merge, nth_element, partial_sort_copy. -struct permutable_comp -{ - bool operator()(const permutable_archetype& __v1, const permutable_archetype& __v2) const - { - return __v1.val < __v2.val; - } - bool operator()(const permutable_archetype_dc& __v1, const permutable_archetype_dc& __v2) const - { - return __v1.val < __v2.val; - } -}; - -static_assert(std::sortable); -static_assert(std::permutable); -static_assert(std::sortable); - -// The merge family additionally needs std::indirectly_copyable from both inputs into the output. -// The output element is therefore assignable from a non-const lvalue of either input element type, -// while remaining non-copyable itself. -// Used by: merge, set_union, set_intersection, set_difference, set_symmetric_difference. -struct merge_out_archetype; - -struct merge_in_archetype -{ - int val; - - // The output element type the algorithm has to be called with, so that a generic test body may - // pick the right one for the input element type it works on. - using out_type = merge_out_archetype; - - explicit merge_in_archetype(int __v) : val(__v) {} - - merge_in_archetype(merge_in_archetype&& __other) : val(__other.val) {} - - merge_in_archetype& operator=(merge_in_archetype&& __other) - { - val = __other.val; - return *this; - } - - merge_in_archetype(const merge_in_archetype&) = delete; - merge_in_archetype& operator=(const merge_in_archetype&) = delete; - TEST_ARCHETYPE_DELETED_ADDRESSOF -}; - -struct merge_out_archetype -{ - int val; - - explicit merge_out_archetype(int __v) : val(__v) {} - - merge_out_archetype(merge_out_archetype&& __other) : val(__other.val) {} - - merge_out_archetype& operator=(merge_out_archetype&& __other) - { - val = __other.val; - return *this; - } - - merge_out_archetype(const merge_out_archetype&) = delete; - merge_out_archetype& operator=(const merge_out_archetype&) = delete; - TEST_ARCHETYPE_DELETED_ADDRESSOF - - merge_out_archetype& operator=(merge_in_archetype& __v) - { - val = __v.val; - return *this; - } -}; - -// The device copyable counterparts of the two archetypes above, used with the hetero policies. -struct merge_out_archetype_dc; - -struct merge_in_archetype_dc -{ - int val; - - using out_type = merge_out_archetype_dc; - - explicit merge_in_archetype_dc(int __v) : val(__v) {} - - TEST_ARCHETYPE_DEFAULTED_OPERATIONS(merge_in_archetype_dc) -}; - -struct merge_out_archetype_dc -{ - int val; - - explicit merge_out_archetype_dc(int __v) : val(__v) {} - - TEST_ARCHETYPE_DEFAULTED_OPERATIONS(merge_out_archetype_dc) - - merge_out_archetype_dc& operator=(merge_in_archetype_dc& __v) - { - val = __v.val; - return *this; - } -}; - -struct merge_comp -{ - bool operator()(const merge_in_archetype& __v1, const merge_in_archetype& __v2) const - { - return __v1.val < __v2.val; - } - bool operator()(const merge_in_archetype_dc& __v1, const merge_in_archetype_dc& __v2) const - { - return __v1.val < __v2.val; - } -}; - -TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(merge_in_archetype_dc) -TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(merge_out_archetype_dc) -static_assert(std::mergeable>, - std::ranges::iterator_t>, - std::ranges::iterator_t>, merge_comp>); -static_assert(!std::default_initializable); - -using merge_in_iterator_t = std::ranges::iterator_t>; -using merge_out_iterator_t = std::ranges::iterator_t>; - -static_assert(std::mergeable); -static_assert(!std::copy_constructible); -static_assert(!std::copy_constructible); -static_assert(!std::default_initializable); - -// min / max / minmax additionally require -// std::indirectly_copyable_storable, range_value_t<_R>*>, which does need a copy -// constructor and copy assignment, but still no default constructor and no ordering operator. -struct storable_archetype -{ - int val; - - explicit storable_archetype(int __v) : val(__v) {} - - storable_archetype(const storable_archetype& __other) : val(__other.val) {} - - storable_archetype& operator=(const storable_archetype& __other) - { - val = __other.val; - return *this; - } - - TEST_ARCHETYPE_DELETED_ADDRESSOF -}; - -// The device copyable counterpart of the archetype above, used with the hetero policies. -struct storable_archetype_dc -{ - int val; - - explicit storable_archetype_dc(int __v) : val(__v) {} - - TEST_ARCHETYPE_DEFAULTED_OPERATIONS(storable_archetype_dc) -}; - -TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(storable_archetype_dc) -static_assert(!std::default_initializable); -static_assert(!std::equality_comparable); -static_assert(!std::totally_ordered); - -struct storable_comp -{ - bool operator()(const storable_archetype& __v1, const storable_archetype& __v2) const - { - return __v1.val < __v2.val; - } - bool operator()(const storable_archetype_dc& __v1, const storable_archetype_dc& __v2) const - { - return __v1.val < __v2.val; - } -}; - -using storable_iterator_t = std::ranges::iterator_t>; - -static_assert(std::indirectly_copyable_storable); -static_assert(std::indirect_strict_weak_order); -static_assert(!std::default_initializable); -static_assert(!std::equality_comparable); -static_assert(!std::totally_ordered); - -static_assert(std::indirectly_copyable_storable>, - storable_archetype_dc*>); - -//------------------------------------------------------------------------------------------------ -// Callables taking their arguments by non-const reference. -// -// std::indirectly_unary_invocable, std::indirect_unary_predicate, std::indirect_binary_predicate, -// std::indirect_strict_weak_order and std::projected are all spelled in terms of iter_value_t<_It>&, -// iter_reference_t<_It> and iter_common_reference_t<_It>. For archetype_view<_T> all three of them -// are _T&, i.e. a non-const lvalue reference, so a callable which accepts nothing but _T& satisfies -// those concepts. The requires-clauses of the algorithms therefore allow such a callable, and an -// implementation which hands a const lvalue, an rvalue or a copy of the element to the user callable -// does not compile with the types below. -// -// The _mut counterparts only add the non-const parameter list; the element archetypes and the -// expected results stay exactly the ones of the corresponding family above. -//------------------------------------------------------------------------------------------------ - -// Family 1: read-only algorithms parameterized by a callable. -struct read_unary_fun_mut -{ - void operator()(read_archetype&) const {} - void operator()(read_archetype_dc&) const {} -}; - -struct read_unary_pred_mut -{ - bool operator()(read_archetype& __v) const { return __v.val % 3 == 0; } - bool operator()(read_archetype_dc& __v) const { return __v.val % 3 == 0; } -}; - -struct read_binary_pred_mut -{ - bool operator()(read_archetype& __v1, read_archetype& __v2) const { return __v1.val == __v2.val; } - bool operator()(read_archetype_dc& __v1, read_archetype_dc& __v2) const { return __v1.val == __v2.val; } -}; - -struct read_comp_mut -{ - bool operator()(read_archetype& __v1, read_archetype& __v2) const { return __v1.val < __v2.val; } - bool operator()(read_archetype_dc& __v1, read_archetype_dc& __v2) const { return __v1.val < __v2.val; } -}; - -// A projection taking the element by non-const reference. Its result is a prvalue of an unrelated -// type, which the pre-existing read_proj_pred consumes: a predicate over a projection cannot take a -// non-const reference itself, because indirect_unary_predicate also requires it to be invocable with -// iter_reference_t of the projected iterator, which is that prvalue. -struct read_proj_mut -{ - read_proj_result operator()(read_archetype& __v) const { return read_proj_result{__v.val}; } - read_proj_result operator()(read_archetype_dc& __v) const { return read_proj_result{__v.val}; } -}; - -static_assert(std::indirectly_unary_invocable); -static_assert(std::indirect_unary_predicate); -static_assert(std::indirect_binary_predicate); -static_assert(std::indirect_strict_weak_order); -static_assert(std::indirect_unary_predicate>); -// The callables really do reject anything but a non-const lvalue of the element type. -static_assert(!std::invocable); -static_assert(!std::invocable); -static_assert(!std::invocable); -static_assert(!std::invocable); - -// Family 2: algorithms taking a search value. The value itself is compared with -// std::ranges::equal_to, so only the projection is a user callable here. The projection returns the -// element by reference, which keeps the equality with the search value as it is in the family above. -struct search_proj_mut -{ - searchable_archetype& operator()(searchable_archetype& __v) const { return __v; } - searchable_archetype_dc& operator()(searchable_archetype_dc& __v) const { return __v; } - removable_archetype& operator()(removable_archetype& __v) const { return __v; } - removable_archetype_dc& operator()(removable_archetype_dc& __v) const { return __v; } -}; - -static_assert(std::indirect_binary_predicate, - const search_value*>); -static_assert(std::indirect_binary_predicate, - const search_value*>); -static_assert(!std::invocable); - -// Family 3: two-range algorithms constrained by std::indirectly_comparable. Both references are -// non-const lvalues, so the predicate may take both of its arguments that way. -struct cross_pred_mut -{ - bool operator()(lhs_archetype& __v1, rhs_archetype& __v2) const { return __v1.val == __v2.val; } - bool operator()(lhs_archetype_dc& __v1, rhs_archetype_dc& __v2) const { return __v1.val == __v2.val; } -}; - -static_assert(std::indirectly_comparable); -static_assert(!std::invocable); - -// The four-combination comparator of includes, see cross_comp: every reference it is handed by -// std::indirect_strict_weak_order is a non-const lvalue as well. -struct cross_comp_mut -{ - bool operator()(lhs_archetype& __v1, lhs_archetype& __v2) const { return __v1.val < __v2.val; } - bool operator()(lhs_archetype& __v1, rhs_archetype& __v2) const { return __v1.val < __v2.val; } - bool operator()(rhs_archetype& __v1, lhs_archetype& __v2) const { return __v1.val < __v2.val; } - bool operator()(rhs_archetype& __v1, rhs_archetype& __v2) const { return __v1.val < __v2.val; } - - bool operator()(lhs_archetype_dc& __v1, lhs_archetype_dc& __v2) const { return __v1.val < __v2.val; } - bool operator()(lhs_archetype_dc& __v1, rhs_archetype_dc& __v2) const { return __v1.val < __v2.val; } - bool operator()(rhs_archetype_dc& __v1, lhs_archetype_dc& __v2) const { return __v1.val < __v2.val; } - bool operator()(rhs_archetype_dc& __v1, rhs_archetype_dc& __v2) const { return __v1.val < __v2.val; } -}; - -static_assert(std::indirect_strict_weak_order); -static_assert(!std::invocable); - -// Family 8: transform. The functor is only required to be std::copy_constructible and invocable with -// the projected reference, which is a non-const lvalue. -struct transform_unary_op_mut -{ - transform_result operator()(transform_in_archetype& __v) const { return transform_result{__v.val * 2}; } - transform_result operator()(transform_in_archetype_dc& __v) const { return transform_result{__v.val * 2}; } -}; - -struct transform_binary_op_mut -{ - transform_result operator()(transform_in_archetype& __v1, transform_in_archetype& __v2) const - { - return transform_result{__v1.val + __v2.val}; - } - transform_result operator()(transform_in_archetype_dc& __v1, transform_in_archetype_dc& __v2) const - { - return transform_result{__v1.val + __v2.val}; - } -}; - -// A projection taking its argument by non-const reference. The functor invoked with the projected -// value cannot do the same: the projection returns a prvalue, which does not bind to a non-const -// lvalue reference, so the projected functors of the const section are reused with this projection. -struct transform_proj_mut -{ - transform_proj_result operator()(transform_in_archetype& __v) const { return transform_proj_result{__v.val + 1}; } - transform_proj_result operator()(transform_in_archetype_dc& __v) const - { - return transform_proj_result{__v.val + 1}; - } -}; - -static_assert(std::indirectly_writable>); -static_assert(!std::invocable); -static_assert(std::indirectly_writable< - transform_out_iterator_t, - std::indirect_result_t>); -static_assert( - !std::invocable); -static_assert(std::indirectly_regular_unary_invocable); -static_assert(!std::invocable); -static_assert(std::indirectly_writable< - transform_out_iterator_t, - std::indirect_result_t>>); - -// Family 9: permuting and sorting algorithms. The element is mutable by definition here, so the -// predicate and the comparator may take it by non-const reference as well. -struct permutable_pred_mut -{ - bool operator()(permutable_archetype& __v) const { return __v.val % 3 == 0; } - bool operator()(permutable_archetype_dc& __v) const { return __v.val % 3 == 0; } -}; - -struct permutable_equiv_mut -{ - bool operator()(permutable_archetype& __v1, permutable_archetype& __v2) const { return __v1.val == __v2.val; } - bool operator()(permutable_archetype_dc& __v1, permutable_archetype_dc& __v2) const - { - return __v1.val == __v2.val; - } -}; - -struct permutable_comp_mut -{ - bool operator()(permutable_archetype& __v1, permutable_archetype& __v2) const { return __v1.val < __v2.val; } - bool operator()(permutable_archetype_dc& __v1, permutable_archetype_dc& __v2) const { return __v1.val < __v2.val; } -}; - -static_assert(std::indirect_unary_predicate); -static_assert(std::indirect_binary_predicate); -static_assert(std::sortable); -static_assert(std::sortable); -static_assert(!std::invocable); - -// The merge family and min / max / minmax, whose comparators are constrained the very same way. -struct merge_comp_mut -{ - bool operator()(merge_in_archetype& __v1, merge_in_archetype& __v2) const { return __v1.val < __v2.val; } - bool operator()(merge_in_archetype_dc& __v1, merge_in_archetype_dc& __v2) const { return __v1.val < __v2.val; } -}; - -struct storable_comp_mut -{ - bool operator()(storable_archetype& __v1, storable_archetype& __v2) const { return __v1.val < __v2.val; } - bool operator()(storable_archetype_dc& __v1, storable_archetype_dc& __v2) const { return __v1.val < __v2.val; } -}; - -static_assert(std::mergeable); -static_assert(std::indirect_strict_weak_order); -static_assert(!std::invocable); -static_assert(!std::invocable); - -} // namespace archetypes -} // namespace test_std_ranges - -#endif // _ENABLE_STD_RANGES_TESTING -#endif // _STD_RANGES_ARCHETYPES_H +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#ifndef _STD_RANGES_ARCHETYPES_H +#define _STD_RANGES_ARCHETYPES_H + +#if _ENABLE_STD_RANGES_TESTING + +//------------------------------------------------------------------------------------------------ +// Archetypes for the algorithms of glue_algorithm_ranges_impl.h +// +// Every algorithm there constrains its range parameters with std::ranges::random_access_range and +// std::ranges::sized_range only; all the remaining requirements are expressed as indirect concepts +// on the iterators. The element archetypes below therefore drop everything a "regular" type would +// have and add back exactly the operations one concept family needs. archetype_view is reused as the +// range, so the ranges are random access and sized but neither contiguous nor common. +//------------------------------------------------------------------------------------------------ + +#include "std_ranges_archetypes_base.h" +#include "std_ranges_archetypes_memory.h" +#include "std_ranges_archetypes_read.h" +#include "std_ranges_archetypes_value.h" +#include "std_ranges_archetypes_write.h" +#include "std_ranges_archetypes_permute.h" +#include "std_ranges_archetypes_merge.h" +#include "std_ranges_archetypes_storable.h" + +#endif // _ENABLE_STD_RANGES_TESTING +#endif // _STD_RANGES_ARCHETYPES_H diff --git a/test/parallel_api/ranges/std_ranges_archetypes_base.h b/test/parallel_api/ranges/std_ranges_archetypes_base.h new file mode 100644 index 00000000000..a4b061c4096 --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_archetypes_base.h @@ -0,0 +1,254 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#ifndef _STD_RANGES_ARCHETYPES_BASE_H +#define _STD_RANGES_ARCHETYPES_BASE_H + +// test_config.h defines both _ENABLE_STD_RANGES_TESTING and TEST_DPCPP_BACKEND_PRESENT, so it has to +// come before the checks below: without it the whole header would silently compile to nothing. +#include "support/test_config.h" + +#if _ENABLE_STD_RANGES_TESTING + +#include +#include +#include +#include +#include +#include +#include + +#if TEST_DPCPP_BACKEND_PRESENT +# include "support/utils_sycl_defs.h" +#endif + +// The types below are "archetypes": each of them satisfies exactly the constraints written in the +// requires-clause of the corresponding oneapi::dpl::ranges algorithm and nothing more. Every +// operation which is not implied by those constraints is explicitly deleted. If an algorithm +// compiles and works with an archetype, the implementation does not silently require more from a +// user type than it declares; otherwise the extra requirement shows up as a compilation error. +// +// Each archetype keeps two observable fields, val1 and val2, so that a test can check which part of +// the raw memory has been written, exactly as the pre-existing Elem/Elem_0 types do. + +// Unary operator& is not required by any constraint, so a conforming implementation has to use +// std::addressof instead of taking the address directly. Define this macro to 0 to relax the +// archetypes if the deleted operator& hides other findings. +#ifndef TEST_ARCHETYPE_DELETE_ADDRESSOF +# define TEST_ARCHETYPE_DELETE_ADDRESSOF 1 +#endif + +#if TEST_ARCHETYPE_DELETE_ADDRESSOF +# define TEST_ARCHETYPE_DELETED_ADDRESSOF void operator&() const = delete; +#else +# define TEST_ARCHETYPE_DELETED_ADDRESSOF +#endif + +// Deletes everything a "regular" type would provide but no constraint of the tested algorithms asks +// for: copying, moving, assignment and taking the address. +#define TEST_ARCHETYPE_DELETED_OPERATIONS(_Name) \ + _Name(const _Name&) = delete; \ + _Name(_Name&&) = delete; \ + _Name& operator=(const _Name&) = delete; \ + _Name& operator=(_Name&&) = delete; \ + TEST_ARCHETYPE_DELETED_ADDRESSOF + +// The device copyable counterpart of TEST_ARCHETYPE_DELETED_OPERATIONS: the copy and the move +// operations are trivial, which makes the type trivially copyable and thus device copyable by +// default, while everything else stays exactly as restricted as in the host only archetype. +#define TEST_ARCHETYPE_DEFAULTED_OPERATIONS(_Name) \ + _Name(const _Name&) = default; \ + _Name(_Name&&) = default; \ + _Name& operator=(const _Name&) = default; \ + _Name& operator=(_Name&&) = default; \ + TEST_ARCHETYPE_DELETED_ADDRESSOF + +// Checks that a device copyable archetype really is accepted by SYCL without an explicit +// sycl::is_device_copyable specialization. +#if TEST_DPCPP_BACKEND_PRESENT +# define TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(_Name) \ + static_assert(std::is_trivially_copyable_v<_Name>); \ + static_assert(sycl::is_device_copyable_v<_Name>); +#else +# define TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(_Name) static_assert(std::is_trivially_copyable_v<_Name>); +#endif + +namespace test_std_ranges +{ +namespace archetypes +{ + +// A random access iterator which is deliberately not a contiguous one. Unlike a pointer, a span +// iterator or a subrange over pointers, it gives the implementation no way to fall back to raw +// pointer arithmetic on the underlying storage. +template +class archetype_iterator +{ + T* ptr = nullptr; + + public: + using iterator_concept = std::random_access_iterator_tag; + using iterator_category = std::random_access_iterator_tag; + using value_type = T; + using difference_type = std::ptrdiff_t; + using reference = T&; + using pointer = T*; + + archetype_iterator() = default; + explicit archetype_iterator(T* p) : ptr(p) {} + + T* base() const { return ptr; } + + reference operator*() const { return *ptr; } + pointer operator->() const { return ptr; } + reference operator[](difference_type n) const { return ptr[n]; } + + archetype_iterator& operator++() { ++ptr; return *this; } + archetype_iterator operator++(int) { auto tmp = *this; ++ptr; return tmp; } + archetype_iterator& operator--() { --ptr; return *this; } + archetype_iterator operator--(int) { auto tmp = *this; --ptr; return tmp; } + + archetype_iterator& operator+=(difference_type n) { ptr += n; return *this; } + archetype_iterator& operator-=(difference_type n) { ptr -= n; return *this; } + + friend archetype_iterator operator+(archetype_iterator i, difference_type n) { return i += n; } + friend archetype_iterator operator+(difference_type n, archetype_iterator i) { return i += n; } + friend archetype_iterator operator-(archetype_iterator i, difference_type n) { return i -= n; } + friend difference_type operator-(archetype_iterator i, archetype_iterator j) { return i.ptr - j.ptr; } + + friend bool operator==(archetype_iterator i, archetype_iterator j) { return i.ptr == j.ptr; } + friend auto operator<=>(archetype_iterator i, archetype_iterator j) { return i.ptr <=> j.ptr; } +}; + +// A sentinel type distinct from the iterator, which makes the range non-common while keeping it +// sized via the sized_sentinel_for requirement. +template +class archetype_sentinel +{ + T* ptr = nullptr; + + public: + archetype_sentinel() = default; + explicit archetype_sentinel(T* p) : ptr(p) {} + + T* base() const { return ptr; } + + friend bool operator==(archetype_iterator i, archetype_sentinel s) { return i.base() == s.ptr; } + friend std::ptrdiff_t operator-(archetype_iterator i, archetype_sentinel s) { return i.base() - s.ptr; } + friend std::ptrdiff_t operator-(archetype_sentinel s, archetype_iterator i) { return s.ptr - i.base(); } +}; + +// A view over raw storage which satisfies __nothrow_random_access_range and sized_range, but is +// neither contiguous nor common. It is marked as a borrowed range so that the algorithms keep +// returning a real iterator rather than std::ranges::dangling. +template +class archetype_view : public std::ranges::view_interface> +{ + T* first = nullptr; + T* last = nullptr; + + public: + archetype_view() = default; + archetype_view(T* p, std::size_t n) : first(p), last(p + n) {} + + archetype_iterator begin() const { return archetype_iterator(first); } + archetype_sentinel end() const { return archetype_sentinel(last); } +}; + +} // namespace archetypes +} // namespace test_std_ranges + +template +inline constexpr bool std::ranges::enable_borrowed_range> = true; + +namespace test_std_ranges +{ +namespace archetypes +{ + +static_assert(std::random_access_iterator>); +static_assert(!std::contiguous_iterator>); +static_assert(std::sized_sentinel_for, archetype_iterator>); + +static_assert(std::ranges::random_access_range>); +static_assert(std::ranges::sized_range>); +static_assert(std::ranges::borrowed_range>); +static_assert(!std::ranges::contiguous_range>); +static_assert(!std::ranges::common_range>); + +// The two extra requirements of __nothrow_random_access_range beyond random_access_range. +static_assert(std::is_lvalue_reference_v>>); +static_assert(std::same_as>>, + std::ranges::range_value_t>>); + +// Owns raw storage and constructs the elements in place. The archetypes are neither copyable nor +// movable, so they cannot be kept in a standard container; the allocator is a template parameter so +// that the very same storage works with std::allocator on the host and with sycl::usm_allocator on +// a device. +template +class archetype_storage +{ + Alloc alloc; + std::size_t count = 0; + T* data = nullptr; + + public: + // _Factory is called as __factory(i) for every index and has to return the arguments of the + // element constructor. + template + archetype_storage(Alloc __alloc, std::size_t __n, _Factory __factory) : alloc(__alloc), count(__n) + { + data = alloc.allocate(count); + for (std::size_t __i = 0; __i < count; ++__i) + std::construct_at(data + __i, __factory(__i)); + } + + archetype_storage(const archetype_storage&) = delete; + archetype_storage& operator=(const archetype_storage&) = delete; + + ~archetype_storage() + { + for (std::size_t __i = 0; __i < count; ++__i) + std::destroy_at(data + __i); + alloc.deallocate(data, count); + } + + std::size_t size() const { return count; } + T* begin_ptr() const { return data; } + + archetype_view view() const { return archetype_view(data, count); } +}; + + +//------------------------------------------------------------------------------------------------ +// Callables taking their arguments by non-const reference. +// +// std::indirectly_unary_invocable, std::indirect_unary_predicate, std::indirect_binary_predicate, +// std::indirect_strict_weak_order and std::projected are all spelled in terms of iter_value_t<_It>&, +// iter_reference_t<_It> and iter_common_reference_t<_It>. For archetype_view<_T> all three of them +// are _T&, i.e. a non-const lvalue reference, so a callable which accepts nothing but _T& satisfies +// those concepts. The requires-clauses of the algorithms therefore allow such a callable, and an +// implementation which hands a const lvalue, an rvalue or a copy of the element to the user callable +// does not compile with the types below. +// +// The _mut counterparts only add the non-const parameter list; the element archetypes and the +// expected results stay exactly the ones of the corresponding family above. +//------------------------------------------------------------------------------------------------ + +} // namespace archetypes +} // namespace test_std_ranges + +#endif // _ENABLE_STD_RANGES_TESTING +#endif // _STD_RANGES_ARCHETYPES_BASE_H diff --git a/test/parallel_api/ranges/std_ranges_archetypes_memory.h b/test/parallel_api/ranges/std_ranges_archetypes_memory.h new file mode 100644 index 00000000000..fdc3225d7b4 --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_archetypes_memory.h @@ -0,0 +1,171 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#ifndef _STD_RANGES_ARCHETYPES_MEMORY_H +#define _STD_RANGES_ARCHETYPES_MEMORY_H + +#if _ENABLE_STD_RANGES_TESTING + +#include "std_ranges_archetypes_base.h" + +namespace test_std_ranges +{ +namespace archetypes +{ + +// std::default_initializable, required by uninitialized_default_construct. +// The default constructor is user-provided, so default- and value-initialization are the same and +// val2 is left untouched by the algorithm. +struct default_construct_archetype +{ + int val1; + int val2; + + default_construct_archetype() { val1 = 1; } + + TEST_ARCHETYPE_DELETED_OPERATIONS(default_construct_archetype) +}; + +static_assert(std::default_initializable); +static_assert(std::destructible); +static_assert(!std::copy_constructible); +static_assert(!std::move_constructible); +static_assert(!std::equality_comparable); +static_assert(!std::swappable); + +// std::default_initializable, required by uninitialized_value_construct. +// The default constructor is defaulted on its first declaration and therefore is not user-provided: +// value-initialization zero-initializes the whole object, which lets the test tell value +// construction apart from default construction. +struct value_construct_archetype +{ + int val1; + int val2; + + value_construct_archetype() = default; + + TEST_ARCHETYPE_DELETED_OPERATIONS(value_construct_archetype) +}; + +static_assert(std::default_initializable); +static_assert(std::destructible); +static_assert(!std::copy_constructible); +static_assert(!std::move_constructible); +static_assert(!std::equality_comparable); +static_assert(!std::swappable); + +// The _T template parameter of uninitialized_fill is deduced from the value argument, so the filler +// type is deliberately different from the range value type: the only required conversion is +// std::constructible_from, const fill_source&>. +struct fill_source +{ + int val; +}; + +struct fill_archetype +{ + int val1; + int val2; + + explicit fill_archetype(const fill_source& src) { val2 = src.val; } + + TEST_ARCHETYPE_DELETED_OPERATIONS(fill_archetype) +}; + +static_assert(std::constructible_from); +static_assert(std::destructible); +static_assert(!std::default_initializable); +static_assert(!std::copy_constructible); +static_assert(!std::move_constructible); + +// Input element type of uninitialized_copy and uninitialized_move. No constraint is imposed on it +// besides forming a random access range, so it is only constructible from an int, which is what the +// test harness uses to prepare the input data. +struct transfer_source +{ + int val1; + int val2; + + explicit transfer_source(int v) { val2 = v; } + + TEST_ARCHETYPE_DELETED_OPERATIONS(transfer_source) +}; + +static_assert(std::destructible); +static_assert(!std::default_initializable); +static_assert(!std::copy_constructible); +static_assert(!std::move_constructible); + +// std::constructible_from, range_reference_t<_InRange>>, required by +// uninitialized_copy. range_reference_t of a range of transfer_source is exactly transfer_source&, +// so the implementation must not pass a const lvalue or an rvalue instead. +struct copy_archetype +{ + int val1; + int val2; + + explicit copy_archetype(transfer_source& src) { val2 = src.val2; } + + TEST_ARCHETYPE_DELETED_OPERATIONS(copy_archetype) +}; + +static_assert(std::constructible_from); +static_assert(std::destructible); +static_assert(!std::constructible_from); +static_assert(!std::constructible_from); +static_assert(!std::default_initializable); +static_assert(!std::copy_constructible); + +// std::constructible_from, range_rvalue_reference_t<_InRange>>, required by +// uninitialized_move. Only an rvalue is accepted, so the implementation has to move the source +// element (std::ranges::iter_move) rather than copy it. +struct move_archetype +{ + int val1; + int val2; + + explicit move_archetype(transfer_source&& src) { val2 = src.val2; } + + TEST_ARCHETYPE_DELETED_OPERATIONS(move_archetype) +}; + +static_assert(std::constructible_from); +static_assert(std::destructible); +static_assert(!std::constructible_from); +static_assert(!std::default_initializable); +static_assert(!std::copy_constructible); + +// std::destructible, required by destroy. No constructor at all is declared, which is enough for the +// test: the harness works on raw memory and only observes the effect of the destructor. +struct destroy_archetype +{ + int val1; + volatile int val2; // volatile prevents optimization of the destructor observed with g++ + + ~destroy_archetype() { val2 = 3; } + + TEST_ARCHETYPE_DELETED_OPERATIONS(destroy_archetype) +}; + +static_assert(std::destructible); +static_assert(!std::default_initializable); +static_assert(!std::copy_constructible); +static_assert(!std::move_constructible); + +} // namespace archetypes +} // namespace test_std_ranges + +#endif // _ENABLE_STD_RANGES_TESTING +#endif // _STD_RANGES_ARCHETYPES_MEMORY_H diff --git a/test/parallel_api/ranges/std_ranges_archetypes_merge.h b/test/parallel_api/ranges/std_ranges_archetypes_merge.h new file mode 100644 index 00000000000..a96fe8f4523 --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_archetypes_merge.h @@ -0,0 +1,151 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#ifndef _STD_RANGES_ARCHETYPES_MERGE_H +#define _STD_RANGES_ARCHETYPES_MERGE_H + +#if _ENABLE_STD_RANGES_TESTING + +#include "std_ranges_archetypes_base.h" + +namespace test_std_ranges +{ +namespace archetypes +{ + +// The merge family additionally needs std::indirectly_copyable from both inputs into the output. +// The output element is therefore assignable from a non-const lvalue of either input element type, +// while remaining non-copyable itself. +// Used by: merge, set_union, set_intersection, set_difference, set_symmetric_difference. +struct merge_out_archetype; + +struct merge_in_archetype +{ + int val; + + // The output element type the algorithm has to be called with, so that a generic test body may + // pick the right one for the input element type it works on. + using out_type = merge_out_archetype; + + explicit merge_in_archetype(int __v) : val(__v) {} + + merge_in_archetype(merge_in_archetype&& __other) : val(__other.val) {} + + merge_in_archetype& operator=(merge_in_archetype&& __other) + { + val = __other.val; + return *this; + } + + merge_in_archetype(const merge_in_archetype&) = delete; + merge_in_archetype& operator=(const merge_in_archetype&) = delete; + TEST_ARCHETYPE_DELETED_ADDRESSOF +}; + +struct merge_out_archetype +{ + int val; + + explicit merge_out_archetype(int __v) : val(__v) {} + + merge_out_archetype(merge_out_archetype&& __other) : val(__other.val) {} + + merge_out_archetype& operator=(merge_out_archetype&& __other) + { + val = __other.val; + return *this; + } + + merge_out_archetype(const merge_out_archetype&) = delete; + merge_out_archetype& operator=(const merge_out_archetype&) = delete; + TEST_ARCHETYPE_DELETED_ADDRESSOF + + merge_out_archetype& operator=(merge_in_archetype& __v) + { + val = __v.val; + return *this; + } +}; + +// The device copyable counterparts of the two archetypes above, used with the hetero policies. +struct merge_out_archetype_dc; + +struct merge_in_archetype_dc +{ + int val; + + // The matching output element type, see merge_in_archetype::out_type: one and the same generic test + // body serves the host and the hetero policies, so it derives the output type from the input one. + using out_type = merge_out_archetype_dc; + + explicit merge_in_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(merge_in_archetype_dc) +}; + +struct merge_out_archetype_dc +{ + int val; + + explicit merge_out_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(merge_out_archetype_dc) + + merge_out_archetype_dc& operator=(merge_in_archetype_dc& __v) + { + val = __v.val; + return *this; + } +}; + +struct merge_comp +{ + bool operator()(const merge_in_archetype& __v1, const merge_in_archetype& __v2) const + { + return __v1.val < __v2.val; + } + bool operator()(const merge_in_archetype_dc& __v1, const merge_in_archetype_dc& __v2) const + { + return __v1.val < __v2.val; + } +}; + +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(merge_in_archetype_dc) +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(merge_out_archetype_dc) +static_assert(std::mergeable>, + std::ranges::iterator_t>, + std::ranges::iterator_t>, merge_comp>); +static_assert(!std::default_initializable); + +using merge_in_iterator_t = std::ranges::iterator_t>; +using merge_out_iterator_t = std::ranges::iterator_t>; + +static_assert(std::mergeable); +static_assert(!std::copy_constructible); +static_assert(!std::copy_constructible); +static_assert(!std::default_initializable); + +// The merge family and min / max / minmax, whose comparators are constrained the very same way. +struct merge_comp_mut +{ + bool operator()(merge_in_archetype& __v1, merge_in_archetype& __v2) const { return __v1.val < __v2.val; } + bool operator()(merge_in_archetype_dc& __v1, merge_in_archetype_dc& __v2) const { return __v1.val < __v2.val; } +}; + +} // namespace archetypes +} // namespace test_std_ranges + +#endif // _ENABLE_STD_RANGES_TESTING +#endif // _STD_RANGES_ARCHETYPES_MERGE_H diff --git a/test/parallel_api/ranges/std_ranges_archetypes_permute.h b/test/parallel_api/ranges/std_ranges_archetypes_permute.h new file mode 100644 index 00000000000..98b71cca8b5 --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_archetypes_permute.h @@ -0,0 +1,149 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#ifndef _STD_RANGES_ARCHETYPES_PERMUTE_H +#define _STD_RANGES_ARCHETYPES_PERMUTE_H + +#if _ENABLE_STD_RANGES_TESTING + +#include "std_ranges_archetypes_base.h" + +namespace test_std_ranges +{ +namespace archetypes +{ + +// Family 9: permuting algorithms. +// std::permutable == forward_iterator && indirectly_movable_storable && +// indirectly_swappable, which does require the element to be movable and move +// constructible, but still not copyable, not default constructible and not comparable. +// Used by: reverse, rotate, shift_left, shift_right, remove_if, remove, unique, partition, +// stable_partition. +struct permutable_archetype +{ + int val; + + explicit permutable_archetype(int __v) : val(__v) {} + + permutable_archetype(permutable_archetype&& __other) : val(__other.val) {} + + permutable_archetype& operator=(permutable_archetype&& __other) + { + val = __other.val; + return *this; + } + + permutable_archetype(const permutable_archetype&) = delete; + permutable_archetype& operator=(const permutable_archetype&) = delete; + TEST_ARCHETYPE_DELETED_ADDRESSOF +}; + +// The device copyable counterpart of the archetype above, used with the hetero policies. +struct permutable_archetype_dc +{ + int val; + + explicit permutable_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(permutable_archetype_dc) +}; + +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(permutable_archetype_dc) +static_assert(!std::default_initializable); +static_assert(!std::equality_comparable); +static_assert(!std::totally_ordered); + +using permutable_iterator_t = std::ranges::iterator_t>; +using permutable_dc_iterator_t = std::ranges::iterator_t>; + +static_assert(std::permutable); +static_assert(!std::copy_constructible); +static_assert(!std::default_initializable); +static_assert(!std::equality_comparable); +static_assert(!std::totally_ordered); + +// The predicate and the comparator of the permuting algorithms only see the projected reference. +struct permutable_pred +{ + bool operator()(const permutable_archetype& __v) const { return __v.val % 3 == 0; } + bool operator()(const permutable_archetype_dc& __v) const { return __v.val % 3 == 0; } +}; + +struct permutable_equiv +{ + bool operator()(const permutable_archetype& __v1, const permutable_archetype& __v2) const + { + return __v1.val == __v2.val; + } + bool operator()(const permutable_archetype_dc& __v1, const permutable_archetype_dc& __v2) const + { + return __v1.val == __v2.val; + } +}; + +// std::sortable == permutable && indirect_strict_weak_order<_Comp, +// projected>, so the very same element archetype works and the ordering has to come from +// the comparator, never from an operator< on the element. +// Used by: sort, stable_sort, partial_sort, inplace_merge, nth_element, partial_sort_copy. +struct permutable_comp +{ + bool operator()(const permutable_archetype& __v1, const permutable_archetype& __v2) const + { + return __v1.val < __v2.val; + } + bool operator()(const permutable_archetype_dc& __v1, const permutable_archetype_dc& __v2) const + { + return __v1.val < __v2.val; + } +}; + +static_assert(std::sortable); +static_assert(std::permutable); +static_assert(std::sortable); + +// Family 9: permuting and sorting algorithms. The element is mutable by definition here, so the +// predicate and the comparator may take it by non-const reference as well. +struct permutable_pred_mut +{ + bool operator()(permutable_archetype& __v) const { return __v.val % 3 == 0; } + bool operator()(permutable_archetype_dc& __v) const { return __v.val % 3 == 0; } +}; + +struct permutable_equiv_mut +{ + bool operator()(permutable_archetype& __v1, permutable_archetype& __v2) const { return __v1.val == __v2.val; } + bool operator()(permutable_archetype_dc& __v1, permutable_archetype_dc& __v2) const + { + return __v1.val == __v2.val; + } +}; + +struct permutable_comp_mut +{ + bool operator()(permutable_archetype& __v1, permutable_archetype& __v2) const { return __v1.val < __v2.val; } + bool operator()(permutable_archetype_dc& __v1, permutable_archetype_dc& __v2) const { return __v1.val < __v2.val; } +}; + +static_assert(std::indirect_unary_predicate); +static_assert(std::indirect_binary_predicate); +static_assert(std::sortable); +static_assert(std::sortable); +static_assert(!std::invocable); + +} // namespace archetypes +} // namespace test_std_ranges + +#endif // _ENABLE_STD_RANGES_TESTING +#endif // _STD_RANGES_ARCHETYPES_PERMUTE_H diff --git a/test/parallel_api/ranges/std_ranges_archetypes_read.h b/test/parallel_api/ranges/std_ranges_archetypes_read.h new file mode 100644 index 00000000000..84fd138a9b0 --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_archetypes_read.h @@ -0,0 +1,286 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#ifndef _STD_RANGES_ARCHETYPES_READ_H +#define _STD_RANGES_ARCHETYPES_READ_H + +#if _ENABLE_STD_RANGES_TESTING + +#include "std_ranges_archetypes_base.h" + +namespace test_std_ranges +{ +namespace archetypes +{ + +// Family 1: read-only algorithms parameterized by a callable. +// std::indirectly_unary_invocable / std::indirect_unary_predicate / std::indirect_strict_weak_order / +// std::indirect_equivalence_relation only require the callable to be invocable with the projected +// value; they impose nothing at all on the element type itself. +// Used by: for_each, find_if, find_if_not, find_last_if, find_last_if_not, any_of, all_of, none_of, +// count_if, is_partitioned, adjacent_find, is_sorted, is_sorted_until, is_heap, is_heap_until, +// min_element, max_element, minmax_element, lexicographical_compare, includes. +struct read_archetype +{ + int val; + + explicit read_archetype(int __v) : val(__v) {} + + TEST_ARCHETYPE_DELETED_OPERATIONS(read_archetype) +}; + +static_assert(std::destructible); +static_assert(!std::default_initializable); +static_assert(!std::copy_constructible); +static_assert(!std::move_constructible); +static_assert(!std::equality_comparable); +static_assert(!std::totally_ordered); + +// The device copyable counterpart of read_archetype, used with the hetero policies: it is trivially +// copyable, so a device kernel may take it by value, but it is still not default constructible and +// not comparable. +struct read_archetype_dc +{ + int val; + + explicit read_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(read_archetype_dc) +}; + +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(read_archetype_dc) +static_assert(!std::default_initializable); +static_assert(!std::equality_comparable); +static_assert(!std::totally_ordered); + +// The callables take exactly const _T& and return exactly the required type, so an implementation +// cannot pass an rvalue, a copy, or expect a wider return type. +struct read_unary_fun +{ + void operator()(const read_archetype&) const {} + void operator()(const read_archetype_dc&) const {} +}; + +struct read_unary_pred +{ + bool operator()(const read_archetype& __v) const { return __v.val % 3 == 0; } + bool operator()(const read_archetype_dc& __v) const { return __v.val % 3 == 0; } +}; + +struct read_binary_pred +{ + bool operator()(const read_archetype& __v1, const read_archetype& __v2) const { return __v1.val == __v2.val; } + bool operator()(const read_archetype_dc& __v1, const read_archetype_dc& __v2) const + { + return __v1.val == __v2.val; + } +}; + +struct read_comp +{ + bool operator()(const read_archetype& __v1, const read_archetype& __v2) const { return __v1.val < __v2.val; } + bool operator()(const read_archetype_dc& __v1, const read_archetype_dc& __v2) const { return __v1.val < __v2.val; } +}; + +// A projection which returns a prvalue of an unrelated type, so nothing links the projected type +// back to the element type. +struct read_proj_result +{ + int val; +}; + +struct read_proj +{ + read_proj_result operator()(const read_archetype& __v) const { return read_proj_result{__v.val}; } + read_proj_result operator()(const read_archetype_dc& __v) const { return read_proj_result{__v.val}; } +}; + +struct read_proj_pred +{ + bool operator()(const read_proj_result& __v) const { return __v.val % 3 == 0; } +}; + +using read_iterator_t = std::ranges::iterator_t>; + +static_assert(std::indirectly_unary_invocable); +static_assert(std::indirect_unary_predicate); +static_assert(std::indirect_binary_predicate); +static_assert(std::indirect_strict_weak_order); +static_assert(std::indirect_unary_predicate>); + +// Family 3: two-range algorithms constrained by std::indirectly_comparable. +// std::indirectly_comparable only asks for the predicate to be +// invocable on the two projected references, so the two element types stay unrelated and neither of +// them is comparable with itself. +// Used by: equal, mismatch, search, find_end, find_first_of, contains_subrange, starts_with, +// ends_with. +struct lhs_archetype +{ + int val; + + explicit lhs_archetype(int __v) : val(__v) {} + + TEST_ARCHETYPE_DELETED_OPERATIONS(lhs_archetype) +}; + +struct rhs_archetype +{ + int val; + + explicit rhs_archetype(int __v) : val(__v) {} + + TEST_ARCHETYPE_DELETED_OPERATIONS(rhs_archetype) +}; + +// The device copyable counterparts of the two archetypes above, used with the hetero policies. +struct lhs_archetype_dc +{ + int val; + + explicit lhs_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(lhs_archetype_dc) +}; + +struct rhs_archetype_dc +{ + int val; + + explicit rhs_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(rhs_archetype_dc) +}; + +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(lhs_archetype_dc) +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(rhs_archetype_dc) +static_assert(!std::equality_comparable); +static_assert(!std::equality_comparable); + +struct cross_pred +{ + bool operator()(const lhs_archetype& __v1, const rhs_archetype& __v2) const { return __v1.val == __v2.val; } + bool operator()(const lhs_archetype_dc& __v1, const rhs_archetype_dc& __v2) const { return __v1.val == __v2.val; } +}; + +// includes is constrained by std::indirect_strict_weak_order over the two projected iterators, which +// subsumes std::relation and therefore asks for the two element types in all four combinations, not +// only for (lhs, rhs) the way std::indirectly_comparable does for cross_pred above. +struct cross_comp +{ + bool operator()(const lhs_archetype& __v1, const lhs_archetype& __v2) const { return __v1.val < __v2.val; } + bool operator()(const lhs_archetype& __v1, const rhs_archetype& __v2) const { return __v1.val < __v2.val; } + bool operator()(const rhs_archetype& __v1, const lhs_archetype& __v2) const { return __v1.val < __v2.val; } + bool operator()(const rhs_archetype& __v1, const rhs_archetype& __v2) const { return __v1.val < __v2.val; } + + bool operator()(const lhs_archetype_dc& __v1, const lhs_archetype_dc& __v2) const { return __v1.val < __v2.val; } + bool operator()(const lhs_archetype_dc& __v1, const rhs_archetype_dc& __v2) const { return __v1.val < __v2.val; } + bool operator()(const rhs_archetype_dc& __v1, const lhs_archetype_dc& __v2) const { return __v1.val < __v2.val; } + bool operator()(const rhs_archetype_dc& __v1, const rhs_archetype_dc& __v2) const { return __v1.val < __v2.val; } +}; + +using lhs_iterator_t = std::ranges::iterator_t>; +using rhs_iterator_t = std::ranges::iterator_t>; + +static_assert(std::indirectly_comparable); +static_assert(std::indirect_strict_weak_order); +static_assert(std::indirect_strict_weak_order>, + std::ranges::iterator_t>>); +static_assert(std::indirectly_comparable>, + std::ranges::iterator_t>, cross_pred>); +static_assert(!std::equality_comparable); +static_assert(!std::equality_comparable); +static_assert(!std::copy_constructible); +static_assert(!std::copy_constructible); + +// Family 1: read-only algorithms parameterized by a callable. +struct read_unary_fun_mut +{ + void operator()(read_archetype&) const {} + void operator()(read_archetype_dc&) const {} +}; + +struct read_unary_pred_mut +{ + bool operator()(read_archetype& __v) const { return __v.val % 3 == 0; } + bool operator()(read_archetype_dc& __v) const { return __v.val % 3 == 0; } +}; + +struct read_binary_pred_mut +{ + bool operator()(read_archetype& __v1, read_archetype& __v2) const { return __v1.val == __v2.val; } + bool operator()(read_archetype_dc& __v1, read_archetype_dc& __v2) const { return __v1.val == __v2.val; } +}; + +struct read_comp_mut +{ + bool operator()(read_archetype& __v1, read_archetype& __v2) const { return __v1.val < __v2.val; } + bool operator()(read_archetype_dc& __v1, read_archetype_dc& __v2) const { return __v1.val < __v2.val; } +}; + +// A projection taking the element by non-const reference. Its result is a prvalue of an unrelated +// type, which the pre-existing read_proj_pred consumes: a predicate over a projection cannot take a +// non-const reference itself, because indirect_unary_predicate also requires it to be invocable with +// iter_reference_t of the projected iterator, which is that prvalue. +struct read_proj_mut +{ + read_proj_result operator()(read_archetype& __v) const { return read_proj_result{__v.val}; } + read_proj_result operator()(read_archetype_dc& __v) const { return read_proj_result{__v.val}; } +}; + +static_assert(std::indirectly_unary_invocable); +static_assert(std::indirect_unary_predicate); +static_assert(std::indirect_binary_predicate); +static_assert(std::indirect_strict_weak_order); +static_assert(std::indirect_unary_predicate>); +// The callables really do reject anything but a non-const lvalue of the element type. +static_assert(!std::invocable); +static_assert(!std::invocable); +static_assert(!std::invocable); +static_assert(!std::invocable); + +// Family 3: two-range algorithms constrained by std::indirectly_comparable. Both references are +// non-const lvalues, so the predicate may take both of its arguments that way. +struct cross_pred_mut +{ + bool operator()(lhs_archetype& __v1, rhs_archetype& __v2) const { return __v1.val == __v2.val; } + bool operator()(lhs_archetype_dc& __v1, rhs_archetype_dc& __v2) const { return __v1.val == __v2.val; } +}; + +static_assert(std::indirectly_comparable); +static_assert(!std::invocable); + +// The four-combination comparator of includes, see cross_comp: every reference it is handed by +// std::indirect_strict_weak_order is a non-const lvalue as well. +struct cross_comp_mut +{ + bool operator()(lhs_archetype& __v1, lhs_archetype& __v2) const { return __v1.val < __v2.val; } + bool operator()(lhs_archetype& __v1, rhs_archetype& __v2) const { return __v1.val < __v2.val; } + bool operator()(rhs_archetype& __v1, lhs_archetype& __v2) const { return __v1.val < __v2.val; } + bool operator()(rhs_archetype& __v1, rhs_archetype& __v2) const { return __v1.val < __v2.val; } + + bool operator()(lhs_archetype_dc& __v1, lhs_archetype_dc& __v2) const { return __v1.val < __v2.val; } + bool operator()(lhs_archetype_dc& __v1, rhs_archetype_dc& __v2) const { return __v1.val < __v2.val; } + bool operator()(rhs_archetype_dc& __v1, lhs_archetype_dc& __v2) const { return __v1.val < __v2.val; } + bool operator()(rhs_archetype_dc& __v1, rhs_archetype_dc& __v2) const { return __v1.val < __v2.val; } +}; + +static_assert(std::indirect_strict_weak_order); +static_assert(!std::invocable); + +} // namespace archetypes +} // namespace test_std_ranges + +#endif // _ENABLE_STD_RANGES_TESTING +#endif // _STD_RANGES_ARCHETYPES_READ_H diff --git a/test/parallel_api/ranges/std_ranges_archetypes_storable.h b/test/parallel_api/ranges/std_ranges_archetypes_storable.h new file mode 100644 index 00000000000..d7105cc59fc --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_archetypes_storable.h @@ -0,0 +1,103 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#ifndef _STD_RANGES_ARCHETYPES_STORABLE_H +#define _STD_RANGES_ARCHETYPES_STORABLE_H + +#if _ENABLE_STD_RANGES_TESTING + +#include "std_ranges_archetypes_base.h" +// storable_comp_mut is constrained over merge_in_iterator_t as well, see the assert below. +#include "std_ranges_archetypes_merge.h" + +namespace test_std_ranges +{ +namespace archetypes +{ + +// min / max / minmax additionally require +// std::indirectly_copyable_storable, range_value_t<_R>*>, which does need a copy +// constructor and copy assignment, but still no default constructor and no ordering operator. +struct storable_archetype +{ + int val; + + explicit storable_archetype(int __v) : val(__v) {} + + storable_archetype(const storable_archetype& __other) : val(__other.val) {} + + storable_archetype& operator=(const storable_archetype& __other) + { + val = __other.val; + return *this; + } + + TEST_ARCHETYPE_DELETED_ADDRESSOF +}; + +// The device copyable counterpart of the archetype above, used with the hetero policies. +struct storable_archetype_dc +{ + int val; + + explicit storable_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(storable_archetype_dc) +}; + +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(storable_archetype_dc) +static_assert(!std::default_initializable); +static_assert(!std::equality_comparable); +static_assert(!std::totally_ordered); + +struct storable_comp +{ + bool operator()(const storable_archetype& __v1, const storable_archetype& __v2) const + { + return __v1.val < __v2.val; + } + bool operator()(const storable_archetype_dc& __v1, const storable_archetype_dc& __v2) const + { + return __v1.val < __v2.val; + } +}; + +using storable_iterator_t = std::ranges::iterator_t>; + +static_assert(std::indirectly_copyable_storable); +static_assert(std::indirect_strict_weak_order); +static_assert(!std::default_initializable); +static_assert(!std::equality_comparable); +static_assert(!std::totally_ordered); + +static_assert(std::indirectly_copyable_storable>, + storable_archetype_dc*>); + +struct storable_comp_mut +{ + bool operator()(storable_archetype& __v1, storable_archetype& __v2) const { return __v1.val < __v2.val; } + bool operator()(storable_archetype_dc& __v1, storable_archetype_dc& __v2) const { return __v1.val < __v2.val; } +}; + +static_assert(std::mergeable); +static_assert(std::indirect_strict_weak_order); +static_assert(!std::invocable); +static_assert(!std::invocable); + +} // namespace archetypes +} // namespace test_std_ranges + +#endif // _ENABLE_STD_RANGES_TESTING +#endif // _STD_RANGES_ARCHETYPES_STORABLE_H diff --git a/test/parallel_api/ranges/std_ranges_archetypes_value.h b/test/parallel_api/ranges/std_ranges_archetypes_value.h new file mode 100644 index 00000000000..7c1391ddb5c --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_archetypes_value.h @@ -0,0 +1,445 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#ifndef _STD_RANGES_ARCHETYPES_VALUE_H +#define _STD_RANGES_ARCHETYPES_VALUE_H + +#if _ENABLE_STD_RANGES_TESTING + +#include "std_ranges_archetypes_base.h" + +namespace test_std_ranges +{ +namespace archetypes +{ + +// Family 2: algorithms taking a search value. +// The constraint is +// std::indirect_binary_predicate, _Proj>, +// const _T*> +// std::ranges::equal_to is itself constrained by std::equality_comparable_with, which is much +// stronger than a bare `element == value`: both types have to be equality comparable with +// themselves and to share a common reference type. The archetypes below provide exactly that and +// nothing else, in particular they are still neither copyable nor movable. +// Used by: find, find_last, count, contains, remove, remove_copy, replace, replace_copy. +// The value is passed to a device kernel by copy, so, unlike the other archetypes, it has to be +// trivially copyable and thus device copyable. Everything else a "regular" type provides is still +// missing: no default constructor, no ordering, no relation to the element type but equality. +struct nocopy_search_value; + +struct search_value +{ + int val; + + explicit search_value(int __v) : val(__v) {} + + search_value(const search_value&) = default; + search_value& operator=(const search_value&) = default; + + friend bool operator==(const search_value& __v1, const search_value& __v2) { return __v1.val == __v2.val; } +}; + +struct searchable_archetype +{ + int val; + + // The non-copyable search value type the algorithm has to be called with, so that a generic test + // body may pick the right one for the element type it works on. + using nocopy_value_type = nocopy_search_value; + + explicit searchable_archetype(int __v) : val(__v) {} + + TEST_ARCHETYPE_DELETED_OPERATIONS(searchable_archetype) + + friend bool operator==(const searchable_archetype& __e1, const searchable_archetype& __e2) + { + return __e1.val == __e2.val; + } + + friend bool operator==(const searchable_archetype& __e, const search_value& __v) { return __e.val == __v.val; } + + friend bool operator==(const searchable_archetype& __e, const nocopy_search_value& __v); +}; + +// Family 2b: the very same constraint, but the search value is neither copyable nor movable. +// std::indirect_binary_predicate, _Proj>, +// const _T*> says nothing about copying _T, so a host policy must keep a reference to the value +// instead of storing a copy of it. A device policy legitimately copies the value into the kernel, +// so this archetype is only ever used with the host policies. +struct nocopy_search_value +{ + int val; + + explicit nocopy_search_value(int __v) : val(__v) {} + + TEST_ARCHETYPE_DELETED_OPERATIONS(nocopy_search_value) + + friend bool operator==(const nocopy_search_value& __v1, const nocopy_search_value& __v2) + { + return __v1.val == __v2.val; + } +}; + +inline bool +operator==(const searchable_archetype& __e, const nocopy_search_value& __v) +{ + return __e.val == __v.val; +} + +// The device copyable counterpart of nocopy_search_value: a device policy copies the value into the +// kernel, so the value used with the hetero policies has to be trivially copyable. Everything else +// stays as restricted as in the host only type: no default constructor, no ordering, no relation to +// the element type but equality. +struct nocopy_search_value_dc +{ + int val; + + explicit nocopy_search_value_dc(int __v) : val(__v) {} + + nocopy_search_value_dc(const nocopy_search_value_dc&) = default; + nocopy_search_value_dc& operator=(const nocopy_search_value_dc&) = default; + + friend bool operator==(const nocopy_search_value_dc& __v1, const nocopy_search_value_dc& __v2) + { + return __v1.val == __v2.val; + } +}; + +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(nocopy_search_value_dc) +static_assert(!std::default_initializable); +static_assert(!std::totally_ordered); + +// The element archetype of the removing algorithms. remove() requires +// std::permutable> && indirect_binary_predicate +// so the element has to be movable, but still not copyable and not default constructible. +struct removable_archetype +{ + int val; + + // See searchable_archetype::nocopy_value_type. + using nocopy_value_type = nocopy_search_value; + + explicit removable_archetype(int __v) : val(__v) {} + + removable_archetype(removable_archetype&& __other) : val(__other.val) {} + + removable_archetype& + operator=(removable_archetype&& __other) + { + val = __other.val; + return *this; + } + + removable_archetype(const removable_archetype&) = delete; + removable_archetype& operator=(const removable_archetype&) = delete; + TEST_ARCHETYPE_DELETED_ADDRESSOF + + friend bool operator==(const removable_archetype& __e1, const removable_archetype& __e2) + { + return __e1.val == __e2.val; + } + + friend bool operator==(const removable_archetype& __e, const nocopy_search_value& __v) + { + return __e.val == __v.val; + } + + friend bool operator==(const removable_archetype& __e, const search_value& __v) { return __e.val == __v.val; } +}; + +// The device copyable counterparts of the two archetypes above, used with the hetero policies. +// They are trivially copyable and thus device copyable by default; nothing else is added. +struct searchable_archetype_dc +{ + int val; + + // The device copyable counterpart of searchable_archetype::nocopy_value_type. + using nocopy_value_type = nocopy_search_value_dc; + + explicit searchable_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(searchable_archetype_dc) + + friend bool operator==(const searchable_archetype_dc& __e1, const searchable_archetype_dc& __e2) + { + return __e1.val == __e2.val; + } + + friend bool operator==(const searchable_archetype_dc& __e, const search_value& __v) { return __e.val == __v.val; } + + friend bool operator==(const searchable_archetype_dc& __e, const nocopy_search_value& __v) + { + return __e.val == __v.val; + } + + friend bool operator==(const searchable_archetype_dc& __e, const nocopy_search_value_dc& __v) + { + return __e.val == __v.val; + } +}; + +struct removable_archetype_dc +{ + int val; + + // The device copyable counterpart of removable_archetype::nocopy_value_type. + using nocopy_value_type = nocopy_search_value_dc; + + explicit removable_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(removable_archetype_dc) + + friend bool operator==(const removable_archetype_dc& __e1, const removable_archetype_dc& __e2) + { + return __e1.val == __e2.val; + } + + friend bool operator==(const removable_archetype_dc& __e, const search_value& __v) { return __e.val == __v.val; } + + friend bool operator==(const removable_archetype_dc& __e, const nocopy_search_value& __v) + { + return __e.val == __v.val; + } + + friend bool operator==(const removable_archetype_dc& __e, const nocopy_search_value_dc& __v) + { + return __e.val == __v.val; + } +}; + +// The common reference required by std::equality_comparable_with. It is only ever formed as a +// reference by the concept machinery, so a minimal type which both archetypes convert to is enough. +struct search_common +{ + int val; + + search_common(const searchable_archetype& __e) : val(__e.val) {} + search_common(const removable_archetype& __e) : val(__e.val) {} + search_common(const searchable_archetype_dc& __e) : val(__e.val) {} + search_common(const removable_archetype_dc& __e) : val(__e.val) {} + search_common(const search_value& __v) : val(__v.val) {} + search_common(const nocopy_search_value& __v) : val(__v.val) {} + search_common(const nocopy_search_value_dc& __v) : val(__v.val) {} + + friend bool operator==(const search_common& __v1, const search_common& __v2) { return __v1.val == __v2.val; } +}; + +} // namespace archetypes +} // namespace test_std_ranges + +namespace std +{ +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; + +template <> +struct common_type +{ + using type = test_std_ranges::archetypes::search_common; +}; +} // namespace std + +namespace test_std_ranges +{ +namespace archetypes +{ + +using searchable_iterator_t = std::ranges::iterator_t>; + +static_assert(std::indirect_binary_predicate); +static_assert( + std::indirect_binary_predicate); +static_assert(!std::copy_constructible); +static_assert(!std::move_constructible); +static_assert(!std::default_initializable); + +using removable_iterator_t = std::ranges::iterator_t>; + +static_assert(std::permutable); +static_assert(std::indirect_binary_predicate); +static_assert(std::indirect_binary_predicate); +static_assert(!std::copy_constructible); +static_assert(!std::default_initializable); +static_assert(!std::totally_ordered); +static_assert(!std::copy_constructible); +static_assert(!std::move_constructible); +static_assert(std::is_trivially_copyable_v); +static_assert(!std::default_initializable); +static_assert(!std::totally_ordered); +static_assert(!std::default_initializable); +static_assert(!std::totally_ordered); + +using searchable_dc_iterator_t = std::ranges::iterator_t>; +using removable_dc_iterator_t = std::ranges::iterator_t>; + +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(searchable_archetype_dc) +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(removable_archetype_dc) +static_assert(std::indirect_binary_predicate); +static_assert( + std::indirect_binary_predicate); +static_assert( + std::indirect_binary_predicate); +static_assert(std::permutable); +static_assert(std::indirect_binary_predicate); +static_assert( + std::indirect_binary_predicate); +static_assert(!std::default_initializable); +static_assert(!std::default_initializable); +static_assert(!std::totally_ordered); +static_assert(!std::totally_ordered); + +// Family 2: algorithms taking a search value. The value itself is compared with +// std::ranges::equal_to, so only the projection is a user callable here. The projection returns the +// element by reference, which keeps the equality with the search value as it is in the family above. +struct search_proj_mut +{ + searchable_archetype& operator()(searchable_archetype& __v) const { return __v; } + searchable_archetype_dc& operator()(searchable_archetype_dc& __v) const { return __v; } + removable_archetype& operator()(removable_archetype& __v) const { return __v; } + removable_archetype_dc& operator()(removable_archetype_dc& __v) const { return __v; } +}; + +static_assert(std::indirect_binary_predicate, + const search_value*>); +static_assert(std::indirect_binary_predicate, + const search_value*>); +static_assert(!std::invocable); + +} // namespace archetypes +} // namespace test_std_ranges + +#endif // _ENABLE_STD_RANGES_TESTING +#endif // _STD_RANGES_ARCHETYPES_VALUE_H diff --git a/test/parallel_api/ranges/std_ranges_archetypes_write.h b/test/parallel_api/ranges/std_ranges_archetypes_write.h new file mode 100644 index 00000000000..76477016830 --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_archetypes_write.h @@ -0,0 +1,515 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#ifndef _STD_RANGES_ARCHETYPES_WRITE_H +#define _STD_RANGES_ARCHETYPES_WRITE_H + +#if _ENABLE_STD_RANGES_TESTING + +#include "std_ranges_archetypes_base.h" + +namespace test_std_ranges +{ +namespace archetypes +{ + +// Family 4: algorithms writing a value into the range itself. +// The constraint is std::indirectly_writable, const _T&>, which needs `*it = value` +// for a const lvalue value and nothing else: the element still does not have to be copyable, +// movable or default constructible, and _T stays an unrelated type. +// Used by: fill, replace_if, replace (new value), replace_copy_if / replace_copy (new value). +struct write_value +{ + int val; + + explicit write_value(int __v) : val(__v) {} + + TEST_ARCHETYPE_DELETED_OPERATIONS(write_value) +}; + +// The device copyable counterpart of write_value: a value argument is passed to a device kernel by +// copy, so the hetero policies need a trivially copyable one. +struct write_value_dc +{ + int val; + + explicit write_value_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(write_value_dc) +}; + +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(write_value_dc) + +struct writable_archetype +{ + int val; + + // The value type the algorithm has to be called with, so that a generic test body may pick the + // right one for the element type it works on. + using value_arg = write_value; + + explicit writable_archetype(int __v) : val(__v) {} + + writable_archetype(const writable_archetype&) = delete; + writable_archetype(writable_archetype&&) = delete; + writable_archetype& operator=(const writable_archetype&) = delete; + writable_archetype& operator=(writable_archetype&&) = delete; + TEST_ARCHETYPE_DELETED_ADDRESSOF + + writable_archetype& operator=(const write_value& __v) + { + val = __v.val; + return *this; + } +}; + +struct writable_archetype_dc +{ + int val; + + using value_arg = write_value_dc; + + explicit writable_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(writable_archetype_dc) + + writable_archetype_dc& operator=(const write_value_dc& __v) + { + val = __v.val; + return *this; + } +}; + +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(writable_archetype_dc) + +using writable_iterator_t = std::ranges::iterator_t>; + +static_assert(std::indirectly_writable); +static_assert(std::indirectly_writable>, + const write_value_dc&>); +static_assert(!std::default_initializable); +static_assert(!std::copyable); +static_assert(!std::movable); +static_assert(!std::default_initializable); + +// Family 5: copying algorithms. +// std::indirectly_copyable == indirectly_readable && indirectly_writable>, so the output element only has to be assignable from a non-const lvalue of +// the input element type. Neither element type has to be copyable, movable or default +// constructible, and the two types are deliberately different. +// Used by: copy, copy_if, reverse_copy, rotate_copy, remove_copy, remove_copy_if, unique_copy, +// replace_copy, replace_copy_if, partition_copy, partial_sort_copy. +struct copy_in_archetype +{ + int val; + + explicit copy_in_archetype(int __v) : val(__v) {} + + TEST_ARCHETYPE_DELETED_OPERATIONS(copy_in_archetype) +}; + +struct copy_out_archetype +{ + int val; + + explicit copy_out_archetype(int __v) : val(__v) {} + + copy_out_archetype(const copy_out_archetype&) = delete; + copy_out_archetype(copy_out_archetype&&) = delete; + copy_out_archetype& operator=(const copy_out_archetype&) = delete; + copy_out_archetype& operator=(copy_out_archetype&&) = delete; + TEST_ARCHETYPE_DELETED_ADDRESSOF + + copy_out_archetype& operator=(copy_in_archetype& __v) + { + val = __v.val; + return *this; + } +}; + +// The device copyable counterparts of the two archetypes above, used with the hetero policies. +struct copy_in_archetype_dc +{ + int val; + + explicit copy_in_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(copy_in_archetype_dc) +}; + +struct copy_out_archetype_dc +{ + int val; + + explicit copy_out_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(copy_out_archetype_dc) + + copy_out_archetype_dc& operator=(copy_in_archetype_dc& __v) + { + val = __v.val; + return *this; + } +}; + +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(copy_in_archetype_dc) +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(copy_out_archetype_dc) +static_assert(std::indirectly_copyable>, + std::ranges::iterator_t>>); +static_assert(!std::default_initializable); + +using copy_in_iterator_t = std::ranges::iterator_t>; +using copy_out_iterator_t = std::ranges::iterator_t>; + +static_assert(std::indirectly_copyable); +static_assert(!std::copyable); +static_assert(!std::copyable); +static_assert(!std::default_initializable); + +// Family 6: the move algorithm. +// std::indirectly_movable asks for indirectly_writable>, +// so the output element is only assignable from an rvalue of the input element type: an +// implementation which copies instead of moving does not compile. +struct move_in_archetype +{ + int val; + + explicit move_in_archetype(int __v) : val(__v) {} + + TEST_ARCHETYPE_DELETED_OPERATIONS(move_in_archetype) +}; + +struct move_out_archetype +{ + int val; + + explicit move_out_archetype(int __v) : val(__v) {} + + move_out_archetype(const move_out_archetype&) = delete; + move_out_archetype(move_out_archetype&&) = delete; + move_out_archetype& operator=(const move_out_archetype&) = delete; + move_out_archetype& operator=(move_out_archetype&&) = delete; + TEST_ARCHETYPE_DELETED_ADDRESSOF + + move_out_archetype& operator=(move_in_archetype&& __v) + { + val = __v.val; + return *this; + } +}; + +// The device copyable counterparts of the two archetypes above, used with the hetero policies. The +// assignment from a non-const lvalue of the input type is still missing, so an implementation which +// copies instead of moving does not compile either. +struct move_in_archetype_dc +{ + int val; + + explicit move_in_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(move_in_archetype_dc) +}; + +struct move_out_archetype_dc +{ + int val; + + explicit move_out_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(move_out_archetype_dc) + + move_out_archetype_dc& operator=(move_in_archetype_dc&& __v) + { + val = __v.val; + return *this; + } +}; + +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(move_in_archetype_dc) +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(move_out_archetype_dc) +static_assert(std::indirectly_movable>, + std::ranges::iterator_t>>); +static_assert(!std::indirectly_copyable>, + std::ranges::iterator_t>>); + +using move_in_iterator_t = std::ranges::iterator_t>; +using move_out_iterator_t = std::ranges::iterator_t>; + +static_assert(std::indirectly_movable); +// An lvalue is explicitly rejected, so copying instead of moving is a compilation error. +static_assert(!std::indirectly_copyable); +static_assert(!std::movable); + +// Family 7: swap_ranges. +// std::indirectly_swappable needs std::ranges::swap on the two references, both ways. A +// dedicated hidden-friend swap is provided, so the element does not have to be move constructible +// or move assignable, which is what the fallback std::swap would require. +struct swap_archetype +{ + int val; + + explicit swap_archetype(int __v) : val(__v) {} + + TEST_ARCHETYPE_DELETED_OPERATIONS(swap_archetype) + + friend void swap(swap_archetype& __v1, swap_archetype& __v2) + { + const int __tmp = __v1.val; + __v1.val = __v2.val; + __v2.val = __tmp; + } +}; + +// The device copyable counterpart of the archetype above, used with the hetero policies. The +// dedicated swap is kept, so the algorithm still has to go through std::ranges::swap. +struct swap_archetype_dc +{ + int val; + + explicit swap_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(swap_archetype_dc) + + friend void swap(swap_archetype_dc& __v1, swap_archetype_dc& __v2) + { + const int __tmp = __v1.val; + __v1.val = __v2.val; + __v2.val = __tmp; + } +}; + +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(swap_archetype_dc) +static_assert(!std::default_initializable); + +using swap_iterator_t = std::ranges::iterator_t>; +static_assert(std::indirectly_swappable); +static_assert(!std::movable); +static_assert(!std::move_constructible); +static_assert(!std::default_initializable); + +// Family 8: transform. +// The output constraint is +// std::indirectly_writable, std::indirect_result_t<_F&, projected...>> +// so the output element is only assignable from the result of the functor, which is a third, +// unrelated type. _F itself is only required to be std::copy_constructible. +struct transform_out_archetype; + +struct transform_in_archetype +{ + int val; + + // The output element type the algorithm has to be called with, so that a generic test body which + // allocates the output range itself may pick the right one for the input element type it works on. + using out_type = transform_out_archetype; + + explicit transform_in_archetype(int __v) : val(__v) {} + + TEST_ARCHETYPE_DELETED_OPERATIONS(transform_in_archetype) +}; + +// The result of the functor. indirectly_writable requires the assignment to work for the prvalue, +// the const lvalue and the const rvalue forms of the result type, which a prvalue-returning functor +// naturally provides. +struct transform_result +{ + int val; +}; + +struct transform_out_archetype +{ + int val; + + explicit transform_out_archetype(int __v) : val(__v) {} + + transform_out_archetype(const transform_out_archetype&) = delete; + transform_out_archetype(transform_out_archetype&&) = delete; + transform_out_archetype& operator=(const transform_out_archetype&) = delete; + transform_out_archetype& operator=(transform_out_archetype&&) = delete; + TEST_ARCHETYPE_DELETED_ADDRESSOF + + transform_out_archetype& operator=(const transform_result& __v) + { + val = __v.val; + return *this; + } +}; + +// The device copyable counterparts of the two archetypes above, used with the hetero policies. +struct transform_out_archetype_dc; + +struct transform_in_archetype_dc +{ + int val; + + using out_type = transform_out_archetype_dc; + + explicit transform_in_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(transform_in_archetype_dc) +}; + +struct transform_out_archetype_dc +{ + int val; + + explicit transform_out_archetype_dc(int __v) : val(__v) {} + + TEST_ARCHETYPE_DEFAULTED_OPERATIONS(transform_out_archetype_dc) + + transform_out_archetype_dc& operator=(const transform_result& __v) + { + val = __v.val; + return *this; + } +}; + +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(transform_in_archetype_dc) +TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(transform_out_archetype_dc) +static_assert(!std::default_initializable); + +struct transform_unary_op +{ + transform_result operator()(const transform_in_archetype& __v) const { return transform_result{__v.val * 2}; } + transform_result operator()(const transform_in_archetype_dc& __v) const { return transform_result{__v.val * 2}; } +}; + +struct transform_binary_op +{ + transform_result operator()(const transform_in_archetype& __v1, const transform_in_archetype& __v2) const + { + return transform_result{__v1.val + __v2.val}; + } + transform_result operator()(const transform_in_archetype_dc& __v1, const transform_in_archetype_dc& __v2) const + { + return transform_result{__v1.val + __v2.val}; + } +}; + +using transform_in_iterator_t = std::ranges::iterator_t>; +using transform_out_iterator_t = std::ranges::iterator_t>; + +static_assert(std::copy_constructible); +static_assert(std::copy_constructible); +static_assert(std::indirectly_writable>); +static_assert(std::indirectly_writable< + transform_out_iterator_t, + std::indirect_result_t>); +static_assert(!std::copyable); +static_assert(!std::default_initializable); + +// Both transform overloads project their input before invoking the functor, and the requires-clause +// spells the functor over std::projected, so the functor never sees the element itself. The +// projection returns yet another unrelated type: an implementation which applies the functor to the +// element, or writes the projected value into the output, does not compile. +struct transform_proj_result +{ + int val; +}; + +struct transform_proj +{ + transform_proj_result operator()(const transform_in_archetype& __v) const + { + return transform_proj_result{__v.val + 1}; + } + transform_proj_result operator()(const transform_in_archetype_dc& __v) const + { + return transform_proj_result{__v.val + 1}; + } +}; + +struct transform_projected_unary_op +{ + transform_result operator()(const transform_proj_result& __v) const { return transform_result{__v.val * 2}; } +}; + +struct transform_projected_binary_op +{ + transform_result operator()(const transform_proj_result& __v1, const transform_proj_result& __v2) const + { + return transform_result{__v1.val + __v2.val}; + } +}; + +using transform_projected_iterator_t = std::projected; + +static_assert(std::copy_constructible); +static_assert(std::indirectly_regular_unary_invocable); +static_assert(std::indirectly_writable< + transform_out_iterator_t, + std::indirect_result_t>); +static_assert(std::indirectly_writable>); +// The projected functors reject the element type, and the output element rejects the projected +// value, so neither the projection nor the functor can be skipped by the implementation. +static_assert(!std::invocable); +static_assert(!std::invocable); +static_assert(!std::indirectly_writable); + +// Family 8: transform. The functor is only required to be std::copy_constructible and invocable with +// the projected reference, which is a non-const lvalue. +struct transform_unary_op_mut +{ + transform_result operator()(transform_in_archetype& __v) const { return transform_result{__v.val * 2}; } + transform_result operator()(transform_in_archetype_dc& __v) const { return transform_result{__v.val * 2}; } +}; + +struct transform_binary_op_mut +{ + transform_result operator()(transform_in_archetype& __v1, transform_in_archetype& __v2) const + { + return transform_result{__v1.val + __v2.val}; + } + transform_result operator()(transform_in_archetype_dc& __v1, transform_in_archetype_dc& __v2) const + { + return transform_result{__v1.val + __v2.val}; + } +}; + +// A projection taking its argument by non-const reference. The functor invoked with the projected +// value cannot do the same: the projection returns a prvalue, which does not bind to a non-const +// lvalue reference, so the projected functors of the const section are reused with this projection. +struct transform_proj_mut +{ + transform_proj_result operator()(transform_in_archetype& __v) const { return transform_proj_result{__v.val + 1}; } + transform_proj_result operator()(transform_in_archetype_dc& __v) const + { + return transform_proj_result{__v.val + 1}; + } +}; + +static_assert(std::indirectly_writable>); +static_assert(!std::invocable); +static_assert(std::indirectly_writable< + transform_out_iterator_t, + std::indirect_result_t>); +static_assert( + !std::invocable); +static_assert(std::indirectly_regular_unary_invocable); +static_assert(!std::invocable); +static_assert(std::indirectly_writable< + transform_out_iterator_t, + std::indirect_result_t>>); + +} // namespace archetypes +} // namespace test_std_ranges + +#endif // _ENABLE_STD_RANGES_TESTING +#endif // _STD_RANGES_ARCHETYPES_WRITE_H From 08cdf10da154c678ea0bc834063477582a900c1d Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Wed, 9 Sep 2026 13:26:57 +0200 Subject: [PATCH 082/148] Restore the default of test_memory_algo::call_id call_id had lost its default value, so test_memory_algo could only be instantiated with all four template arguments spelled out. The six pre-existing memory tests, std_ranges_destroy.pass.cpp and the five std_ranges_uninitialized_*.pass.cpp, each run a single algorithm and name two arguments only, so none of them compiled any more; that would have broken the g++/C++20 CI job, which is where those tests run. The added comment says what the parameter is for, since a test naming a single algorithm has no reason to guess: it makes the SYCL kernel name of the device call unique inside a translation unit, because with -fno-sycl-unnamed-lambda two device kernels sharing a name are a "definition with same mangled name" error. Co-Authored-By: Claude Opus 5 --- test/parallel_api/ranges/std_ranges_memory_test.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/parallel_api/ranges/std_ranges_memory_test.h b/test/parallel_api/ranges/std_ranges_memory_test.h index 77b8c441ad5..107cef947b1 100644 --- a/test/parallel_api/ranges/std_ranges_memory_test.h +++ b/test/parallel_api/ranges/std_ranges_memory_test.h @@ -56,7 +56,11 @@ constexpr int test_mode_id = 0; // OutElem is the element type of the output range of two-range algorithms (uninitialized_copy, // uninitialized_move); it may differ from the input element type Elem. -template +// +// call_id makes the SYCL kernel name of the device call unique within a translation unit: with +// -fno-sycl-unnamed-lambda two kernels sharing a name are a "definition with same mangled name" +// error. It is defaulted, so a test running a single algorithm does not have to name it. +template struct test_memory_algo { void run_host(auto algo, auto checker, auto&&... args) From 62f400d7edfc43fec3d5be3b0ff2695cb49ac237 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Wed, 9 Sep 2026 13:27:09 +0200 Subject: [PATCH 083/148] Move the memory archetype harness into its own header run_over_archetype_view and run_archetype_view_all_policies were defined in the middle of std_ranges_memory_archetypes.pass.cpp, between the test_mode_id specializations and main(), which left the only test file of the memory archetypes carrying the harness of every future one. They move into a header of their own, and the .pass.cpp keeps just its two specializations and main(). The harness is deliberately not folded into either existing header. Putting it into std_ranges_memory_test.h would make the six pre-existing memory tests parse the archetypes they never use, and putting it next to the algorithm harness in std_ranges_algo_archetypes_test.h does not work at all: the memory algorithms operate on raw uninitialized storage, so the elements are not constructed yet and archetype_storage, which constructs every element in its constructor, cannot supply the range. Co-Authored-By: Claude Opus 5 --- .../std_ranges_memory_archetypes.pass.cpp | 44 +--------- .../std_ranges_memory_archetypes_test.h | 83 +++++++++++++++++++ 2 files changed, 84 insertions(+), 43 deletions(-) create mode 100644 test/parallel_api/ranges/std_ranges_memory_archetypes_test.h diff --git a/test/parallel_api/ranges/std_ranges_memory_archetypes.pass.cpp b/test/parallel_api/ranges/std_ranges_memory_archetypes.pass.cpp index 336e31bde59..71b10651d04 100644 --- a/test/parallel_api/ranges/std_ranges_memory_archetypes.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_memory_archetypes.pass.cpp @@ -21,10 +21,7 @@ #include "support/utils.h" #if _ENABLE_STD_RANGES_TESTING -#include "std_ranges_memory_test.h" -#include "std_ranges_archetypes.h" - -#include +#include "std_ranges_memory_archetypes_test.h" namespace test_std_ranges { @@ -33,45 +30,6 @@ constexpr int test_mode_id constexpr int test_mode_id> = 1; -// Runs a one-range algorithm over archetype_view, which is random access and sized but neither -// contiguous nor common, so the implementation cannot fall back to raw pointer arithmetic. -template -void -run_over_archetype_view(Alloc& alloc, Policy&& policy, Algo algo, Checker checker, const char* algo_name) -{ - const std::size_t n = medium_size; - Elem* data = alloc.allocate(n); - std::memset(reinterpret_cast(data), -1, n * sizeof(Elem)); // -1 means no initialization - - archetypes::archetype_view view(data, n); - - auto res = algo(std::forward(policy), view); - - EXPECT_TRUE(res == view.begin() + n, (std::string("wrong return value from ") + algo_name + - " over archetype_view").c_str()); - EXPECT_TRUE(std::ranges::all_of(view, checker), (std::string("wrong effect from ") + algo_name + - " over archetype_view").c_str()); - - alloc.deallocate(data, n); -} - -template -void -run_archetype_view_all_policies(Algo algo, Checker checker, const char* algo_name) -{ - std::allocator alloc; - run_over_archetype_view(alloc, oneapi::dpl::execution::seq, algo, checker, algo_name); - run_over_archetype_view(alloc, oneapi::dpl::execution::unseq, algo, checker, algo_name); - run_over_archetype_view(alloc, oneapi::dpl::execution::par, algo, checker, algo_name); - run_over_archetype_view(alloc, oneapi::dpl::execution::par_unseq, algo, checker, algo_name); - -#if TEST_DPCPP_BACKEND_PRESENT - auto policy = TestUtils::get_dpcpp_test_policy(); - sycl::usm_allocator q_alloc{policy.queue()}; - run_over_archetype_view(q_alloc, policy, algo, checker, algo_name); -#endif //TEST_DPCPP_BACKEND_PRESENT -} - } //namespace test_std_ranges #endif //_ENABLE_STD_RANGES_TESTING diff --git a/test/parallel_api/ranges/std_ranges_memory_archetypes_test.h b/test/parallel_api/ranges/std_ranges_memory_archetypes_test.h new file mode 100644 index 00000000000..a6ba79b82a3 --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_memory_archetypes_test.h @@ -0,0 +1,83 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#ifndef _STD_RANGES_MEMORY_ARCHETYPES_TEST_H +#define _STD_RANGES_MEMORY_ARCHETYPES_TEST_H + +#if _ENABLE_STD_RANGES_TESTING + +// The harness of the memory algorithms over the archetypes. It is kept apart from +// std_ranges_memory_test.h so that the pre-existing memory tests do not have to parse the archetypes, +// and apart from std_ranges_algo_archetypes_test.h because the memory algorithms work over raw +// uninitialized storage: the elements are not constructed yet, so archetype_storage, which constructs +// every element in its constructor, cannot be used here. +#include "std_ranges_memory_test.h" +#include "std_ranges_archetypes.h" + +#include +#include +#include +#include +#include +#include + +namespace test_std_ranges +{ + +// Runs a one-range algorithm over archetype_view, which is random access and sized but neither +// contiguous nor common, so the implementation cannot fall back to raw pointer arithmetic. +template +void +run_over_archetype_view(Alloc& alloc, Policy&& policy, Algo algo, Checker checker, const char* algo_name) +{ + const std::size_t n = medium_size; + Elem* data = alloc.allocate(n); + std::memset(reinterpret_cast(data), -1, n * sizeof(Elem)); // -1 means no initialization + + archetypes::archetype_view view(data, n); + + auto res = algo(std::forward(policy), view); + + EXPECT_TRUE(res == view.begin() + n, (std::string("wrong return value from ") + algo_name + + " over archetype_view").c_str()); + EXPECT_TRUE(std::ranges::all_of(view, checker), (std::string("wrong effect from ") + algo_name + + " over archetype_view").c_str()); + + alloc.deallocate(data, n); +} + +// call_id makes the SYCL kernel name of the device call unique within a translation unit, see +// test_memory_algo in std_ranges_memory_test.h. +template +void +run_archetype_view_all_policies(Algo algo, Checker checker, const char* algo_name) +{ + std::allocator alloc; + run_over_archetype_view(alloc, oneapi::dpl::execution::seq, algo, checker, algo_name); + run_over_archetype_view(alloc, oneapi::dpl::execution::unseq, algo, checker, algo_name); + run_over_archetype_view(alloc, oneapi::dpl::execution::par, algo, checker, algo_name); + run_over_archetype_view(alloc, oneapi::dpl::execution::par_unseq, algo, checker, algo_name); + +#if TEST_DPCPP_BACKEND_PRESENT + auto policy = TestUtils::get_dpcpp_test_policy(); + sycl::usm_allocator q_alloc{policy.queue()}; + run_over_archetype_view(q_alloc, policy, algo, checker, algo_name); +#endif //TEST_DPCPP_BACKEND_PRESENT +} + +} //namespace test_std_ranges + +#endif // _ENABLE_STD_RANGES_TESTING +#endif // _STD_RANGES_MEMORY_ARCHETYPES_TEST_H From 20c8d26cde7f02c935fd881d5e183bcbe440652d Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Wed, 9 Sep 2026 13:27:36 +0200 Subject: [PATCH 084/148] Cut the archetype tests by family only and drop the duplicated call sites The suite is a two-dimensional matrix, archetype family times const-ness of the user callable, but it had been cut along both axes at once: six per-family files held the const-callable cases and one 1057-line std_ranges_algo_archetypes_mutable_callable.pass.cpp held the non-const-reference ones for every family, so every algorithm appeared in two files and neither file showed all the coverage of its family. The cut is now along the family axis alone: one file is one archetype family, with a const-callable section followed by a section for callables taking their arguments by non-const reference, and an algorithm lives in exactly one file. Two families that had been squatting in a neighbour's file get their own: min/max/minmax, constrained by indirectly_copyable_storable, move out of the merge test into a storable test, and the two-range comparison algorithms (equal, mismatch, search, find_end, find_first_of, includes) move out of the read test into a cross test. The harness gains run_algo_all_policies and run_algo2_all_policies, which run a single generic lambda with the host policies and with the hetero ones, so the "#if TEST_DPCPP_BACKEND_PRESENT" copy of every case, differing from the host one only in the archetype name and the test name string, is gone. Where the implementation is known to require more than the requires-clause of the algorithm allows, the "#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES__" guard around the call becomes a template argument of the runner, discarded by if constexpr, so the case stays visible even while one of its two sides is switched off. That also lets the lambdas which had been hoisted into named variables only to be shared by the host and the hetero call site move back inline, which removes the unused-variable warnings they produced whenever a guard disabled the calls using them. Choosing the allocator was the caller's job and forced a lambda to name a concrete archetype: it moves into make_storage / make_out_storage, which pick std::allocator or a shared usm_allocator by the policy they are handed. Where a lambda would still have to name a device-copyable archetype, the element archetype names the type it needs itself (out_type for the transform output, nocopy_value_type for the value argument), so one lambda serves the host and the hetero side alike. The 108 hand-written _CallId values are replaced by __LINE__, which is unique by construction and needs no bookkeeping when a case is added or moved; the ids only have to be unique inside one translation unit, and every test file is its own executable. The set of test-name strings is byte-identical to the previous commit, so no case was dropped and none was added. All 8 archetype test files and the 6 pre-existing memory tests were built and run, both plain host and -fsycl, and all 28 configurations pass. As a follow-up, not part of this change: partial_sort, nth_element and inplace_merge are covered with a non-const comparator only and set_intersection and set_symmetric_difference with a const one only, so those five cells of the matrix are still empty. Co-Authored-By: Claude Opus 5 --- .../std_ranges_algo_archetypes_cross.pass.cpp | 179 +++ .../std_ranges_algo_archetypes_merge.pass.cpp | 231 ++-- ..._algo_archetypes_mutable_callable.pass.cpp | 1057 ----------------- ...td_ranges_algo_archetypes_permute.pass.cpp | 213 ++-- .../std_ranges_algo_archetypes_read.pass.cpp | 428 ++----- ...d_ranges_algo_archetypes_storable.pass.cpp | 83 ++ .../ranges/std_ranges_algo_archetypes_test.h | 335 +++--- .../std_ranges_algo_archetypes_value.pass.cpp | 187 ++- .../std_ranges_algo_archetypes_write.pass.cpp | 391 +++--- 9 files changed, 1057 insertions(+), 2047 deletions(-) create mode 100644 test/parallel_api/ranges/std_ranges_algo_archetypes_cross.pass.cpp delete mode 100644 test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp create mode 100644 test/parallel_api/ranges/std_ranges_algo_archetypes_storable.pass.cpp diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_cross.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_cross.pass.cpp new file mode 100644 index 00000000000..ad718370bef --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_cross.pass.cpp @@ -0,0 +1,179 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#include +#include + +#include "support/test_config.h" +#include "support/test_macros.h" +#include "support/utils.h" + +#if _ENABLE_STD_RANGES_TESTING +#include "std_ranges_archetypes.h" +#include "std_ranges_algo_archetypes_test.h" +#endif //_ENABLE_STD_RANGES_TESTING + +int +main() +{ +#if _ENABLE_STD_RANGES_TESTING + using namespace test_std_ranges; + using namespace test_std_ranges::archetypes; + namespace dpl_ranges = oneapi::dpl::ranges; + + // The lhs_archetype/rhs_archetype family: the two-range algorithms which compare the elements of two + // ranges of unrelated types through a user callable only. + // Covers equal, mismatch, search, find_end, find_first_of and includes, first with const callables + // and then with callables taking their arguments by non-const reference. + + // Two ranges of unrelated element types, compared only through the user predicate. + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::equal(std::forward(policy), view1, view2, cross_pred{}); + }, + [](auto&&, auto&&, bool res) { return res; }, "equal"); + + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::mismatch(std::forward(policy), view1, view2, cross_pred{}); + }, + [](auto&& view1, auto&& view2, auto res) { + return res.in1 == std::ranges::begin(view1) + std::ranges::size(view1) && + res.in2 == std::ranges::begin(view2) + std::ranges::size(view2); + }, + "mismatch"); + + // The two ranges hold the very same sequence, so the second one occurs in the first one exactly + // once, at its very beginning. + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::search(std::forward(policy), view1, view2, cross_pred{}); + }, + [](auto&& view1, auto&& view2, auto res) { + return std::ranges::begin(res) == std::ranges::begin(view1) && + std::ranges::size(res) == std::ranges::size(view2); + }, + "search"); + + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::find_end(std::forward(policy), view1, view2, cross_pred{}); + }, + [](auto&& view1, auto&& view2, auto res) { + return std::ranges::begin(res) == std::ranges::begin(view1) && + std::ranges::size(res) == std::ranges::size(view2); + }, + "find_end"); + + // KSATODO: std::indirectly_comparable<_It1, _It2, _Pred> only requires the predicate to be + // invocable as __pred(*__it1, *__it2), never the other way round. The SIMD brick swaps the two + // arguments, so the vectorized host policies unseq and par_unseq do not compile: + // - unseq_backend_simd.h:827 - __simd_find_first_of builds __u_pred as + // __pred(__val, *__first) with __val taken from the second range and *__first from the first + // one; the branch is a plain if, so it is instantiated whatever the sizes of the ranges are. + // Fixing this means keeping the argument order of the two ranges in both branches. + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::find_first_of(std::forward(policy), view1, view2, cross_pred{}); + }, + [](auto&& view1, auto&&, auto res) { return res == std::ranges::begin(view1); }, "find_first_of"); + + // includes needs a comparator accepting the two element types in all four combinations, see + // cross_comp. Both ranges hold the very same ascending sequence, so the second one is included in + // the first one. + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::includes(std::forward(policy), view1, view2, cross_comp{}); + }, + [](auto&&, auto&&, bool res) { return res; }, "includes"); + + //---------------------------------------------------------------------------------------------- + // The same algorithms with callables taking their arguments by non-const reference. + //---------------------------------------------------------------------------------------------- + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::equal(std::forward(policy), view1, view2, cross_pred_mut{}); + }, + [](auto&&, auto&&, bool res) { return res; }, "equal, non-const callable"); + + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::mismatch(std::forward(policy), view1, view2, cross_pred_mut{}); + }, + [](auto&& view1, auto&& view2, auto res) { + return res.in1 == std::ranges::begin(view1) + std::ranges::size(view1) && + res.in2 == std::ranges::begin(view2) + std::ranges::size(view2); + }, + "mismatch, non-const callable"); + + // The two ranges hold the very same sequence, so the second one occurs in the first one exactly + // once, at its very beginning. + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::search(std::forward(policy), view1, view2, cross_pred_mut{}); + }, + [](auto&& view1, auto&& view2, auto res) { + return std::ranges::begin(res) == std::ranges::begin(view1) && + std::ranges::size(res) == std::ranges::size(view2); + }, + "search, non-const callable"); + + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::find_end(std::forward(policy), view1, view2, cross_pred_mut{}); + }, + [](auto&& view1, auto&& view2, auto res) { + return std::ranges::begin(res) == std::ranges::begin(view1) && + std::ranges::size(res) == std::ranges::size(view2); + }, + "find_end, non-const callable"); + + // KSATODO: std::indirectly_comparable<_It1, _It2, _Pred> only requires the predicate to be + // invocable as __pred(*__it1, *__it2), never the other way round. The SIMD brick swaps the two + // arguments, so the vectorized host policies unseq and par_unseq do not compile: + // - unseq_backend_simd.h:827 - __simd_find_first_of builds __u_pred as + // __pred(__val, *__first) with __val taken from the second range and *__first from the first + // one; the branch is a plain if, so it is instantiated whatever the sizes of the ranges are. + // Fixing this means keeping the argument order of the two ranges in both branches. + // + // KSATODO: the device path of find_first_of copies the element of the first range into a const + // local, which std::indirectly_comparable neither asks for nor allows to require, so the call does + // not compile: + // - unseq_backend_sycl.h:632,636 - first_match_pred::operator() writes + // const auto __elem = __acc[__shifted_idx]; and passes __elem to the predicate. A forwarding + // reference instead of the const copy fixes both the const-ness and the extra copy. + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::find_first_of(std::forward(policy), view1, view2, cross_pred_mut{}); + }, + [](auto&& view1, auto&&, auto res) { return res == std::ranges::begin(view1); }, + "find_first_of, non-const callable"); + + // includes needs a comparator accepting the two element types in all four combinations, see + // cross_comp_mut. Both ranges hold the very same ascending sequence, so the second one is included + // in the first one. + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::includes(std::forward(policy), view1, view2, cross_comp_mut{}); + }, + [](auto&&, auto&&, bool res) { return res; }, "includes, non-const comparator"); + +#endif //_ENABLE_STD_RANGES_TESTING + + return TestUtils::done(_ENABLE_STD_RANGES_TESTING); +} diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp index 3399421400e..e89eb89d352 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp @@ -33,16 +33,17 @@ main() using namespace test_std_ranges::archetypes; namespace dpl_ranges = oneapi::dpl::ranges; - // Neither archetype is device copyable, so the host policies are the only ones their - // constraints allow. + // The merge archetype family: the algorithms which are constrained by std::mergeable, i.e. which + // read two sorted inputs and write into an output range of their own. Covers merge, set_union, + // set_difference, set_intersection and set_symmetric_difference, first with const comparators and + // then with comparators taking their arguments by non-const reference. // Both inputs hold the very same sorted sequence 0, 1, 2, ... - run_algo2_host_policies( + run_algo2_all_policies( [](auto&& policy, auto&& view1, auto&& view2) { - using __out_elem = - typename std::ranges::range_value_t>::out_type; - archetype_storage<__out_elem, std::allocator<__out_elem>> out_storage( - std::allocator<__out_elem>{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); + using elem_t = std::ranges::range_value_t>; + auto out_storage = make_out_storage(policy, 2 * archetype_test_size); auto out_view = out_storage.view(); auto res = dpl_ranges::merge(std::forward(policy), view1, view2, out_view, merge_comp{}); return std::ranges::begin(out_view)[0].val == 0 && std::ranges::begin(out_view)[1].val == 0 && @@ -50,24 +51,6 @@ main() }, [](auto&&, auto&&, auto res) { return res; }, "merge"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - using __out_elem = - typename std::ranges::range_value_t>::out_type; - // The output range is written by a device kernel, so its storage has to be device - // accessible: host memory from std::allocator would be dereferenced on the device. - sycl::usm_allocator<__out_elem, sycl::usm::alloc::shared> out_alloc{policy.queue()}; - archetype_storage<__out_elem, decltype(out_alloc)> out_storage(out_alloc, 2 * archetype_test_size, - [](std::size_t) { return 0; }); - auto out_view = out_storage.view(); - auto res = dpl_ranges::merge(std::forward(policy), view1, view2, out_view, merge_comp{}); - return std::ranges::begin(out_view)[0].val == 0 && std::ranges::begin(out_view)[1].val == 0 && - std::ranges::begin(out_view)[2].val == 1 && res.out == std::ranges::end(out_view); - }, - [](auto&&, auto&&, auto res) { return res; }, "merge"); -#endif // TEST_DPCPP_BACKEND_PRESENT - // KSATODO: the set operations only require std::mergeable, i.e. indirectly_copyable from either // input into the output, which is an assignment and not a construction. The implementation // instead constructs the output element into raw memory, so the calls below do not compile: @@ -80,11 +63,12 @@ main() // placement news the output element and takes its address as well; it is reached from // parallel_backend_sycl_reduce_then_scan.h:67,571,1049 for every set operation. // Fixing this means assigning through the output iterator instead of constructing in place. -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HOST - run_algo2_host_policies( + run_algo2_all_policies( [](auto&& policy, auto&& view1, auto&& view2) { - archetype_storage> out_storage( - std::allocator{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); + using elem_t = std::ranges::range_value_t>; + auto out_storage = make_out_storage(policy, 2 * archetype_test_size); auto out_view = out_storage.view(); auto res = dpl_ranges::set_union(std::forward(policy), view1, view2, out_view, merge_comp{}); @@ -93,35 +77,13 @@ main() (std::size_t)(res.out - std::ranges::begin(out_view)) == archetype_test_size; }, [](auto&&, auto&&, auto res) { return res; }, "set_union"); -#endif -#if TEST_DPCPP_BACKEND_PRESENT -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HETERO - run_algo2_hetero_policies( + run_algo2_all_policies( [](auto&& policy, auto&& view1, auto&& view2) { - using __out_elem = - typename std::ranges::range_value_t>::out_type; - // The output range is written by a device kernel, so its storage has to be device - // accessible: host memory from std::allocator would be dereferenced on the device. - sycl::usm_allocator<__out_elem, sycl::usm::alloc::shared> out_alloc{policy.queue()}; - archetype_storage<__out_elem, decltype(out_alloc)> out_storage(out_alloc, 2 * archetype_test_size, - [](std::size_t) { return 0; }); - auto out_view = out_storage.view(); - auto res = - dpl_ranges::set_union(std::forward(policy), view1, view2, out_view, merge_comp{}); - // The two inputs hold the very same sequence, so the union is that sequence itself. - return std::ranges::begin(out_view)[7].val == 7 && - (std::size_t)(res.out - std::ranges::begin(out_view)) == archetype_test_size; - }, - [](auto&&, auto&&, auto res) { return res; }, "set_union"); -#endif // !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HETERO -#endif // TEST_DPCPP_BACKEND_PRESENT - -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HOST - run_algo2_host_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - archetype_storage> out_storage( - std::allocator{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); + using elem_t = std::ranges::range_value_t>; + auto out_storage = make_out_storage(policy, 2 * archetype_test_size); auto out_view = out_storage.view(); auto res = dpl_ranges::set_difference(std::forward(policy), view1, view2, out_view, merge_comp{}); @@ -129,55 +91,15 @@ main() return res.out == std::ranges::begin(out_view); }, [](auto&&, auto&&, auto res) { return res; }, "set_difference"); -#endif - -#if TEST_DPCPP_BACKEND_PRESENT -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HETERO - run_algo2_hetero_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - using __out_elem = - typename std::ranges::range_value_t>::out_type; - // The output range is written by a device kernel, so its storage has to be device - // accessible: host memory from std::allocator would be dereferenced on the device. - sycl::usm_allocator<__out_elem, sycl::usm::alloc::shared> out_alloc{policy.queue()}; - archetype_storage<__out_elem, decltype(out_alloc)> out_storage(out_alloc, 2 * archetype_test_size, - [](std::size_t) { return 0; }); - auto out_view = out_storage.view(); - auto res = dpl_ranges::set_difference(std::forward(policy), view1, view2, out_view, - merge_comp{}); - // The two inputs are equal, so the difference is empty. - return res.out == std::ranges::begin(out_view); - }, - [](auto&&, auto&&, auto res) { return res; }, "set_difference"); -#endif // !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HETERO -#endif // TEST_DPCPP_BACKEND_PRESENT // set_intersection and set_symmetric_difference construct the output element the very same way, // see the note above set_union. -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_INTERSECTION_HOST - run_algo2_host_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - archetype_storage> out_storage( - std::allocator{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); - auto out_view = out_storage.view(); - auto res = dpl_ranges::set_intersection(std::forward(policy), view1, view2, out_view, - merge_comp{}); - // The two inputs hold the very same sequence, so the intersection is that sequence itself. - return std::ranges::begin(out_view)[7].val == 7 && - (std::size_t)(res.out - std::ranges::begin(out_view)) == archetype_test_size; - }, - [](auto&&, auto&&, auto res) { return res; }, "set_intersection"); -#endif - -#if TEST_DPCPP_BACKEND_PRESENT -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_INTERSECTION_HETERO - run_algo2_hetero_policies( + run_algo2_all_policies( [](auto&& policy, auto&& view1, auto&& view2) { - using __out_elem = - typename std::ranges::range_value_t>::out_type; - sycl::usm_allocator<__out_elem, sycl::usm::alloc::shared> out_alloc{policy.queue()}; - archetype_storage<__out_elem, decltype(out_alloc)> out_storage(out_alloc, 2 * archetype_test_size, - [](std::size_t) { return 0; }); + using elem_t = std::ranges::range_value_t>; + auto out_storage = make_out_storage(policy, 2 * archetype_test_size); auto out_view = out_storage.view(); auto res = dpl_ranges::set_intersection(std::forward(policy), view1, view2, out_view, merge_comp{}); @@ -186,14 +108,13 @@ main() (std::size_t)(res.out - std::ranges::begin(out_view)) == archetype_test_size; }, [](auto&&, auto&&, auto res) { return res; }, "set_intersection"); -#endif // !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_INTERSECTION_HETERO -#endif // TEST_DPCPP_BACKEND_PRESENT -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_SYMMETRIC_DIFFERENCE_HOST - run_algo2_host_policies( + run_algo2_all_policies( [](auto&& policy, auto&& view1, auto&& view2) { - archetype_storage> out_storage( - std::allocator{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); + using elem_t = std::ranges::range_value_t>; + auto out_storage = make_out_storage(policy, 2 * archetype_test_size); auto out_view = out_storage.view(); auto res = dpl_ranges::set_symmetric_difference(std::forward(policy), view1, view2, out_view, merge_comp{}); @@ -201,68 +122,56 @@ main() return res.out == std::ranges::begin(out_view); }, [](auto&&, auto&&, auto res) { return res; }, "set_symmetric_difference"); -#endif -#if TEST_DPCPP_BACKEND_PRESENT -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_SYMMETRIC_DIFFERENCE_HETERO - run_algo2_hetero_policies( + //---------------------------------------------------------------------------------------------- + // The same algorithms with callables taking their arguments by non-const reference. + //---------------------------------------------------------------------------------------------- + // Both inputs hold the very same sorted sequence 0, 1, 2, ... + run_algo2_all_policies( [](auto&& policy, auto&& view1, auto&& view2) { - using __out_elem = - typename std::ranges::range_value_t>::out_type; - sycl::usm_allocator<__out_elem, sycl::usm::alloc::shared> out_alloc{policy.queue()}; - archetype_storage<__out_elem, decltype(out_alloc)> out_storage(out_alloc, 2 * archetype_test_size, - [](std::size_t) { return 0; }); + using elem_t = std::ranges::range_value_t>; + auto out_storage = make_out_storage(policy, 2 * archetype_test_size); auto out_view = out_storage.view(); - auto res = dpl_ranges::set_symmetric_difference(std::forward(policy), view1, view2, - out_view, merge_comp{}); - // The two inputs are equal, so the symmetric difference is empty. - return res.out == std::ranges::begin(out_view); - }, - [](auto&&, auto&&, auto res) { return res; }, "set_symmetric_difference"); -#endif // !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_SYMMETRIC_DIFFERENCE_HETERO -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::min(std::forward(policy), view, storable_comp{}); - }, - [](auto&&, auto res) { return res.val == 0; }, "min"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::min(std::forward(policy), view, storable_comp{}); + auto res = + dpl_ranges::merge(std::forward(policy), view1, view2, out_view, merge_comp_mut{}); + return std::ranges::begin(out_view)[0].val == 0 && std::ranges::begin(out_view)[1].val == 0 && + std::ranges::begin(out_view)[2].val == 1 && res.out == std::ranges::end(out_view); }, - [](auto&&, auto res) { return res.val == 0; }, "min"); -#endif // TEST_DPCPP_BACKEND_PRESENT + [](auto&&, auto&&, auto res) { return res; }, "merge, non-const comparator"); - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::max(std::forward(policy), view, storable_comp{}); - }, - [](auto&&, auto res) { return res.val == (int)archetype_test_size - 1; }, "max"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::max(std::forward(policy), view, storable_comp{}); + // The set operations, whose comparator is constrained exactly like the one of merge. They are + // guarded by the very same macros as the const comparator cases above: the implementation + // constructs the output element instead of assigning to it, which std::mergeable never asks for, + // and that breaks the call before the comparator is ever reached. + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + using elem_t = std::ranges::range_value_t>; + auto out_storage = make_out_storage(policy, 2 * archetype_test_size); + auto out_view = out_storage.view(); + auto res = dpl_ranges::set_union(std::forward(policy), view1, view2, out_view, + merge_comp_mut{}); + // The two inputs hold the very same sequence, so the union is that sequence itself. + return std::ranges::begin(out_view)[7].val == 7 && + (std::size_t)(res.out - std::ranges::begin(out_view)) == archetype_test_size; }, - [](auto&&, auto res) { return res.val == (int)archetype_test_size - 1; }, "max"); -#endif // TEST_DPCPP_BACKEND_PRESENT + [](auto&&, auto&&, auto res) { return res; }, "set_union, non-const comparator"); - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::minmax(std::forward(policy), view, storable_comp{}); - }, - [](auto&&, auto&& res) { return res.min.val == 0 && res.max.val == (int)archetype_test_size - 1; }, "minmax"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::minmax(std::forward(policy), view, storable_comp{}); + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + using elem_t = std::ranges::range_value_t>; + auto out_storage = make_out_storage(policy, 2 * archetype_test_size); + auto out_view = out_storage.view(); + auto res = dpl_ranges::set_difference(std::forward(policy), view1, view2, out_view, + merge_comp_mut{}); + // The two inputs are equal, so the difference is empty. + return res.out == std::ranges::begin(out_view); }, - [](auto&&, auto&& res) { return res.min.val == 0 && res.max.val == (int)archetype_test_size - 1; }, "minmax"); -#endif // TEST_DPCPP_BACKEND_PRESENT + [](auto&&, auto&&, auto res) { return res; }, "set_difference, non-const comparator"); #endif //_ENABLE_STD_RANGES_TESTING diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp deleted file mode 100644 index df6124cd11d..00000000000 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_mutable_callable.pass.cpp +++ /dev/null @@ -1,1057 +0,0 @@ -// -*- C++ -*- -//===----------------------------------------------------------------------===// -// -// Copyright (C) Intel Corporation -// -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -// This file incorporates work covered by the following copyright and permission -// notice: -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// -//===----------------------------------------------------------------------===// - -#include -#include - -#include "support/test_config.h" -#include "support/test_macros.h" -#include "support/utils.h" - -#if _ENABLE_STD_RANGES_TESTING -#include "std_ranges_archetypes.h" -#include "std_ranges_algo_archetypes_test.h" -#endif //_ENABLE_STD_RANGES_TESTING - -int -main() -{ -#if _ENABLE_STD_RANGES_TESTING - using namespace test_std_ranges; - using namespace test_std_ranges::archetypes; - namespace dpl_ranges = oneapi::dpl::ranges; - - // The storage is filled with the values 0, 1, 2, ... - constexpr int searched = 3; - - //---------------------------------------------------------------------------------------------- - // Read-only algorithms with a callable taking the element by non-const reference. - //---------------------------------------------------------------------------------------------- - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::for_each(std::forward(policy), view, read_unary_fun_mut{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, - "for_each, non-const callable"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::for_each(std::forward(policy), view, read_unary_fun_mut{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, - "for_each, non-const callable"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_if(std::forward(policy), view, read_unary_pred_mut{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "find_if, non-const callable"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_if(std::forward(policy), view, read_unary_pred_mut{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "find_if, non-const callable"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_if_not(std::forward(policy), view, read_unary_pred_mut{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + 1; }, "find_if_not, non-const callable"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_if_not(std::forward(policy), view, read_unary_pred_mut{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + 1; }, "find_if_not, non-const callable"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // The last element whose value is divisible by three, and the last one whose value is not. - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_last_if(std::forward(policy), view, read_unary_pred_mut{}); - }, - [](auto&& view, auto res) { - auto __n = (int)std::ranges::size(view); - return std::ranges::begin(res) == std::ranges::begin(view) + (__n - 1) / 3 * 3; - }, - "find_last_if, non-const callable"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_last_if(std::forward(policy), view, read_unary_pred_mut{}); - }, - [](auto&& view, auto res) { - auto __n = (int)std::ranges::size(view); - return std::ranges::begin(res) == std::ranges::begin(view) + (__n - 1) / 3 * 3; - }, - "find_last_if, non-const callable"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_last_if_not(std::forward(policy), view, read_unary_pred_mut{}); - }, - [](auto&& view, auto res) { - auto __n = (int)std::ranges::size(view); - return std::ranges::begin(res) == std::ranges::begin(view) + ((__n - 1) % 3 == 0 ? __n - 2 : __n - 1); - }, - "find_last_if_not, non-const callable"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_last_if_not(std::forward(policy), view, read_unary_pred_mut{}); - }, - [](auto&& view, auto res) { - auto __n = (int)std::ranges::size(view); - return std::ranges::begin(res) == std::ranges::begin(view) + ((__n - 1) % 3 == 0 ? __n - 2 : __n - 1); - }, - "find_last_if_not, non-const callable"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::any_of(std::forward(policy), view, read_unary_pred_mut{}); - }, - [](auto&&, bool res) { return res; }, "any_of, non-const callable"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::any_of(std::forward(policy), view, read_unary_pred_mut{}); - }, - [](auto&&, bool res) { return res; }, "any_of, non-const callable"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // Every third element satisfies the predicate, so the range is neither all nor none of it, and it - // is not partitioned either. - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::all_of(std::forward(policy), view, read_unary_pred_mut{}); - }, - [](auto&&, bool res) { return !res; }, "all_of, non-const callable"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::all_of(std::forward(policy), view, read_unary_pred_mut{}); - }, - [](auto&&, bool res) { return !res; }, "all_of, non-const callable"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::none_of(std::forward(policy), view, read_unary_pred_mut{}); - }, - [](auto&&, bool res) { return !res; }, "none_of, non-const callable"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::none_of(std::forward(policy), view, read_unary_pred_mut{}); - }, - [](auto&&, bool res) { return !res; }, "none_of, non-const callable"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::is_partitioned(std::forward(policy), view, read_unary_pred_mut{}); - }, - [](auto&&, bool res) { return !res; }, "is_partitioned, non-const callable"); - - // KSATODO: std::indirect_unary_predicate only requires the predicate to be invocable with - // iter_reference_t<_It>, a non-const lvalue here, but the device path applies it to a const - // lvalue, so the call does not compile: - // - algorithm_impl_hetero.h:1078,1080 - __pattern_is_partitioned_transform_fn::operator() is - // const and takes the accessor by value, so __acc[__gidx] yields a const reference which is - // passed straight into the predicate. -#if TEST_DPCPP_BACKEND_PRESENT -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_IS_PARTITIONED_HETERO - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::is_partitioned(std::forward(policy), view, read_unary_pred_mut{}); - }, - [](auto&&, bool res) { return !res; }, "is_partitioned, non-const callable"); -#endif // !_TEST_CPP20_RANGES_BROKEN_REQUIRES_IS_PARTITIONED_HETERO -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::count_if(std::forward(policy), view, read_unary_pred_mut{}); - }, - [](auto&& view, auto res) { - return res == (std::ranges::range_difference_t)((std::ranges::size(view) + 2) / 3); - }, - "count_if, non-const callable"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::count_if(std::forward(policy), view, read_unary_pred_mut{}); - }, - [](auto&& view, auto res) { - return res == (std::ranges::range_difference_t)((std::ranges::size(view) + 2) / 3); - }, - "count_if, non-const callable"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // The projection takes the element by non-const reference; the predicate sees its prvalue result. - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_if(std::forward(policy), view, read_proj_pred{}, read_proj_mut{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "find_if, non-const projection"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_if(std::forward(policy), view, read_proj_pred{}, read_proj_mut{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "find_if, non-const projection"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::count_if(std::forward(policy), view, read_proj_pred{}, - read_proj_mut{}); - }, - [](auto&& view, auto res) { - return res == (std::ranges::range_difference_t)((std::ranges::size(view) + 2) / 3); - }, - "count_if, non-const projection"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::count_if(std::forward(policy), view, read_proj_pred{}, - read_proj_mut{}); - }, - [](auto&& view, auto res) { - return res == (std::ranges::range_difference_t)((std::ranges::size(view) + 2) / 3); - }, - "count_if, non-const projection"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::adjacent_find(std::forward(policy), view, read_binary_pred_mut{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, - "adjacent_find, non-const callable"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::adjacent_find(std::forward(policy), view, read_binary_pred_mut{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, - "adjacent_find, non-const callable"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::is_sorted(std::forward(policy), view, read_comp_mut{}); - }, - [](auto&&, bool res) { return res; }, "is_sorted, non-const comparator"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::is_sorted(std::forward(policy), view, read_comp_mut{}); - }, - [](auto&&, bool res) { return res; }, "is_sorted, non-const comparator"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // The whole range is sorted, so the scan stops at its end. - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::is_sorted_until(std::forward(policy), view, read_comp_mut{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, - "is_sorted_until, non-const comparator"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::is_sorted_until(std::forward(policy), view, read_comp_mut{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, - "is_sorted_until, non-const comparator"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::min_element(std::forward(policy), view, read_comp_mut{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "min_element, non-const comparator"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::min_element(std::forward(policy), view, read_comp_mut{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "min_element, non-const comparator"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::max_element(std::forward(policy), view, read_comp_mut{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view) - 1; }, - "max_element, non-const comparator"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::max_element(std::forward(policy), view, read_comp_mut{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view) - 1; }, - "max_element, non-const comparator"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::minmax_element(std::forward(policy), view, read_comp_mut{}); - }, - [](auto&& view, auto res) { - return res.min == std::ranges::begin(view) && - res.max == std::ranges::begin(view) + std::ranges::size(view) - 1; - }, - "minmax_element, non-const comparator"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::minmax_element(std::forward(policy), view, read_comp_mut{}); - }, - [](auto&& view, auto res) { - return res.min == std::ranges::begin(view) && - res.max == std::ranges::begin(view) + std::ranges::size(view) - 1; - }, - "minmax_element, non-const comparator"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - //---------------------------------------------------------------------------------------------- - // The value based algorithms with a projection taking the element by non-const reference. - //---------------------------------------------------------------------------------------------- - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find(std::forward(policy), view, search_value{searched}, - search_proj_mut{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, - "find, non-const projection"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find(std::forward(policy), view, search_value{searched}, - search_proj_mut{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, - "find, non-const projection"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_last(std::forward(policy), view, search_value{searched}, - search_proj_mut{}); - }, - [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, - "find_last, non-const projection"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_last(std::forward(policy), view, search_value{searched}, - search_proj_mut{}); - }, - [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, - "find_last, non-const projection"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::count(std::forward(policy), view, search_value{searched}, - search_proj_mut{}); - }, - [](auto&&, auto res) { return res == 1; }, "count, non-const projection"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::count(std::forward(policy), view, search_value{searched}, - search_proj_mut{}); - }, - [](auto&&, auto res) { return res == 1; }, "count, non-const projection"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::contains(std::forward(policy), view, search_value{searched}, - search_proj_mut{}); - }, - [](auto&&, auto res) { return res; }, "contains, non-const projection"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::contains(std::forward(policy), view, search_value{searched}, - search_proj_mut{}); - }, - [](auto&&, auto res) { return res; }, "contains, non-const projection"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::remove(std::forward(policy), view, search_value{searched}, - search_proj_mut{}); - }, - // remove() returns the tail holding the removed elements, and the value 3 occurs exactly once. - [](auto&&, auto res) { return std::ranges::size(res) == 1; }, "remove, non-const projection"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::remove(std::forward(policy), view, search_value{searched}, - search_proj_mut{}); - }, - // remove() returns the tail holding the removed elements, and the value 3 occurs exactly once. - [](auto&&, auto res) { return std::ranges::size(res) == 1; }, "remove, non-const projection"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - //---------------------------------------------------------------------------------------------- - // Two-range algorithms with a predicate taking both elements by non-const reference. - //---------------------------------------------------------------------------------------------- - run_algo2_host_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::equal(std::forward(policy), view1, view2, cross_pred_mut{}); - }, - [](auto&&, auto&&, bool res) { return res; }, "equal, non-const callable"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::equal(std::forward(policy), view1, view2, cross_pred_mut{}); - }, - [](auto&&, auto&&, bool res) { return res; }, "equal, non-const callable"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo2_host_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::mismatch(std::forward(policy), view1, view2, cross_pred_mut{}); - }, - [](auto&& view1, auto&& view2, auto res) { - return res.in1 == std::ranges::begin(view1) + std::ranges::size(view1) && - res.in2 == std::ranges::begin(view2) + std::ranges::size(view2); - }, - "mismatch, non-const callable"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::mismatch(std::forward(policy), view1, view2, cross_pred_mut{}); - }, - [](auto&& view1, auto&& view2, auto res) { - return res.in1 == std::ranges::begin(view1) + std::ranges::size(view1) && - res.in2 == std::ranges::begin(view2) + std::ranges::size(view2); - }, - "mismatch, non-const callable"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // The two ranges hold the very same sequence, so the second one occurs in the first one exactly - // once, at its very beginning. - run_algo2_host_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::search(std::forward(policy), view1, view2, cross_pred_mut{}); - }, - [](auto&& view1, auto&& view2, auto res) { - return std::ranges::begin(res) == std::ranges::begin(view1) && - std::ranges::size(res) == std::ranges::size(view2); - }, - "search, non-const callable"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::search(std::forward(policy), view1, view2, cross_pred_mut{}); - }, - [](auto&& view1, auto&& view2, auto res) { - return std::ranges::begin(res) == std::ranges::begin(view1) && - std::ranges::size(res) == std::ranges::size(view2); - }, - "search, non-const callable"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo2_host_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::find_end(std::forward(policy), view1, view2, cross_pred_mut{}); - }, - [](auto&& view1, auto&& view2, auto res) { - return std::ranges::begin(res) == std::ranges::begin(view1) && - std::ranges::size(res) == std::ranges::size(view2); - }, - "find_end, non-const callable"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::find_end(std::forward(policy), view1, view2, cross_pred_mut{}); - }, - [](auto&& view1, auto&& view2, auto res) { - return std::ranges::begin(res) == std::ranges::begin(view1) && - std::ranges::size(res) == std::ranges::size(view2); - }, - "find_end, non-const callable"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // KSATODO: std::indirectly_comparable<_It1, _It2, _Pred> only requires the predicate to be - // invocable as __pred(*__it1, *__it2), never the other way round. The SIMD brick swaps the two - // arguments, so the vectorized host policies unseq and par_unseq do not compile: - // - unseq_backend_simd.h:827 - __simd_find_first_of builds __u_pred as - // __pred(__val, *__first) with __val taken from the second range and *__first from the first - // one; the branch is a plain if, so it is instantiated whatever the sizes of the ranges are. - // Fixing this means keeping the argument order of the two ranges in both branches. - auto find_first_of_algo = [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::find_first_of(std::forward(policy), view1, view2, cross_pred_mut{}); - }; - auto find_first_of_checker = [](auto&& view1, auto&&, auto res) { return res == std::ranges::begin(view1); }; - -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_FIRST_OF_HOST - run_algo2_host_policies(find_first_of_algo, find_first_of_checker, - "find_first_of, non-const callable"); -#endif - - // KSATODO: the device path of find_first_of copies the element of the first range into a const - // local, which std::indirectly_comparable neither asks for nor allows to require, so the call does - // not compile: - // - unseq_backend_sycl.h:632,636 - first_match_pred::operator() writes - // const auto __elem = __acc[__shifted_idx]; and passes __elem to the predicate. A forwarding - // reference instead of the const copy fixes both the const-ness and the extra copy. -#if TEST_DPCPP_BACKEND_PRESENT -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_FIRST_OF_HETERO - run_algo2_hetero_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::find_first_of(std::forward(policy), view1, view2, cross_pred_mut{}); - }, - [](auto&& view1, auto&&, auto res) { return res == std::ranges::begin(view1); }, - "find_first_of, non-const callable"); -#endif // !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_FIRST_OF_HETERO -#endif // TEST_DPCPP_BACKEND_PRESENT - - // includes needs a comparator accepting the two element types in all four combinations, see - // cross_comp_mut. Both ranges hold the very same ascending sequence, so the second one is included - // in the first one. - run_algo2_host_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::includes(std::forward(policy), view1, view2, cross_comp_mut{}); - }, - [](auto&&, auto&&, bool res) { return res; }, "includes, non-const comparator"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::includes(std::forward(policy), view1, view2, cross_comp_mut{}); - }, - [](auto&&, auto&&, bool res) { return res; }, "includes, non-const comparator"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - //---------------------------------------------------------------------------------------------- - // transform with a functor taking the input element by non-const reference. - //---------------------------------------------------------------------------------------------- - run_algo2_host_policies( - [](auto&& policy, auto&& in_view, auto&& out_view) { - return dpl_ranges::transform(std::forward(policy), in_view, out_view, - transform_unary_op_mut{}); - }, - [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 14; }, - "transform, non-const callable"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies( - [](auto&& policy, auto&& in_view, auto&& out_view) { - return dpl_ranges::transform(std::forward(policy), in_view, out_view, - transform_unary_op_mut{}); - }, - [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 14; }, - "transform, non-const callable"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // The projection is the one taking the element by non-const reference here: the functor is - // invoked with the projected prvalue and cannot take it by non-const reference at all. - run_algo2_host_policies( - [](auto&& policy, auto&& in_view, auto&& out_view) { - return dpl_ranges::transform(std::forward(policy), in_view, out_view, - transform_projected_unary_op{}, transform_proj_mut{}); - }, - [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 16; }, - "transform, non-const projection"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies( - [](auto&& policy, auto&& in_view, auto&& out_view) { - return dpl_ranges::transform(std::forward(policy), in_view, out_view, - transform_projected_unary_op{}, transform_proj_mut{}); - }, - [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 16; }, - "transform, non-const projection"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // The binary overload with a functor taking both input elements by non-const reference. It takes - // two input ranges, so the output range is allocated inside the call and checked there as well. - run_algo2_host_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - archetype_storage> out_storage( - std::allocator{}, archetype_test_size, [](std::size_t) { return 0; }); - auto out_view = out_storage.view(); - auto res = dpl_ranges::transform(std::forward(policy), view1, view2, out_view, - transform_binary_op_mut{}); - return std::ranges::begin(out_view)[7].val == 14 && res.out == std::ranges::end(out_view); - }, - [](auto&&, auto&&, auto res) { return res; }, "transform, binary, non-const callable"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - // The output range is written by a device kernel, so its storage has to be device - // accessible: host memory from std::allocator would be dereferenced on the device. - sycl::usm_allocator out_alloc{policy.queue()}; - archetype_storage out_storage( - out_alloc, archetype_test_size, [](std::size_t) { return 0; }); - auto out_view = out_storage.view(); - auto res = dpl_ranges::transform(std::forward(policy), view1, view2, out_view, - transform_binary_op_mut{}); - return std::ranges::begin(out_view)[7].val == 14 && res.out == std::ranges::end(out_view); - }, - [](auto&&, auto&&, auto res) { return res; }, "transform, binary, non-const callable"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // The binary overload with a non-const projection for either input. - run_algo2_host_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - archetype_storage> out_storage( - std::allocator{}, archetype_test_size, [](std::size_t) { return 0; }); - auto out_view = out_storage.view(); - auto res = - dpl_ranges::transform(std::forward(policy), view1, view2, out_view, - transform_projected_binary_op{}, transform_proj_mut{}, transform_proj_mut{}); - return std::ranges::begin(out_view)[7].val == 16 && res.out == std::ranges::end(out_view); - }, - [](auto&&, auto&&, auto res) { return res; }, "transform, binary, non-const projections"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - sycl::usm_allocator out_alloc{policy.queue()}; - archetype_storage out_storage( - out_alloc, archetype_test_size, [](std::size_t) { return 0; }); - auto out_view = out_storage.view(); - auto res = - dpl_ranges::transform(std::forward(policy), view1, view2, out_view, - transform_projected_binary_op{}, transform_proj_mut{}, transform_proj_mut{}); - return std::ranges::begin(out_view)[7].val == 16 && res.out == std::ranges::end(out_view); - }, - [](auto&&, auto&&, auto res) { return res; }, "transform, binary, non-const projections"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - //---------------------------------------------------------------------------------------------- - // The permuting and the sorting algorithms. - //---------------------------------------------------------------------------------------------- - // Every third element is removed; the returned subrange is the tail holding the removed elements. - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::remove_if(std::forward(policy), view, permutable_pred_mut{}); - }, - [](auto&& view, auto res) { return std::ranges::size(res) == (std::ranges::size(view) + 2) / 3; }, - "remove_if, non-const callable"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::remove_if(std::forward(policy), view, permutable_pred_mut{}); - }, - [](auto&& view, auto res) { return std::ranges::size(res) == (std::ranges::size(view) + 2) / 3; }, - "remove_if, non-const callable"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // All the elements are unique, so nothing is dropped. - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::unique(std::forward(policy), view, permutable_equiv_mut{}); - }, - [](auto&&, auto res) { return std::ranges::size(res) == 0; }, "unique, non-const callable"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::unique(std::forward(policy), view, permutable_equiv_mut{}); - }, - [](auto&&, auto res) { return std::ranges::size(res) == 0; }, "unique, non-const callable"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // partition returns the tail of the elements which do not satisfy the predicate. - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::partition(std::forward(policy), view, permutable_pred_mut{}); - }, - [](auto&& view, auto res) { - return std::ranges::size(res) == std::ranges::size(view) - (std::ranges::size(view) + 2) / 3; - }, - "partition, non-const callable"); - - // KSATODO: the device path of partition applies the predicate to a const lvalue, which - // std::indirect_unary_predicate over a permutable iterator does not ask for, so it does not - // compile: - // - unseq_backend_sycl.h:122 - walk_n::operator() is const and calls __f(__rngs[__idx]...) on - // the const range members of single_match_pred_by_idx. -#if TEST_DPCPP_BACKEND_PRESENT -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_PARTITION_HETERO - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::partition(std::forward(policy), view, permutable_pred_mut{}); - }, - [](auto&& view, auto res) { - return std::ranges::size(res) == std::ranges::size(view) - (std::ranges::size(view) + 2) / 3; - }, - "partition, non-const callable"); -#endif // !_TEST_CPP20_RANGES_BROKEN_REQUIRES_PARTITION_HETERO -#endif // TEST_DPCPP_BACKEND_PRESENT - - // The very same comparator as the one the sorting algorithms below are called with. - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::is_sorted(std::forward(policy), view, permutable_comp_mut{}); - }, - [](auto&&, bool res) { return res; }, "is_sorted of a permutable range, non-const comparator"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::is_sorted(std::forward(policy), view, permutable_comp_mut{}); - }, - [](auto&&, bool res) { return res; }, "is_sorted of a permutable range, non-const comparator"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // KSATODO: std::sortable<_It, _Comp> only requires the comparator to be invocable with - // iter_reference_t<_It>, which is a non-const lvalue for archetype_view, so a comparator taking - // its arguments by non-const reference is enough. The parallel host merge sort compares against a - // const lvalue instead, so par and par_unseq do not compile: - // - parallel_backend_tbb.h:1037 - std::lower_bound(..., _M_comp) passes the const lvalue _Val - // of the merge split point to the comparator; - // - utils.h:203 - __binary_op::operator() forwards that const lvalue into std::invoke. - // seq and unseq keep the element non-const all the way down. - auto sort_algo = [](auto&& policy, auto&& view) { - return dpl_ranges::sort(std::forward(policy), view, permutable_comp_mut{}); - }; - auto sorted_checker = [](auto&& view, auto) { - return std::ranges::begin(view)[0].val == 0 && - std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; - }; - -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT_HOST - run_algo_host_policies(sort_algo, sorted_checker, "sort, non-const comparator"); -#endif - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::sort(std::forward(policy), view, permutable_comp_mut{}); - }, - [](auto&& view, auto) { - return std::ranges::begin(view)[0].val == 0 && - std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; - }, - "sort, non-const comparator"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // KSATODO: stable_sort shares the merge sort of the parallel host policies with sort, so it is - // broken for par and par_unseq in exactly the same way, see the note above. - auto stable_sort_algo = [](auto&& policy, auto&& view) { - return dpl_ranges::stable_sort(std::forward(policy), view, permutable_comp_mut{}); - }; - -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT_HOST - run_algo_host_policies(stable_sort_algo, sorted_checker, "stable_sort, non-const comparator"); -#endif - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::stable_sort(std::forward(policy), view, permutable_comp_mut{}); - }, - [](auto&& view, auto) { - return std::ranges::begin(view)[0].val == 0 && - std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; - }, - "stable_sort, non-const comparator"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - //---------------------------------------------------------------------------------------------- - // merge and min / max / minmax. - //---------------------------------------------------------------------------------------------- - // Both inputs hold the very same sorted sequence 0, 1, 2, ... - run_algo2_host_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - using __out_elem = typename std::ranges::range_value_t>::out_type; - archetype_storage<__out_elem, std::allocator<__out_elem>> out_storage( - std::allocator<__out_elem>{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); - auto out_view = out_storage.view(); - auto res = - dpl_ranges::merge(std::forward(policy), view1, view2, out_view, merge_comp_mut{}); - return std::ranges::begin(out_view)[0].val == 0 && std::ranges::begin(out_view)[1].val == 0 && - std::ranges::begin(out_view)[2].val == 1 && res.out == std::ranges::end(out_view); - }, - [](auto&&, auto&&, auto res) { return res; }, "merge, non-const comparator"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - using __out_elem = typename std::ranges::range_value_t>::out_type; - // The output range is passed to a kernel just like the inputs, so it has to live in USM. - sycl::usm_allocator<__out_elem, sycl::usm::alloc::shared> __out_alloc{policy.queue()}; - archetype_storage<__out_elem, decltype(__out_alloc)> out_storage( - __out_alloc, 2 * archetype_test_size, [](std::size_t) { return 0; }); - auto out_view = out_storage.view(); - auto res = - dpl_ranges::merge(std::forward(policy), view1, view2, out_view, merge_comp_mut{}); - return std::ranges::begin(out_view)[0].val == 0 && std::ranges::begin(out_view)[1].val == 0 && - std::ranges::begin(out_view)[2].val == 1 && res.out == std::ranges::end(out_view); - }, - [](auto&&, auto&&, auto res) { return res; }, "merge, non-const comparator"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::min(std::forward(policy), view, storable_comp_mut{}); - }, - [](auto&&, auto res) { return res.val == 0; }, "min, non-const comparator"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::min(std::forward(policy), view, storable_comp_mut{}); - }, - [](auto&&, auto res) { return res.val == 0; }, "min, non-const comparator"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::max(std::forward(policy), view, storable_comp_mut{}); - }, - [](auto&&, auto res) { return res.val == (int)archetype_test_size - 1; }, "max, non-const comparator"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::max(std::forward(policy), view, storable_comp_mut{}); - }, - [](auto&&, auto res) { return res.val == (int)archetype_test_size - 1; }, "max, non-const comparator"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::minmax(std::forward(policy), view, storable_comp_mut{}); - }, - [](auto&&, auto&& res) { return res.min.val == 0 && res.max.val == (int)archetype_test_size - 1; }, - "minmax, non-const comparator"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::minmax(std::forward(policy), view, storable_comp_mut{}); - }, - [](auto&&, auto&& res) { return res.min.val == 0 && res.max.val == (int)archetype_test_size - 1; }, - "minmax, non-const comparator"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - //---------------------------------------------------------------------------------------------- - // The remaining algorithms constrained by std::sortable, i.e. by the very same comparator - // requirement as sort: partial_sort, nth_element and inplace_merge. - //---------------------------------------------------------------------------------------------- - // KSATODO: partial_sort shares the parallel merge sort with sort, so the parallel host policies - // hand a const lvalue to the comparator here as well and par / par_unseq do not compile: - // - parallel_backend_tbb.h:1023,1026,1034 - __merge_func::split_merging passes *(_M_x_beg + __ym) - // to std::upper_bound / std::lower_bound, which compares against their const lvalue parameter; - // - utils.h:203 - __binary_op::operator() forwards that const lvalue into std::invoke. - // seq and unseq keep the element non-const all the way down. - // The range is ascending already, so the first ten elements are 0 ... 9 afterwards. - auto partial_sort_algo = [](auto&& policy, auto&& view) { - return dpl_ranges::partial_sort(std::forward(policy), view, std::ranges::begin(view) + 10, - permutable_comp_mut{}); - }; - auto partial_sort_checker = [](auto&& view, auto) { - return std::ranges::begin(view)[0].val == 0 && std::ranges::begin(view)[9].val == 9; - }; - -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_PARTIAL_SORT_HOST - run_algo_host_policies(partial_sort_algo, partial_sort_checker, - "partial_sort, non-const comparator"); -#endif - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::partial_sort(std::forward(policy), view, - std::ranges::begin(view) + 10, permutable_comp_mut{}); - }, - [](auto&& view, auto) { return std::ranges::begin(view)[0].val == 0 && std::ranges::begin(view)[9].val == 9; }, - "partial_sort, non-const comparator"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // KSATODO: std::sortable only requires the comparator to be invocable with the non-const - // reference of the element, but the parallel path of nth_element compares against a const lvalue, - // so par and par_unseq do not compile: - // - algorithm_impl.h:2841 - the partition predicate of the quickselect loop takes const _Tp& and - // passes it into std::invoke(__comp, __x, *__first). - // Taking the element by reference in that lambda is enough to fix it; seq and unseq are fine. - auto nth_element_algo = [](auto&& policy, auto&& view) { - return dpl_ranges::nth_element(std::forward(policy), view, std::ranges::begin(view) + 10, - permutable_comp_mut{}); - }; - auto nth_element_checker = [](auto&& view, auto) { return std::ranges::begin(view)[10].val == 10; }; - -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_NTH_ELEMENT_HOST - run_algo_host_policies(nth_element_algo, nth_element_checker, - "nth_element, non-const comparator"); -#endif - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::nth_element(std::forward(policy), view, std::ranges::begin(view) + 10, - permutable_comp_mut{}); - }, - [](auto&& view, auto) { return std::ranges::begin(view)[10].val == 10; }, "nth_element, non-const comparator"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // KSATODO: inplace_merge does not compile with any host policy, for two independent reasons: - // - algorithm_ranges_impl.h:848 - the serial path returns __end(__r), i.e. the sentinel of the - // range, while the declared return type is std::ranges::borrowed_iterator_t<_R>. For a range - // which is not a common_range the two types differ, so seq already fails to compile. This one - // is independent of the comparator and hits any user range with a distinct sentinel type; - // - the const lvalue of the merge split point is handed to the comparator, which std::sortable - // never asks for: std::inplace_merge compares against its const value parameter for unseq, and - // parallel_backend_tbb.h:1240,1245 does the same through std::upper_bound / std::lower_bound - // for par and par_unseq. - // Both halves of the ascending range are sorted, so merging them keeps it as it is. -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_INPLACE_MERGE_HOST - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::inplace_merge(std::forward(policy), view, - std::ranges::begin(view) + std::ranges::size(view) / 2, - permutable_comp_mut{}); - }, - sorted_checker, "inplace_merge, non-const comparator"); -#endif - - // KSATODO: the device path of inplace_merge compares two const lvalues, which std::sortable does - // not ask for, so the call does not compile: - // - parallel_backend_sycl_merge.h:128-133 - the lambda of __find_start_point captures __rng1 and - // __rng2 and subscripts them as const, and both results go into the comparator. -#if TEST_DPCPP_BACKEND_PRESENT -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_INPLACE_MERGE_HETERO - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::inplace_merge(std::forward(policy), view, - std::ranges::begin(view) + std::ranges::size(view) / 2, - permutable_comp_mut{}); - }, - [](auto&& view, auto) { - return std::ranges::begin(view)[0].val == 0 && - std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; - }, - "inplace_merge, non-const comparator"); -#endif // !_TEST_CPP20_RANGES_BROKEN_REQUIRES_INPLACE_MERGE_HETERO -#endif // TEST_DPCPP_BACKEND_PRESENT - - //---------------------------------------------------------------------------------------------- - // The set operations, whose comparator is constrained exactly like the one of merge. They are - // guarded by the very same macros as in std_ranges_algo_archetypes_merge.pass.cpp: the - // implementation constructs the output element instead of assigning to it, which std::mergeable - // never asks for, and that breaks the call before the comparator is ever reached. - //---------------------------------------------------------------------------------------------- -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HOST - run_algo2_host_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - archetype_storage> out_storage( - std::allocator{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); - auto out_view = out_storage.view(); - auto res = dpl_ranges::set_union(std::forward(policy), view1, view2, out_view, - merge_comp_mut{}); - // The two inputs hold the very same sequence, so the union is that sequence itself. - return std::ranges::begin(out_view)[7].val == 7 && - (std::size_t)(res.out - std::ranges::begin(out_view)) == archetype_test_size; - }, - [](auto&&, auto&&, auto res) { return res; }, "set_union, non-const comparator"); -#endif - -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HOST - run_algo2_host_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - archetype_storage> out_storage( - std::allocator{}, 2 * archetype_test_size, [](std::size_t) { return 0; }); - auto out_view = out_storage.view(); - auto res = dpl_ranges::set_difference(std::forward(policy), view1, view2, out_view, - merge_comp_mut{}); - // The two inputs are equal, so the difference is empty. - return res.out == std::ranges::begin(out_view); - }, - [](auto&&, auto&&, auto res) { return res; }, "set_difference, non-const comparator"); -#endif - -#if TEST_DPCPP_BACKEND_PRESENT -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HETERO - run_algo2_hetero_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - using __out_elem = typename std::ranges::range_value_t>::out_type; - sycl::usm_allocator<__out_elem, sycl::usm::alloc::shared> __out_alloc{policy.queue()}; - archetype_storage<__out_elem, decltype(__out_alloc)> out_storage(__out_alloc, 2 * archetype_test_size, - [](std::size_t) { return 0; }); - auto out_view = out_storage.view(); - auto res = dpl_ranges::set_union(std::forward(policy), view1, view2, out_view, - merge_comp_mut{}); - return std::ranges::begin(out_view)[7].val == 7 && - (std::size_t)(res.out - std::ranges::begin(out_view)) == archetype_test_size; - }, - [](auto&&, auto&&, auto res) { return res; }, "set_union, non-const comparator"); -#endif // !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HETERO - -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HETERO - run_algo2_hetero_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - using __out_elem = typename std::ranges::range_value_t>::out_type; - sycl::usm_allocator<__out_elem, sycl::usm::alloc::shared> __out_alloc{policy.queue()}; - archetype_storage<__out_elem, decltype(__out_alloc)> out_storage(__out_alloc, 2 * archetype_test_size, - [](std::size_t) { return 0; }); - auto out_view = out_storage.view(); - auto res = dpl_ranges::set_difference(std::forward(policy), view1, view2, out_view, - merge_comp_mut{}); - return res.out == std::ranges::begin(out_view); - }, - [](auto&&, auto&&, auto res) { return res; }, "set_difference, non-const comparator"); -#endif // !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HETERO -#endif // TEST_DPCPP_BACKEND_PRESENT - -#endif //_ENABLE_STD_RANGES_TESTING - - return TestUtils::done(_ENABLE_STD_RANGES_TESTING); -} diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp index 4b3a15ebeea..597945eb2cb 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp @@ -33,9 +33,17 @@ main() using namespace test_std_ranges::archetypes; namespace dpl_ranges = oneapi::dpl::ranges; + // The permutable archetype family, i.e. the algorithms constrained by std::permutable or by + // std::sortable: reverse, remove_if, unique, partition, sort, stable_sort, is_sorted, and, with a + // non-const comparator only, partial_sort, nth_element and inplace_merge. The first section calls + // them with const callables, the second one with callables taking their arguments by reference. + + //---------------------------------------------------------------------------------------------- + // Const callables. + //---------------------------------------------------------------------------------------------- // permutable_archetype is movable but not copyable, so it is not device copyable either: the // host policies are the only ones its constraints allow. - run_algo_host_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::reverse(std::forward(policy), view); }, [](auto&& view, auto) { const auto n = std::ranges::size(view); @@ -43,32 +51,9 @@ main() }, "reverse"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { return dpl_ranges::reverse(std::forward(policy), view); }, - [](auto&& view, auto) { - const auto n = std::ranges::size(view); - return std::ranges::begin(view)[0].val == (int)n - 1 && std::ranges::begin(view)[n - 1].val == 0; - }, - "reverse"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // The storage is filled with 0, 1, 2, ... so every third element is removed. The returned - // subrange is the tail holding the removed elements. - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::remove_if(std::forward(policy), view, permutable_pred{}); - }, - [](auto&& view, auto res) { - const auto n = std::ranges::size(view); - return std::ranges::size(res) == (n + 2) / 3; - }, - "remove_if"); - // The storage is filled with 0, 1, 2, ... so every third element is removed. The returned // subrange is the tail holding the removed elements. -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::remove_if(std::forward(policy), view, permutable_pred{}); }, @@ -77,36 +62,16 @@ main() return std::ranges::size(res) == (n + 2) / 3; }, "remove_if"); -#endif // TEST_DPCPP_BACKEND_PRESENT // All the elements are unique, so nothing is dropped. - run_algo_host_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::unique(std::forward(policy), view, permutable_equiv{}); }, [](auto&& view, auto res) { return std::ranges::size(res) == 0; }, "unique"); -#if TEST_DPCPP_BACKEND_PRESENT - // All the elements are unique, so nothing is dropped. - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::unique(std::forward(policy), view, permutable_equiv{}); - }, - [](auto&& view, auto res) { return std::ranges::size(res) == 0; }, "unique"); -#endif // TEST_DPCPP_BACKEND_PRESENT - // partition returns the tail of the elements which do not satisfy the predicate. - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::partition(std::forward(policy), view, permutable_pred{}); - }, - [](auto&& view, auto res) { - return std::ranges::size(res) == std::ranges::size(view) - (std::ranges::size(view) + 2) / 3; - }, - "partition"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::partition(std::forward(policy), view, permutable_pred{}); }, @@ -114,11 +79,10 @@ main() return std::ranges::size(res) == std::ranges::size(view) - (std::ranges::size(view) + 2) / 3; }, "partition"); -#endif // TEST_DPCPP_BACKEND_PRESENT // The storage of the harness is filled in ascending order, so sorting it keeps it as it is: what // these two cases check is that the call compiles and leaves the range intact, not the ordering. - run_algo_host_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::sort(std::forward(policy), view, permutable_comp{}); }, @@ -128,53 +92,156 @@ main() }, "sort"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { - return dpl_ranges::sort(std::forward(policy), view, permutable_comp{}); + return dpl_ranges::stable_sort(std::forward(policy), view, permutable_comp{}); }, [](auto&& view, auto) { return std::ranges::begin(view)[0].val == 0 && std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; }, - "sort"); -#endif // TEST_DPCPP_BACKEND_PRESENT + "stable_sort"); - run_algo_host_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { - return dpl_ranges::stable_sort(std::forward(policy), view, permutable_comp{}); + return dpl_ranges::is_sorted(std::forward(policy), view, permutable_comp{}); + }, + [](auto&&, auto res) { return res; }, "is_sorted"); + + //---------------------------------------------------------------------------------------------- + // Callables taking their arguments by non-const reference. The element of a permutable range is + // mutable by definition, so its predicate and its comparator only ever see a non-const lvalue and + // are not required to accept a const one. + //---------------------------------------------------------------------------------------------- + // Every third element is removed; the returned subrange is the tail holding the removed elements. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::remove_if(std::forward(policy), view, permutable_pred_mut{}); + }, + [](auto&& view, auto res) { return std::ranges::size(res) == (std::ranges::size(view) + 2) / 3; }, + "remove_if, non-const callable"); + + // All the elements are unique, so nothing is dropped. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::unique(std::forward(policy), view, permutable_equiv_mut{}); + }, + [](auto&&, auto res) { return std::ranges::size(res) == 0; }, "unique, non-const callable"); + + // partition returns the tail of the elements which do not satisfy the predicate. + // + // KSATODO: the device path of partition applies the predicate to a const lvalue, which + // std::indirect_unary_predicate over a permutable iterator does not ask for, so it does not + // compile: + // - unseq_backend_sycl.h:122 - walk_n::operator() is const and calls __f(__rngs[__idx]...) on + // the const range members of single_match_pred_by_idx. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::partition(std::forward(policy), view, permutable_pred_mut{}); + }, + [](auto&& view, auto res) { + return std::ranges::size(res) == std::ranges::size(view) - (std::ranges::size(view) + 2) / 3; + }, + "partition, non-const callable"); + + // The very same comparator as the one the sorting algorithms below are called with. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_sorted(std::forward(policy), view, permutable_comp_mut{}); + }, + [](auto&&, bool res) { return res; }, "is_sorted of a permutable range, non-const comparator"); + + // KSATODO: std::sortable<_It, _Comp> only requires the comparator to be invocable with + // iter_reference_t<_It>, which is a non-const lvalue for archetype_view, so a comparator taking + // its arguments by non-const reference is enough. The parallel host merge sort compares against a + // const lvalue instead, so par and par_unseq do not compile: + // - parallel_backend_tbb.h:1037 - std::lower_bound(..., _M_comp) passes the const lvalue _Val + // of the merge split point to the comparator; + // - utils.h:203 - __binary_op::operator() forwards that const lvalue into std::invoke. + // seq and unseq keep the element non-const all the way down. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::sort(std::forward(policy), view, permutable_comp_mut{}); }, [](auto&& view, auto) { return std::ranges::begin(view)[0].val == 0 && std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; }, - "stable_sort"); + "sort, non-const comparator"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( + // KSATODO: stable_sort shares the merge sort of the parallel host policies with sort, so it is + // broken for par and par_unseq in exactly the same way, see the note above. + run_algo_all_policies( [](auto&& policy, auto&& view) { - return dpl_ranges::stable_sort(std::forward(policy), view, permutable_comp{}); + return dpl_ranges::stable_sort(std::forward(policy), view, permutable_comp_mut{}); }, [](auto&& view, auto) { return std::ranges::begin(view)[0].val == 0 && std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; }, - "stable_sort"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( + "stable_sort, non-const comparator"); + + // KSATODO: partial_sort shares the parallel merge sort with sort, so the parallel host policies + // hand a const lvalue to the comparator here as well and par / par_unseq do not compile: + // - parallel_backend_tbb.h:1023,1026,1034 - __merge_func::split_merging passes *(_M_x_beg + __ym) + // to std::upper_bound / std::lower_bound, which compares against their const lvalue parameter; + // - utils.h:203 - __binary_op::operator() forwards that const lvalue into std::invoke. + // seq and unseq keep the element non-const all the way down. + // The range is ascending already, so the first ten elements are 0 ... 9 afterwards. + run_algo_all_policies( [](auto&& policy, auto&& view) { - return dpl_ranges::is_sorted(std::forward(policy), view, permutable_comp{}); - }, - [](auto&&, auto res) { return res; }, "is_sorted"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( + return dpl_ranges::partial_sort(std::forward(policy), view, std::ranges::begin(view) + 10, + permutable_comp_mut{}); + }, + [](auto&& view, auto) { return std::ranges::begin(view)[0].val == 0 && std::ranges::begin(view)[9].val == 9; }, + "partial_sort, non-const comparator"); + + // KSATODO: std::sortable only requires the comparator to be invocable with the non-const + // reference of the element, but the parallel path of nth_element compares against a const lvalue, + // so par and par_unseq do not compile: + // - algorithm_impl.h:2841 - the partition predicate of the quickselect loop takes const _Tp& and + // passes it into std::invoke(__comp, __x, *__first). + // Taking the element by reference in that lambda is enough to fix it; seq and unseq are fine. + run_algo_all_policies( [](auto&& policy, auto&& view) { - return dpl_ranges::is_sorted(std::forward(policy), view, permutable_comp{}); + return dpl_ranges::nth_element(std::forward(policy), view, std::ranges::begin(view) + 10, + permutable_comp_mut{}); + }, + [](auto&& view, auto) { return std::ranges::begin(view)[10].val == 10; }, "nth_element, non-const comparator"); + + // KSATODO: inplace_merge does not compile with any host policy, for two independent reasons: + // - algorithm_ranges_impl.h:848 - the serial path returns __end(__r), i.e. the sentinel of the + // range, while the declared return type is std::ranges::borrowed_iterator_t<_R>. For a range + // which is not a common_range the two types differ, so seq already fails to compile. This one + // is independent of the comparator and hits any user range with a distinct sentinel type; + // - the const lvalue of the merge split point is handed to the comparator, which std::sortable + // never asks for: std::inplace_merge compares against its const value parameter for unseq, and + // parallel_backend_tbb.h:1240,1245 does the same through std::upper_bound / std::lower_bound + // for par and par_unseq. + // Both halves of the ascending range are sorted, so merging them keeps it as it is. + // + // KSATODO: the device path of inplace_merge compares two const lvalues, which std::sortable does + // not ask for, so the call does not compile: + // - parallel_backend_sycl_merge.h:128-133 - the lambda of __find_start_point captures __rng1 and + // __rng2 and subscripts them as const, and both results go into the comparator. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::inplace_merge(std::forward(policy), view, + std::ranges::begin(view) + std::ranges::size(view) / 2, + permutable_comp_mut{}); }, - [](auto&&, auto res) { return res; }, "is_sorted"); -#endif // TEST_DPCPP_BACKEND_PRESENT + [](auto&& view, auto) { + return std::ranges::begin(view)[0].val == 0 && + std::ranges::begin(view)[std::ranges::size(view) - 1].val == (int)std::ranges::size(view) - 1; + }, + "inplace_merge, non-const comparator"); #endif //_ENABLE_STD_RANGES_TESTING diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp index be288a16bb0..fb532e4338f 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp @@ -33,52 +33,33 @@ main() using namespace test_std_ranges::archetypes; namespace dpl_ranges = oneapi::dpl::ranges; + // The read_archetype family: the read-only algorithms which are parameterized by a callable only. + // Covers for_each, find_if, find_if_not, find_last_if, find_last_if_not, any_of, all_of, none_of, + // is_partitioned, count_if, min_element, max_element, minmax_element, is_sorted, is_sorted_until and + // adjacent_find, first with const callables and then with callables taking non-const references. + // read_archetype is neither copyable, movable, default constructible nor comparable; the only // operations available are the ones the callables of the algorithm provide. - run_algo_host_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::for_each(std::forward(policy), view, read_unary_fun{}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, "for_each"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::for_each(std::forward(policy), view, read_unary_fun{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, "for_each"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_if(std::forward(policy), view, read_unary_pred{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "find_if"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::find_if(std::forward(policy), view, read_unary_pred{}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "find_if"); -#endif // TEST_DPCPP_BACKEND_PRESENT - run_algo_host_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::find_if_not(std::forward(policy), view, read_unary_pred{}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + 1; }, "find_if_not"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_if_not(std::forward(policy), view, read_unary_pred{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + 1; }, "find_if_not"); -#endif // TEST_DPCPP_BACKEND_PRESENT - // The last element whose value is divisible by three, and the last one whose value is not. - run_algo_host_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::find_last_if(std::forward(policy), view, read_unary_pred{}); }, @@ -88,19 +69,7 @@ main() }, "find_last_if"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_last_if(std::forward(policy), view, read_unary_pred{}); - }, - [](auto&& view, auto res) { - auto __n = (int)std::ranges::size(view); - return std::ranges::begin(res) == std::ranges::begin(view) + (__n - 1) / 3 * 3; - }, - "find_last_if"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::find_last_if_not(std::forward(policy), view, read_unary_pred{}); }, @@ -110,170 +79,71 @@ main() }, "find_last_if_not"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_last_if_not(std::forward(policy), view, read_unary_pred{}); - }, - [](auto&& view, auto res) { - auto __n = (int)std::ranges::size(view); - return std::ranges::begin(res) == std::ranges::begin(view) + ((__n - 1) % 3 == 0 ? __n - 2 : __n - 1); - }, - "find_last_if_not"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::any_of(std::forward(policy), view, read_unary_pred{}); }, [](auto&&, bool res) { return res; }, "any_of"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::any_of(std::forward(policy), view, read_unary_pred{}); - }, - [](auto&&, bool res) { return res; }, "any_of"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::all_of(std::forward(policy), view, read_unary_pred{}); - }, - [](auto&&, bool res) { return !res; }, "all_of"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::all_of(std::forward(policy), view, read_unary_pred{}); }, [](auto&&, bool res) { return !res; }, "all_of"); -#endif // TEST_DPCPP_BACKEND_PRESENT - run_algo_host_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::none_of(std::forward(policy), view, read_unary_pred{}); }, [](auto&&, bool res) { return !res; }, "none_of"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::none_of(std::forward(policy), view, read_unary_pred{}); - }, - [](auto&&, bool res) { return !res; }, "none_of"); -#endif // TEST_DPCPP_BACKEND_PRESENT - // The predicate holds for 0, fails for 1 and holds again for 3, so the range is not partitioned. - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::is_partitioned(std::forward(policy), view, read_unary_pred{}); - }, - [](auto&&, bool res) { return !res; }, "is_partitioned"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::is_partitioned(std::forward(policy), view, read_unary_pred{}); }, [](auto&&, bool res) { return !res; }, "is_partitioned"); -#endif // TEST_DPCPP_BACKEND_PRESENT - run_algo_host_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::count_if(std::forward(policy), view, read_unary_pred{}); }, [](auto&& view, auto res) { return res == (std::ranges::range_difference_t) ((std::ranges::size(view) + 2) / 3); }, "count_if"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::count_if(std::forward(policy), view, read_unary_pred{}); - }, - [](auto&& view, auto res) { return res == (std::ranges::range_difference_t) - ((std::ranges::size(view) + 2) / 3); }, "count_if"); -#endif // TEST_DPCPP_BACKEND_PRESENT - // The projection returns an unrelated prvalue type, so the predicate can only ever be applied to // the projected value. - run_algo_host_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::find_if(std::forward(policy), view, read_proj_pred{}, read_proj{}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "find_if with proj"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_if(std::forward(policy), view, read_proj_pred{}, read_proj{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "find_if with proj"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::count_if(std::forward(policy), view, read_proj_pred{}, read_proj{}); }, [](auto&& view, auto res) { return res == (std::ranges::range_difference_t) ((std::ranges::size(view) + 2) / 3); }, "count_if with proj"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::count_if(std::forward(policy), view, read_proj_pred{}, read_proj{}); - }, - [](auto&& view, auto res) { return res == (std::ranges::range_difference_t) - ((std::ranges::size(view) + 2) / 3); }, "count_if with proj"); -#endif // TEST_DPCPP_BACKEND_PRESENT - // min_element/max_element/minmax_element only require std::indirect_strict_weak_order on the // projected iterator, so the element type stays non-copyable and non-default-constructible: both // backends carry an index and dereference the iterator for the comparison instead of storing the // element by value. - run_algo_host_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::min_element(std::forward(policy), view, read_comp{}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "min_element"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::min_element(std::forward(policy), view, read_comp{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "min_element"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::max_element(std::forward(policy), view, read_comp{}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view) - 1; }, "max_element"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::max_element(std::forward(policy), view, read_comp{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view) - 1; }, - "max_element"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::minmax_element(std::forward(policy), view, read_comp{}); - }, - [](auto&& view, auto res) { - return res.min == std::ranges::begin(view) && - res.max == std::ranges::begin(view) + std::ranges::size(view) - 1; - }, - "minmax_element"); - - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::minmax_element(std::forward(policy), view, read_comp{}); }, @@ -282,227 +152,173 @@ main() res.max == std::ranges::begin(view) + std::ranges::size(view) - 1; }, "minmax_element"); -#endif // TEST_DPCPP_BACKEND_PRESENT - run_algo_host_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::is_sorted(std::forward(policy), view, read_comp{}); }, [](auto&&, bool res) { return res; }, "is_sorted"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::is_sorted(std::forward(policy), view, read_comp{}); - }, - [](auto&&, bool res) { return res; }, "is_sorted"); -#endif // TEST_DPCPP_BACKEND_PRESENT - // The whole range is sorted, so the scan stops at its end. - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::is_sorted_until(std::forward(policy), view, read_comp{}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, - "is_sorted_until"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::is_sorted_until(std::forward(policy), view, read_comp{}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, "is_sorted_until"); -#endif // TEST_DPCPP_BACKEND_PRESENT - run_algo_host_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::adjacent_find(std::forward(policy), view, read_binary_pred{}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, "adjacent_find"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( + //---------------------------------------------------------------------------------------------- + // The same algorithms with callables taking their arguments by non-const reference. + //---------------------------------------------------------------------------------------------- + run_algo_all_policies( [](auto&& policy, auto&& view) { - return dpl_ranges::adjacent_find(std::forward(policy), view, read_binary_pred{}); + return dpl_ranges::for_each(std::forward(policy), view, read_unary_fun_mut{}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, - "adjacent_find"); -#endif // TEST_DPCPP_BACKEND_PRESENT + "for_each, non-const callable"); - // The search value type is unrelated to the element type. - run_algo_host_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { - return dpl_ranges::find(std::forward(policy), view, search_value{7}); + return dpl_ranges::find_if(std::forward(policy), view, read_unary_pred_mut{}); }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + 7; }, "find"); + [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "find_if, non-const callable"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { - return dpl_ranges::find(std::forward(policy), view, search_value{7}); + return dpl_ranges::find_if_not(std::forward(policy), view, read_unary_pred_mut{}); }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + 7; }, "find"); -#endif // TEST_DPCPP_BACKEND_PRESENT + [](auto&& view, auto res) { return res == std::ranges::begin(view) + 1; }, "find_if_not, non-const callable"); - // find_last returns the tail of the range starting at the last occurrence of the value. - run_algo_host_policies( + // The last element whose value is divisible by three, and the last one whose value is not. + run_algo_all_policies( [](auto&& policy, auto&& view) { - return dpl_ranges::find_last(std::forward(policy), view, search_value{7}); + return dpl_ranges::find_last_if(std::forward(policy), view, read_unary_pred_mut{}); }, [](auto&& view, auto res) { - return std::ranges::begin(res) == std::ranges::begin(view) + 7 && - std::ranges::size(res) == (std::ranges::range_difference_t)std::ranges::size(view) - 7; + auto __n = (int)std::ranges::size(view); + return std::ranges::begin(res) == std::ranges::begin(view) + (__n - 1) / 3 * 3; }, - "find_last"); + "find_last_if, non-const callable"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { - return dpl_ranges::find_last(std::forward(policy), view, search_value{7}); + return dpl_ranges::find_last_if_not(std::forward(policy), view, read_unary_pred_mut{}); }, [](auto&& view, auto res) { - return std::ranges::begin(res) == std::ranges::begin(view) + 7 && - std::ranges::size(res) == (std::ranges::range_difference_t)std::ranges::size(view) - 7; + auto __n = (int)std::ranges::size(view); + return std::ranges::begin(res) == std::ranges::begin(view) + ((__n - 1) % 3 == 0 ? __n - 2 : __n - 1); }, - "find_last"); -#endif // TEST_DPCPP_BACKEND_PRESENT + "find_last_if_not, non-const callable"); - run_algo_host_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { - return dpl_ranges::count(std::forward(policy), view, search_value{7}); + return dpl_ranges::any_of(std::forward(policy), view, read_unary_pred_mut{}); }, - [](auto&&, auto res) { return res == 1; }, "count"); + [](auto&&, bool res) { return res; }, "any_of, non-const callable"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( + // Every third element satisfies the predicate, so the range is neither all nor none of it, and it + // is not partitioned either. + run_algo_all_policies( [](auto&& policy, auto&& view) { - return dpl_ranges::count(std::forward(policy), view, search_value{7}); + return dpl_ranges::all_of(std::forward(policy), view, read_unary_pred_mut{}); }, - [](auto&&, auto res) { return res == 1; }, "count"); -#endif // TEST_DPCPP_BACKEND_PRESENT + [](auto&&, bool res) { return !res; }, "all_of, non-const callable"); - // Two ranges of unrelated element types, compared only through the user predicate. - run_algo2_host_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::equal(std::forward(policy), view1, view2, cross_pred{}); + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::none_of(std::forward(policy), view, read_unary_pred_mut{}); }, - [](auto&&, auto&&, bool res) { return res; }, "equal"); + [](auto&&, bool res) { return !res; }, "none_of, non-const callable"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::equal(std::forward(policy), view1, view2, cross_pred{}); + // KSATODO: std::indirect_unary_predicate only requires the predicate to be invocable with + // iter_reference_t<_It>, a non-const lvalue here, but the device path applies it to a const + // lvalue, so the call does not compile: + // - algorithm_impl_hetero.h:1078,1080 - __pattern_is_partitioned_transform_fn::operator() is + // const and takes the accessor by value, so __acc[__gidx] yields a const reference which is + // passed straight into the predicate. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_partitioned(std::forward(policy), view, read_unary_pred_mut{}); }, - [](auto&&, auto&&, bool res) { return res; }, "equal"); -#endif // TEST_DPCPP_BACKEND_PRESENT + [](auto&&, bool res) { return !res; }, "is_partitioned, non-const callable"); - run_algo2_host_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::mismatch(std::forward(policy), view1, view2, cross_pred{}); + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count_if(std::forward(policy), view, read_unary_pred_mut{}); }, - [](auto&& view1, auto&& view2, auto res) { - return res.in1 == std::ranges::begin(view1) + std::ranges::size(view1) && - res.in2 == std::ranges::begin(view2) + std::ranges::size(view2); + [](auto&& view, auto res) { + return res == (std::ranges::range_difference_t)((std::ranges::size(view) + 2) / 3); }, - "mismatch"); + "count_if, non-const callable"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::mismatch(std::forward(policy), view1, view2, cross_pred{}); - }, - [](auto&& view1, auto&& view2, auto res) { - return res.in1 == std::ranges::begin(view1) + std::ranges::size(view1) && - res.in2 == std::ranges::begin(view2) + std::ranges::size(view2); + // The projection takes the element by non-const reference; the predicate sees its prvalue result. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_if(std::forward(policy), view, read_proj_pred{}, read_proj_mut{}); }, - "mismatch"); -#endif // TEST_DPCPP_BACKEND_PRESENT + [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "find_if, non-const projection"); - // The two ranges hold the very same sequence, so the second one occurs in the first one exactly - // once, at its very beginning. - run_algo2_host_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::search(std::forward(policy), view1, view2, cross_pred{}); + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count_if(std::forward(policy), view, read_proj_pred{}, + read_proj_mut{}); }, - [](auto&& view1, auto&& view2, auto res) { - return std::ranges::begin(res) == std::ranges::begin(view1) && - std::ranges::size(res) == std::ranges::size(view2); + [](auto&& view, auto res) { + return res == (std::ranges::range_difference_t)((std::ranges::size(view) + 2) / 3); }, - "search"); + "count_if, non-const projection"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::search(std::forward(policy), view1, view2, cross_pred{}); - }, - [](auto&& view1, auto&& view2, auto res) { - return std::ranges::begin(res) == std::ranges::begin(view1) && - std::ranges::size(res) == std::ranges::size(view2); + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::adjacent_find(std::forward(policy), view, read_binary_pred_mut{}); }, - "search"); -#endif // TEST_DPCPP_BACKEND_PRESENT + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, + "adjacent_find, non-const callable"); - run_algo2_host_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::find_end(std::forward(policy), view1, view2, cross_pred{}); - }, - [](auto&& view1, auto&& view2, auto res) { - return std::ranges::begin(res) == std::ranges::begin(view1) && - std::ranges::size(res) == std::ranges::size(view2); + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_sorted(std::forward(policy), view, read_comp_mut{}); }, - "find_end"); + [](auto&&, bool res) { return res; }, "is_sorted, non-const comparator"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::find_end(std::forward(policy), view1, view2, cross_pred{}); + // The whole range is sorted, so the scan stops at its end. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_sorted_until(std::forward(policy), view, read_comp_mut{}); }, - [](auto&& view1, auto&& view2, auto res) { - return std::ranges::begin(res) == std::ranges::begin(view1) && - std::ranges::size(res) == std::ranges::size(view2); + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, + "is_sorted_until, non-const comparator"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::min_element(std::forward(policy), view, read_comp_mut{}); }, - "find_end"); -#endif // TEST_DPCPP_BACKEND_PRESENT + [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "min_element, non-const comparator"); - // KSATODO: std::indirectly_comparable<_It1, _It2, _Pred> only requires the predicate to be - // invocable as __pred(*__it1, *__it2), never the other way round. The SIMD brick swaps the two - // arguments, so the vectorized host policies unseq and par_unseq do not compile: - // - unseq_backend_simd.h:827 - __simd_find_first_of builds __u_pred as - // __pred(__val, *__first) with __val taken from the second range and *__first from the first - // one; the branch is a plain if, so it is instantiated whatever the sizes of the ranges are. - // Fixing this means keeping the argument order of the two ranges in both branches. - auto find_first_of_algo = [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::find_first_of(std::forward(policy), view1, view2, cross_pred{}); - }; - auto find_first_of_checker = [](auto&& view1, auto&&, auto res) { return res == std::ranges::begin(view1); }; + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::max_element(std::forward(policy), view, read_comp_mut{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view) - 1; }, + "max_element, non-const comparator"); -#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_FIRST_OF_HOST - run_algo2_host_policies(find_first_of_algo, find_first_of_checker, "find_first_of"); -#endif - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies(find_first_of_algo, find_first_of_checker, - "find_first_of"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // includes needs a comparator accepting the two element types in all four combinations, see - // cross_comp. Both ranges hold the very same ascending sequence, so the second one is included in - // the first one. - run_algo2_host_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::includes(std::forward(policy), view1, view2, cross_comp{}); - }, - [](auto&&, auto&&, bool res) { return res; }, "includes"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::includes(std::forward(policy), view1, view2, cross_comp{}); - }, - [](auto&&, auto&&, bool res) { return res; }, "includes"); -#endif // TEST_DPCPP_BACKEND_PRESENT + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::minmax_element(std::forward(policy), view, read_comp_mut{}); + }, + [](auto&& view, auto res) { + return res.min == std::ranges::begin(view) && + res.max == std::ranges::begin(view) + std::ranges::size(view) - 1; + }, + "minmax_element, non-const comparator"); #endif //_ENABLE_STD_RANGES_TESTING diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_storable.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_storable.pass.cpp new file mode 100644 index 00000000000..5a05a72c9b9 --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_storable.pass.cpp @@ -0,0 +1,83 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#include +#include + +#include "support/test_config.h" +#include "support/test_macros.h" +#include "support/utils.h" + +#if _ENABLE_STD_RANGES_TESTING +#include "std_ranges_archetypes.h" +#include "std_ranges_algo_archetypes_test.h" +#endif //_ENABLE_STD_RANGES_TESTING + +int +main() +{ +#if _ENABLE_STD_RANGES_TESTING + using namespace test_std_ranges; + using namespace test_std_ranges::archetypes; + namespace dpl_ranges = oneapi::dpl::ranges; + + // The storable archetype family: the algorithms which return an element by value and are therefore + // constrained by std::indirectly_copyable_storable, i.e. min, max and minmax. Covers them first + // with const comparators and then with comparators taking their arguments by non-const reference. + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::min(std::forward(policy), view, storable_comp{}); + }, + [](auto&&, auto res) { return res.val == 0; }, "min"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::max(std::forward(policy), view, storable_comp{}); + }, + [](auto&&, auto res) { return res.val == (int)archetype_test_size - 1; }, "max"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::minmax(std::forward(policy), view, storable_comp{}); + }, + [](auto&&, auto&& res) { return res.min.val == 0 && res.max.val == (int)archetype_test_size - 1; }, "minmax"); + + //---------------------------------------------------------------------------------------------- + // The same algorithms with callables taking their arguments by non-const reference. + //---------------------------------------------------------------------------------------------- + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::min(std::forward(policy), view, storable_comp_mut{}); + }, + [](auto&&, auto res) { return res.val == 0; }, "min, non-const comparator"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::max(std::forward(policy), view, storable_comp_mut{}); + }, + [](auto&&, auto res) { return res.val == (int)archetype_test_size - 1; }, "max, non-const comparator"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::minmax(std::forward(policy), view, storable_comp_mut{}); + }, + [](auto&&, auto&& res) { return res.min.val == 0 && res.max.val == (int)archetype_test_size - 1; }, + "minmax, non-const comparator"); + +#endif //_ENABLE_STD_RANGES_TESTING + + return TestUtils::done(_ENABLE_STD_RANGES_TESTING); +} diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h b/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h index 055c8eb0030..3ecbed512ba 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h @@ -1,134 +1,201 @@ -// -*- C++ -*- -//===----------------------------------------------------------------------===// -// -// Copyright (C) Intel Corporation -// -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -// This file incorporates work covered by the following copyright and permission -// notice: -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// -//===----------------------------------------------------------------------===// - -#ifndef _STD_RANGES_ALGO_ARCHETYPES_TEST_H -#define _STD_RANGES_ALGO_ARCHETYPES_TEST_H - -#include - -#include "support/test_config.h" -#include "support/utils.h" - -#if _ENABLE_STD_RANGES_TESTING - -#include "std_ranges_archetypes.h" - -#include -#include -#include -#include - -namespace test_std_ranges -{ - -// The archetypes are neither copyable nor movable, so they cannot live in a container: the storage -// is raw memory with in-place constructed elements, wrapped into archetype_view, which is random -// access and sized but neither contiguous nor common. This leaves the implementation no way to fall -// back to raw pointer arithmetic or to a hidden copy of the elements. -inline constexpr std::size_t archetype_test_size = 1000; - -// Runs a one-range algorithm and checks the result with __checker(view, result). -template -void -run_algo(_Alloc __alloc, _Policy&& __policy, _Algo __algo, _Checker __checker, const char* __algo_name) -{ - archetypes::archetype_storage<_Elem, _Alloc> __storage(__alloc, archetype_test_size, - [](std::size_t __i) { return (int)__i; }); - auto __view = __storage.view(); - - auto __res = __algo(std::forward<_Policy>(__policy), __view); - - EXPECT_TRUE(__checker(__view, __res), (std::string("wrong result from ") + __algo_name).c_str()); -} - -// Runs a two-range algorithm and checks the result with __checker(view1, view2, result). -template -void -run_algo2(_Alloc1 __alloc1, _Alloc2 __alloc2, _Policy&& __policy, _Algo __algo, _Checker __checker, - const char* __algo_name) -{ - archetypes::archetype_storage<_Elem1, _Alloc1> __storage1(__alloc1, archetype_test_size, - [](std::size_t __i) { return (int)__i; }); - archetypes::archetype_storage<_Elem2, _Alloc2> __storage2(__alloc2, archetype_test_size, - [](std::size_t __i) { return (int)__i; }); - auto __view1 = __storage1.view(); - auto __view2 = __storage2.view(); - - auto __res = __algo(std::forward<_Policy>(__policy), __view1, __view2); - - EXPECT_TRUE(__checker(__view1, __view2, __res), (std::string("wrong result from ") + __algo_name).c_str()); -} - -// Runs a one-range algorithm with the host policies only. A value argument which is neither -// copyable nor movable cannot be passed to a device kernel, so such an archetype is meaningful for -// the host policies only, where the implementation is required to keep a reference to the value. -template -void -run_algo_host_policies(_Algo __algo, _Checker __checker, const char* __algo_name) -{ - std::allocator<_Elem> __alloc; - run_algo<_Elem>(__alloc, oneapi::dpl::execution::seq, __algo, __checker, __algo_name); - run_algo<_Elem>(__alloc, oneapi::dpl::execution::unseq, __algo, __checker, __algo_name); - run_algo<_Elem>(__alloc, oneapi::dpl::execution::par, __algo, __checker, __algo_name); - run_algo<_Elem>(__alloc, oneapi::dpl::execution::par_unseq, __algo, __checker, __algo_name); -} - -// Runs a two-range algorithm with the host policies only, see run_algo_host_policies. -template -void -run_algo2_host_policies(_Algo __algo, _Checker __checker, const char* __algo_name) -{ - std::allocator<_Elem1> __alloc1; - std::allocator<_Elem2> __alloc2; - run_algo2<_Elem1, _Elem2>(__alloc1, __alloc2, oneapi::dpl::execution::seq, __algo, __checker, __algo_name); - run_algo2<_Elem1, _Elem2>(__alloc1, __alloc2, oneapi::dpl::execution::unseq, __algo, __checker, __algo_name); - run_algo2<_Elem1, _Elem2>(__alloc1, __alloc2, oneapi::dpl::execution::par, __algo, __checker, __algo_name); - run_algo2<_Elem1, _Elem2>(__alloc1, __alloc2, oneapi::dpl::execution::par_unseq, __algo, __checker, __algo_name); -} - -#if TEST_DPCPP_BACKEND_PRESENT -// A device policy passes the element type into a kernel, so the caller is expected to name the -// device copyable archetype (the _dc one) explicitly. Everything else the host only archetype lacks -// (default construction, comparison, ordering, ...) is still missing in the _dc counterpart. -// -// _CallId makes the SYCL kernel name of the device call unique: every instantiation of the harness -// submits its own kernel, and with -fno-sycl-unnamed-lambda two kernels sharing a name are a -// "definition with same mangled name" error. -template -void -run_algo_hetero_policies(_Algo __algo, _Checker __checker, const char* __algo_name) -{ - auto __policy = TestUtils::get_dpcpp_test_policy<_CallId>(); - sycl::usm_allocator<_Elem, sycl::usm::alloc::shared> __q_alloc{__policy.queue()}; - run_algo<_Elem>(__q_alloc, __policy, __algo, __checker, __algo_name); -} - -// Runs a two-range algorithm with the hetero policies, see run_algo_hetero_policies. -template -void -run_algo2_hetero_policies(_Algo __algo, _Checker __checker, const char* __algo_name) -{ - auto __policy = TestUtils::get_dpcpp_test_policy<_CallId>(); - sycl::usm_allocator<_Elem1, sycl::usm::alloc::shared> __q_alloc1{__policy.queue()}; - sycl::usm_allocator<_Elem2, sycl::usm::alloc::shared> __q_alloc2{__policy.queue()}; - run_algo2<_Elem1, _Elem2>(__q_alloc1, __q_alloc2, __policy, __algo, __checker, __algo_name); -} -#endif // TEST_DPCPP_BACKEND_PRESENT - -} //namespace test_std_ranges - -#endif //_ENABLE_STD_RANGES_TESTING -#endif //_STD_RANGES_ALGO_ARCHETYPES_TEST_H +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#ifndef _STD_RANGES_ALGO_ARCHETYPES_TEST_H +#define _STD_RANGES_ALGO_ARCHETYPES_TEST_H + +#include + +#include "support/test_config.h" +#include "support/utils.h" + +#if _ENABLE_STD_RANGES_TESTING + +#include "std_ranges_archetypes.h" + +#include +#include +#include +#include +#include + +namespace test_std_ranges +{ + +// The archetypes are neither copyable nor movable, so they cannot live in a container: the storage +// is raw memory with in-place constructed elements, wrapped into archetype_view, which is random +// access and sized but neither contiguous nor common. This leaves the implementation no way to fall +// back to raw pointer arithmetic or to a hidden copy of the elements. +inline constexpr std::size_t archetype_test_size = 1000; + +// The default content of an input range: 0, 1, 2, ... An output range is filled with zeros instead, +// see make_out_storage below. +inline constexpr auto archetype_index_factory = [](std::size_t __i) { return (int)__i; }; + +#if TEST_DPCPP_BACKEND_PRESENT +// True for the device policies, i.e. the ones carrying a SYCL queue. +template +inline constexpr bool is_device_policy_v = requires(_Policy& __policy) { __policy.queue(); }; +#else +template +inline constexpr bool is_device_policy_v = false; +#endif + +// Builds the storage of one range with the allocator matching the policy: a range touched by a device +// kernel has to live in device accessible memory, while a host policy is happy with std::allocator. +// archetype_storage is immovable, so it is returned as a prvalue and initialized directly into the +// variable of the caller. +template +auto +make_storage(_Policy&& __policy, std::size_t __n, _Factory __factory) +{ +#if TEST_DPCPP_BACKEND_PRESENT + if constexpr (is_device_policy_v>) + { + sycl::usm_allocator<_Elem, sycl::usm::alloc::shared> __alloc{__policy.queue()}; + return archetypes::archetype_storage<_Elem, decltype(__alloc)>(__alloc, __n, __factory); + } + else +#endif + { + return archetypes::archetype_storage<_Elem, std::allocator<_Elem>>(std::allocator<_Elem>{}, __n, __factory); + } +} + +// The storage of the output range of an algorithm which writes into a range of its own (merge, the +// set operations, the binary transform, ...). Every element starts as zero, so a test has to check a +// position the algorithm is expected to write a non-zero value into. +template +auto +make_out_storage(_Policy&& __policy, std::size_t __n) +{ + return make_storage<_Elem>(std::forward<_Policy>(__policy), __n, [](std::size_t) { return 0; }); +} + +// Runs a one-range algorithm and checks the result with __checker(view, result). +template +void +run_algo(_Policy&& __policy, _Algo __algo, _Checker __checker, const char* __algo_name) +{ + auto __storage = make_storage<_Elem>(__policy, archetype_test_size, archetype_index_factory); + auto __view = __storage.view(); + + auto __res = __algo(std::forward<_Policy>(__policy), __view); + + EXPECT_TRUE(__checker(__view, __res), (std::string("wrong result from ") + __algo_name).c_str()); +} + +// Runs a two-range algorithm and checks the result with __checker(view1, view2, result). +template +void +run_algo2(_Policy&& __policy, _Algo __algo, _Checker __checker, const char* __algo_name) +{ + auto __storage1 = make_storage<_Elem1>(__policy, archetype_test_size, archetype_index_factory); + auto __storage2 = make_storage<_Elem2>(__policy, archetype_test_size, archetype_index_factory); + auto __view1 = __storage1.view(); + auto __view2 = __storage2.view(); + + auto __res = __algo(std::forward<_Policy>(__policy), __view1, __view2); + + EXPECT_TRUE(__checker(__view1, __view2, __res), (std::string("wrong result from ") + __algo_name).c_str()); +} + +// Runs a one-range algorithm with the host policies only. A value argument which is neither +// copyable nor movable cannot be passed to a device kernel, so such an archetype is meaningful for +// the host policies only, where the implementation is required to keep a reference to the value. +template +void +run_algo_host_policies(_Algo __algo, _Checker __checker, const char* __algo_name) +{ + run_algo<_Elem>(oneapi::dpl::execution::seq, __algo, __checker, __algo_name); + run_algo<_Elem>(oneapi::dpl::execution::unseq, __algo, __checker, __algo_name); + run_algo<_Elem>(oneapi::dpl::execution::par, __algo, __checker, __algo_name); + run_algo<_Elem>(oneapi::dpl::execution::par_unseq, __algo, __checker, __algo_name); +} + +// Runs a two-range algorithm with the host policies only, see run_algo_host_policies. +template +void +run_algo2_host_policies(_Algo __algo, _Checker __checker, const char* __algo_name) +{ + run_algo2<_Elem1, _Elem2>(oneapi::dpl::execution::seq, __algo, __checker, __algo_name); + run_algo2<_Elem1, _Elem2>(oneapi::dpl::execution::unseq, __algo, __checker, __algo_name); + run_algo2<_Elem1, _Elem2>(oneapi::dpl::execution::par, __algo, __checker, __algo_name); + run_algo2<_Elem1, _Elem2>(oneapi::dpl::execution::par_unseq, __algo, __checker, __algo_name); +} + +#if TEST_DPCPP_BACKEND_PRESENT +// A device policy passes the element type into a kernel, so the caller is expected to name the +// device copyable archetype (the _dc one) explicitly. Everything else the host only archetype lacks +// (default construction, comparison, ordering, ...) is still missing in the _dc counterpart. +// +// _CallId makes the SYCL kernel name of the device call unique: every instantiation of the harness +// submits its own kernel, and with -fno-sycl-unnamed-lambda two kernels sharing a name are a +// "definition with same mangled name" error. Pass __LINE__, which is unique by construction; the ids +// only have to be unique inside one translation unit, and every test file is its own executable. +template +void +run_algo_hetero_policies(_Algo __algo, _Checker __checker, const char* __algo_name) +{ + run_algo<_Elem>(TestUtils::get_dpcpp_test_policy<_CallId>(), __algo, __checker, __algo_name); +} + +// Runs a two-range algorithm with the hetero policies, see run_algo_hetero_policies. +template +void +run_algo2_hetero_policies(_Algo __algo, _Checker __checker, const char* __algo_name) +{ + run_algo2<_Elem1, _Elem2>(TestUtils::get_dpcpp_test_policy<_CallId>(), __algo, __checker, __algo_name); +} +#endif // TEST_DPCPP_BACKEND_PRESENT + +// Runs one and the same generic lambda with the host and with the hetero policies: _Elem is the host +// only archetype and _ElemDc its device copyable counterpart, so the lambda has to derive every other +// type it needs (a value argument, an output element type) from the element type it is handed. +// +// _RunHost and _RunHetero switch one side off where the implementation is known to ask for more than +// the requires-clause of the algorithm allows. A false branch is discarded by if constexpr, so the +// call is not instantiated at all and the compilation error stays away. +template +void +run_algo_all_policies(_Algo __algo, _Checker __checker, const char* __algo_name) +{ + if constexpr (_RunHost) + run_algo_host_policies<_Elem>(__algo, __checker, __algo_name); +#if TEST_DPCPP_BACKEND_PRESENT + if constexpr (_RunHetero) + run_algo_hetero_policies<_ElemDc, _CallId>(__algo, __checker, __algo_name); +#endif +} + +// Runs a two-range algorithm with both the host and the hetero policies, see run_algo_all_policies. +template +void +run_algo2_all_policies(_Algo __algo, _Checker __checker, const char* __algo_name) +{ + if constexpr (_RunHost) + run_algo2_host_policies<_Elem1, _Elem2>(__algo, __checker, __algo_name); +#if TEST_DPCPP_BACKEND_PRESENT + if constexpr (_RunHetero) + run_algo2_hetero_policies<_Elem1Dc, _Elem2Dc, _CallId>(__algo, __checker, __algo_name); +#endif +} + +} //namespace test_std_ranges + +#endif //_ENABLE_STD_RANGES_TESTING +#endif //_STD_RANGES_ALGO_ARCHETYPES_TEST_H diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp index ac0fbecf6a3..9a3e870b930 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp @@ -33,172 +33,149 @@ main() using namespace test_std_ranges::archetypes; namespace dpl_ranges = oneapi::dpl::ranges; + // This file covers the search value archetype family: searchable_archetype and + // removable_archetype (with their device copyable _dc counterparts) as the element type, and + // search_value / nocopy_search_value as the searched value. The algorithms are find, find_last, + // count, contains and remove, both with const callables and with a non-const projection. + // The storage is filled with the values 0, 1, 2, ... so the value 3 is found exactly once. constexpr int searched = 3; + //---------------------------------------------------------------------------------------------- + // The value based algorithms: the search value is compared with std::ranges::equal_to, so the + // value type itself is the only requirement beyond the element type. + //---------------------------------------------------------------------------------------------- // search_value is trivially copyable and thus device copyable, so it can be used with all the // policies including the device ones. - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find(std::forward(policy), view, search_value{searched}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::find(std::forward(policy), view, search_value{searched}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find"); -#endif // TEST_DPCPP_BACKEND_PRESENT - run_algo_host_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::find_last(std::forward(policy), view, search_value{searched}); }, [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, "find_last"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_last(std::forward(policy), view, search_value{searched}); - }, - [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, - "find_last"); -#endif - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::count(std::forward(policy), view, search_value{searched}); - }, - [](auto&&, auto res) { return res == 1; }, "count"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::count(std::forward(policy), view, search_value{searched}); }, [](auto&&, auto res) { return res == 1; }, "count"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::contains(std::forward(policy), view, search_value{searched}); - }, - [](auto&&, auto res) { return res; }, "contains"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::contains(std::forward(policy), view, search_value{searched}); }, [](auto&&, auto res) { return res; }, "contains"); -#endif // TEST_DPCPP_BACKEND_PRESENT - // removable_archetype is movable but not device copyable, so remove() is checked on the host - // policies only. - run_algo_host_policies( + // remove() moves the surviving elements over the removed ones, so its element type has to be + // movable: removable_archetype adds a move constructor and move assignment to the searchable + // archetype and nothing else. + run_algo_all_policies( [](auto&& policy, auto&& view) { return dpl_ranges::remove(std::forward(policy), view, search_value{searched}); }, // remove() returns the tail holding the removed elements, and the value occurs exactly once. [](auto&&, auto res) { return std::ranges::size(res) == 1; }, "remove"); -#if TEST_DPCPP_BACKEND_PRESENT - // removable_archetype is movable but not device copyable, so remove() is checked on the host - // policies only. - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::remove(std::forward(policy), view, search_value{searched}); - }, - // remove() returns the tail holding the removed elements, and the value occurs exactly once. - [](auto&&, auto res) { return std::ranges::size(res) == 1; }, "remove"); -#endif // TEST_DPCPP_BACKEND_PRESENT - // nocopy_search_value is neither copyable nor movable: the host implementations must refer to - // the value passed by the user instead of storing a copy of it. It cannot be captured by a - // device kernel, hence the host policies only. - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find(std::forward(policy), view, nocopy_search_value{searched}); - }, - [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find, noncopyable value"); - -#if TEST_DPCPP_BACKEND_PRESENT - // A device policy copies the value into the kernel, so the hetero runs use the device copyable - // counterpart of the value: it is still neither default constructible nor ordered. - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find(std::forward(policy), view, nocopy_search_value_dc{searched}); + // the value passed by the user instead of storing a copy of it. + // + // A device policy has to copy the value into the kernel, so the hetero runs cannot use that very + // type and take its device copyable counterpart instead, which is still neither default + // constructible nor ordered. The element archetype names the matching value type as + // nocopy_value_type, so one generic lambda serves both sides. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + using elem_t = std::ranges::range_value_t>; + return dpl_ranges::find(std::forward(policy), view, + typename elem_t::nocopy_value_type{searched}); }, [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, "find, noncopyable value"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::find_last(std::forward(policy), view, nocopy_search_value{searched}); - }, - [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, - "find_last, noncopyable value"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { + using elem_t = std::ranges::range_value_t>; return dpl_ranges::find_last(std::forward(policy), view, - nocopy_search_value_dc{searched}); + typename elem_t::nocopy_value_type{searched}); }, [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, "find_last, noncopyable value"); -#endif // TEST_DPCPP_BACKEND_PRESENT // count() must refer to the value instead of storing a copy of it: the requires-clause never // asks for a copyable value type. - run_algo_host_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { - return dpl_ranges::count(std::forward(policy), view, nocopy_search_value{searched}); + using elem_t = std::ranges::range_value_t>; + return dpl_ranges::count(std::forward(policy), view, + typename elem_t::nocopy_value_type{searched}); }, [](auto&&, auto res) { return res == 1; }, "count, noncopyable value"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::count(std::forward(policy), view, nocopy_search_value_dc{searched}); - }, - [](auto&&, auto res) { return res == 1; }, "count, noncopyable value"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo_host_policies( - [](auto&& policy, auto&& view) { - return dpl_ranges::contains(std::forward(policy), view, nocopy_search_value{searched}); - }, - [](auto&&, auto res) { return res; }, "contains, noncopyable value"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { - return dpl_ranges::contains(std::forward(policy), view, nocopy_search_value_dc{searched}); + using elem_t = std::ranges::range_value_t>; + return dpl_ranges::contains(std::forward(policy), view, + typename elem_t::nocopy_value_type{searched}); }, [](auto&&, auto res) { return res; }, "contains, noncopyable value"); -#endif // TEST_DPCPP_BACKEND_PRESENT // Same for remove(): the predicate it builds internally must hold a reference to the value for // the host policies. - run_algo_host_policies( + run_algo_all_policies( [](auto&& policy, auto&& view) { - return dpl_ranges::remove(std::forward(policy), view, nocopy_search_value{searched}); + using elem_t = std::ranges::range_value_t>; + return dpl_ranges::remove(std::forward(policy), view, + typename elem_t::nocopy_value_type{searched}); }, // remove() returns the tail holding the removed elements, and the value occurs exactly once. [](auto&&, auto res) { return std::ranges::size(res) == 1; }, "remove, noncopyable value"); -#if TEST_DPCPP_BACKEND_PRESENT - run_algo_hetero_policies( + //---------------------------------------------------------------------------------------------- + // Callables taking their arguments by non-const reference: the value based algorithms with a + // projection taking the element by non-const reference. + //---------------------------------------------------------------------------------------------- + run_algo_all_policies( [](auto&& policy, auto&& view) { - return dpl_ranges::remove(std::forward(policy), view, nocopy_search_value_dc{searched}); + return dpl_ranges::find(std::forward(policy), view, search_value{searched}, + search_proj_mut{}); }, - // remove() returns the tail holding the removed elements, and the value occurs exactly once. - [](auto&&, auto res) { return std::ranges::size(res) == 1; }, "remove, noncopyable value"); -#endif // TEST_DPCPP_BACKEND_PRESENT + [](auto&& view, auto res) { return res == std::ranges::begin(view) + searched; }, + "find, non-const projection"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::find_last(std::forward(policy), view, search_value{searched}, + search_proj_mut{}); + }, + [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, + "find_last, non-const projection"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::count(std::forward(policy), view, search_value{searched}, + search_proj_mut{}); + }, + [](auto&&, auto res) { return res == 1; }, "count, non-const projection"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::contains(std::forward(policy), view, search_value{searched}, + search_proj_mut{}); + }, + [](auto&&, auto res) { return res; }, "contains, non-const projection"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::remove(std::forward(policy), view, search_value{searched}, + search_proj_mut{}); + }, + // remove() returns the tail holding the removed elements, and the value 3 occurs exactly once. + [](auto&&, auto res) { return std::ranges::size(res) == 1; }, "remove, non-const projection"); #endif //_ENABLE_STD_RANGES_TESTING diff --git a/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp b/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp index a4cd5a5fa67..c8442432977 100644 --- a/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp @@ -1,211 +1,180 @@ -// -*- C++ -*- -//===----------------------------------------------------------------------===// -// -// Copyright (C) Intel Corporation -// -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -// This file incorporates work covered by the following copyright and permission -// notice: -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// -//===----------------------------------------------------------------------===// - -#include -#include - -#include "support/test_config.h" -#include "support/test_macros.h" -#include "support/utils.h" - -#if _ENABLE_STD_RANGES_TESTING -#include "std_ranges_archetypes.h" -#include "std_ranges_algo_archetypes_test.h" -#endif //_ENABLE_STD_RANGES_TESTING - -int -main() -{ -#if _ENABLE_STD_RANGES_TESTING - using namespace test_std_ranges; - using namespace test_std_ranges::archetypes; - namespace dpl_ranges = oneapi::dpl::ranges; - - // None of the archetypes below is device copyable, so the host policies are the only ones the - // constraints of these algorithms allow. - run_algo_host_policies( - [](auto&& policy, auto&& view) { - using __elem = std::ranges::range_value_t>; - return dpl_ranges::fill(std::forward(policy), view, typename __elem::value_arg{42}); - }, - [](auto&& view, auto) { - return std::ranges::begin(view)[0].val == 42 && - std::ranges::begin(view)[std::ranges::size(view) - 1].val == 42; - }, - "fill"); - -#if TEST_DPCPP_BACKEND_PRESENT - // None of the archetypes below is device copyable, so the host policies are the only ones the - // constraints of these algorithms allow. - run_algo_hetero_policies( - [](auto&& policy, auto&& view) { - using __elem = std::ranges::range_value_t>; - return dpl_ranges::fill(std::forward(policy), view, typename __elem::value_arg{42}); - }, - [](auto&& view, auto) { - return std::ranges::begin(view)[0].val == 42 && - std::ranges::begin(view)[std::ranges::size(view) - 1].val == 42; - }, - "fill"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo2_host_policies( - [](auto&& policy, auto&& in_view, auto&& out_view) { - return dpl_ranges::copy(std::forward(policy), in_view, out_view); - }, - [](auto&& in_view, auto&& out_view, auto) { - return std::ranges::begin(out_view)[7].val == std::ranges::begin(in_view)[7].val; - }, - "copy"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies( - [](auto&& policy, auto&& in_view, auto&& out_view) { - return dpl_ranges::copy(std::forward(policy), in_view, out_view); - }, - [](auto&& in_view, auto&& out_view, auto) { - return std::ranges::begin(out_view)[7].val == std::ranges::begin(in_view)[7].val; - }, - "copy"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo2_host_policies( - [](auto&& policy, auto&& in_view, auto&& out_view) { - return dpl_ranges::move(std::forward(policy), in_view, out_view); - }, - [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 7; }, "move"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies( - [](auto&& policy, auto&& in_view, auto&& out_view) { - return dpl_ranges::move(std::forward(policy), in_view, out_view); - }, - [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 7; }, "move"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo2_host_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::swap_ranges(std::forward(policy), view1, view2); - }, - [](auto&& view1, auto&& view2, auto) { - return std::ranges::begin(view1)[7].val == 7 && std::ranges::begin(view2)[7].val == 7; - }, - "swap_ranges"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - return dpl_ranges::swap_ranges(std::forward(policy), view1, view2); - }, - [](auto&& view1, auto&& view2, auto) { - return std::ranges::begin(view1)[7].val == 7 && std::ranges::begin(view2)[7].val == 7; - }, - "swap_ranges"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - run_algo2_host_policies( - [](auto&& policy, auto&& in_view, auto&& out_view) { - return dpl_ranges::transform(std::forward(policy), in_view, out_view, - transform_unary_op{}); - }, - [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 14; }, "transform"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies( - [](auto&& policy, auto&& in_view, auto&& out_view) { - return dpl_ranges::transform(std::forward(policy), in_view, out_view, - transform_unary_op{}); - }, - [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 14; }, "transform"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // The same overload with a non-identity projection: the functor is invoked with the projected - // value, which is neither the element nor the output element type. - run_algo2_host_policies( - [](auto&& policy, auto&& in_view, auto&& out_view) { - return dpl_ranges::transform(std::forward(policy), in_view, out_view, - transform_projected_unary_op{}, transform_proj{}); - }, - [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 16; }, - "transform, projection"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies( - [](auto&& policy, auto&& in_view, auto&& out_view) { - return dpl_ranges::transform(std::forward(policy), in_view, out_view, - transform_projected_unary_op{}, transform_proj{}); - }, - [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 16; }, - "transform, projection"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // The binary overload takes two input ranges, so the output range is allocated inside the call - // and the check is done there as well. - run_algo2_host_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - archetype_storage> out_storage( - std::allocator{}, archetype_test_size, [](std::size_t) { return 0; }); - auto out_view = out_storage.view(); - auto res = dpl_ranges::transform(std::forward(policy), view1, view2, out_view, - transform_binary_op{}); - return std::ranges::begin(out_view)[7].val == 14 && res.out == std::ranges::end(out_view); - }, - [](auto&&, auto&&, auto res) { return res; }, "transform, binary"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - // The output range is written by a device kernel, so its storage has to be device - // accessible: host memory from std::allocator would be dereferenced on the device. - sycl::usm_allocator out_alloc{policy.queue()}; - archetype_storage out_storage( - out_alloc, archetype_test_size, [](std::size_t) { return 0; }); - auto out_view = out_storage.view(); - auto res = dpl_ranges::transform(std::forward(policy), view1, view2, out_view, - transform_binary_op{}); - return std::ranges::begin(out_view)[7].val == 14 && res.out == std::ranges::end(out_view); - }, - [](auto&&, auto&&, auto res) { return res; }, "transform, binary"); -#endif // TEST_DPCPP_BACKEND_PRESENT - - // The binary overload has a projection of its own for either input. - run_algo2_host_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - archetype_storage> out_storage( - std::allocator{}, archetype_test_size, [](std::size_t) { return 0; }); - auto out_view = out_storage.view(); - auto res = dpl_ranges::transform(std::forward(policy), view1, view2, out_view, - transform_projected_binary_op{}, transform_proj{}, transform_proj{}); - return std::ranges::begin(out_view)[7].val == 16 && res.out == std::ranges::end(out_view); - }, - [](auto&&, auto&&, auto res) { return res; }, "transform, binary, projections"); - -#if TEST_DPCPP_BACKEND_PRESENT - run_algo2_hetero_policies( - [](auto&& policy, auto&& view1, auto&& view2) { - sycl::usm_allocator out_alloc{policy.queue()}; - archetype_storage out_storage( - out_alloc, archetype_test_size, [](std::size_t) { return 0; }); - auto out_view = out_storage.view(); - auto res = dpl_ranges::transform(std::forward(policy), view1, view2, out_view, - transform_projected_binary_op{}, transform_proj{}, transform_proj{}); - return std::ranges::begin(out_view)[7].val == 16 && res.out == std::ranges::end(out_view); - }, - [](auto&&, auto&&, auto res) { return res; }, "transform, binary, projections"); -#endif // TEST_DPCPP_BACKEND_PRESENT -#endif //_ENABLE_STD_RANGES_TESTING - - return TestUtils::done(_ENABLE_STD_RANGES_TESTING); -} +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#include +#include + +#include "support/test_config.h" +#include "support/test_macros.h" +#include "support/utils.h" + +#if _ENABLE_STD_RANGES_TESTING +#include "std_ranges_archetypes.h" +#include "std_ranges_algo_archetypes_test.h" +#endif //_ENABLE_STD_RANGES_TESTING + +int +main() +{ +#if _ENABLE_STD_RANGES_TESTING + using namespace test_std_ranges; + using namespace test_std_ranges::archetypes; + namespace dpl_ranges = oneapi::dpl::ranges; + + // The write archetype families: an element which is only assignable, from an unrelated value type + // (fill), from the element of another range (copy, move, swap_ranges) or from the result of a + // functor (transform, with and without projections). Nothing here is copyable, movable or default + // constructible, and the elements written from are of a different type than the ones written to. + + //---------------------------------------------------------------------------------------------- + // The writing algorithms; every callable takes its arguments by const reference. + //---------------------------------------------------------------------------------------------- + run_algo_all_policies( + [](auto&& policy, auto&& view) { + using __elem = std::ranges::range_value_t>; + return dpl_ranges::fill(std::forward(policy), view, typename __elem::value_arg{42}); + }, + [](auto&& view, auto) { + return std::ranges::begin(view)[0].val == 42 && + std::ranges::begin(view)[std::ranges::size(view) - 1].val == 42; + }, + "fill"); + + run_algo2_all_policies( + [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::copy(std::forward(policy), in_view, out_view); + }, + [](auto&& in_view, auto&& out_view, auto) { + return std::ranges::begin(out_view)[7].val == std::ranges::begin(in_view)[7].val; + }, + "copy"); + + run_algo2_all_policies( + [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::move(std::forward(policy), in_view, out_view); + }, + [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 7; }, "move"); + + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::swap_ranges(std::forward(policy), view1, view2); + }, + [](auto&& view1, auto&& view2, auto) { + return std::ranges::begin(view1)[7].val == 7 && std::ranges::begin(view2)[7].val == 7; + }, + "swap_ranges"); + + run_algo2_all_policies( + [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::transform(std::forward(policy), in_view, out_view, + transform_unary_op{}); + }, + [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 14; }, "transform"); + + // The same overload with a non-identity projection: the functor is invoked with the projected + // value, which is neither the element nor the output element type. + run_algo2_all_policies( + [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::transform(std::forward(policy), in_view, out_view, + transform_projected_unary_op{}, transform_proj{}); + }, + [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 16; }, + "transform, projection"); + + // The binary overload takes two input ranges, so the output range is allocated inside the call + // and the check is done there as well. + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + using elem_t = std::ranges::range_value_t>; + auto out_storage = make_out_storage(policy, archetype_test_size); + auto out_view = out_storage.view(); + auto res = dpl_ranges::transform(std::forward(policy), view1, view2, out_view, + transform_binary_op{}); + return std::ranges::begin(out_view)[7].val == 14 && res.out == std::ranges::end(out_view); + }, + [](auto&&, auto&&, auto res) { return res; }, "transform, binary"); + + // The binary overload has a projection of its own for either input. + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + using elem_t = std::ranges::range_value_t>; + auto out_storage = make_out_storage(policy, archetype_test_size); + auto out_view = out_storage.view(); + auto res = dpl_ranges::transform(std::forward(policy), view1, view2, out_view, + transform_projected_binary_op{}, transform_proj{}, transform_proj{}); + return std::ranges::begin(out_view)[7].val == 16 && res.out == std::ranges::end(out_view); + }, + [](auto&&, auto&&, auto res) { return res; }, "transform, binary, projections"); + + //---------------------------------------------------------------------------------------------- + // The same algorithms with callables taking their arguments by non-const reference. + //---------------------------------------------------------------------------------------------- + run_algo2_all_policies( + [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::transform(std::forward(policy), in_view, out_view, + transform_unary_op_mut{}); + }, + [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 14; }, + "transform, non-const callable"); + + // The projection is the one taking the element by non-const reference here: the functor is + // invoked with the projected prvalue and cannot take it by non-const reference at all. + run_algo2_all_policies( + [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::transform(std::forward(policy), in_view, out_view, + transform_projected_unary_op{}, transform_proj_mut{}); + }, + [](auto&&, auto&& out_view, auto) { return std::ranges::begin(out_view)[7].val == 16; }, + "transform, non-const projection"); + + // The binary overload with a functor taking both input elements by non-const reference. It takes + // two input ranges, so the output range is allocated inside the call and checked there as well. + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + using elem_t = std::ranges::range_value_t>; + auto out_storage = make_out_storage(policy, archetype_test_size); + auto out_view = out_storage.view(); + auto res = dpl_ranges::transform(std::forward(policy), view1, view2, out_view, + transform_binary_op_mut{}); + return std::ranges::begin(out_view)[7].val == 14 && res.out == std::ranges::end(out_view); + }, + [](auto&&, auto&&, auto res) { return res; }, "transform, binary, non-const callable"); + + // The binary overload with a non-const projection for either input. + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + using elem_t = std::ranges::range_value_t>; + auto out_storage = make_out_storage(policy, archetype_test_size); + auto out_view = out_storage.view(); + auto res = + dpl_ranges::transform(std::forward(policy), view1, view2, out_view, + transform_projected_binary_op{}, transform_proj_mut{}, transform_proj_mut{}); + return std::ranges::begin(out_view)[7].val == 16 && res.out == std::ranges::end(out_view); + }, + [](auto&&, auto&&, auto res) { return res; }, "transform, binary, non-const projections"); +#endif //_ENABLE_STD_RANGES_TESTING + + return TestUtils::done(_ENABLE_STD_RANGES_TESTING); +} From cfa82d0f0a26b845f1d078b45202601e03530c69 Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Wed, 9 Sep 2026 13:31:08 +0200 Subject: [PATCH 085/148] Use LF line endings in the memory archetype test Every other file of the test suite uses LF, and the file had ended up with CRLF endings plus a single LF line added by the previous commit, i.e. genuinely mixed endings. The content is unchanged. Co-Authored-By: Claude Opus 5 --- .../std_ranges_memory_archetypes.pass.cpp | 274 +++++++++--------- 1 file changed, 137 insertions(+), 137 deletions(-) diff --git a/test/parallel_api/ranges/std_ranges_memory_archetypes.pass.cpp b/test/parallel_api/ranges/std_ranges_memory_archetypes.pass.cpp index 71b10651d04..5a814c180f3 100644 --- a/test/parallel_api/ranges/std_ranges_memory_archetypes.pass.cpp +++ b/test/parallel_api/ranges/std_ranges_memory_archetypes.pass.cpp @@ -1,137 +1,137 @@ -// -*- C++ -*- -//===----------------------------------------------------------------------===// -// -// Copyright (C) Intel Corporation -// -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -// This file incorporates work covered by the following copyright and permission -// notice: -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// -//===----------------------------------------------------------------------===// - -#include -#include - -#include "support/test_config.h" -#include "support/test_macros.h" -#include "support/utils.h" - -#if _ENABLE_STD_RANGES_TESTING -#include "std_ranges_memory_archetypes_test.h" - -namespace test_std_ranges -{ -template<> -constexpr int test_mode_id> = 1; -template<> -constexpr int test_mode_id> = 1; - -} //namespace test_std_ranges -#endif //_ENABLE_STD_RANGES_TESTING - -int -main() -{ -#if _ENABLE_STD_RANGES_TESTING - using namespace test_std_ranges; - using namespace test_std_ranges::archetypes; - namespace dpl_ranges = oneapi::dpl::ranges; - - // The single required operation is std::default_initializable. The default constructor is - // user-provided, so only val1 is written and val2 must keep the no-initialization pattern. - auto default_construct_checker = - [](const auto& res, const auto& r) { - using R = std::remove_cvref_t; - bool bres1 = (res == std::ranges::borrowed_iterator_t(std::ranges::begin(r) + std::ranges::size(r))); - bool bres2 = std::ranges::all_of(r, [](const auto& v) { return v.val1 == 1 && v.val2 == -1;}); - - return std::pair{bres1, bres2}; - }; - - test_memory_algo{}.run(dpl_ranges::uninitialized_default_construct, default_construct_checker); - - // The default constructor is defaulted on its first declaration, so value-initialization - // zero-initializes the whole object, including val2. - auto value_construct_checker = - [](const auto& res, const auto& r) { - using R = std::remove_cvref_t; - bool bres1 = (res == std::ranges::borrowed_iterator_t(std::ranges::begin(r) + std::ranges::size(r))); - bool bres2 = std::ranges::all_of(r, [](const auto& v) { return v.val1 == 0 && v.val2 == 0;}); - - return std::pair{bres1, bres2}; - }; - - test_memory_algo{}.run(dpl_ranges::uninitialized_value_construct, value_construct_checker); - - // The filler type differs from the range value type, so the only required operation is - // std::constructible_from. - auto fill_checker = - [](const auto& res, const auto& r, const auto& value) { - using R = std::remove_cvref_t; - bool bres1 = (res == std::ranges::borrowed_iterator_t(std::ranges::begin(r) + std::ranges::size(r))); - bool bres2 = std::ranges::all_of(r, [](const auto& v) { return v.val1 == -1;}) - && std::ranges::all_of(r, [value](const auto& v) { return v.val2 == value.val;}); - - return std::pair{bres1, bres2}; - }; - - test_memory_algo{}.run(dpl_ranges::uninitialized_fill, fill_checker, fill_source{2}); - - // Input and output element types are different, which the requires-clause of uninitialized_copy - // and uninitialized_move explicitly allows. copy_archetype is constructible only from - // transfer_source&, move_archetype only from transfer_source&&. - auto transfer_checker = - [](const auto& res, auto&& r_in, auto&& r_out) { - using InRange = std::remove_cvref_t; - using OutRange = std::remove_cvref_t; - - using Size = std::common_type_t, std::ranges::range_size_t>; - const Size sz = std::ranges::min((Size)std::ranges::size(r_in), (Size)std::ranges::size(r_out)); - - const bool bres1 = (res.in == std::ranges::borrowed_iterator_t(std::ranges::begin(r_in) + sz) - && res.out == std::ranges::borrowed_iterator_t(std::ranges::begin(r_out) + sz)); - - const bool bres2 = std::ranges::all_of(r_out, [](const auto& v) { return v.val1 == -1;}) - && std::ranges::equal(std::ranges::take_view(r_in, sz), std::ranges::take_view(r_out, sz), - [](const auto& v1, const auto& v2) { return v1.val2 == v2.val2;}) - && std::ranges::all_of(std::ranges::drop_view(r_out, sz), [](const auto& v) { return v.val2 == -1;}); - - return std::pair{bres1, bres2}; - }; - - test_memory_algo{}.run(dpl_ranges::uninitialized_copy, transfer_checker); - test_memory_algo{}.run(dpl_ranges::uninitialized_move, transfer_checker); - - // The single required operation is std::destructible. - auto destroy_checker = - [](const auto& res, const auto& r) { - using R = std::remove_cvref_t; - bool bres1 = (res == std::ranges::borrowed_iterator_t(std::ranges::begin(r) + std::ranges::size(r))); - bool bres2 = std::ranges::all_of(r, [](const auto& v) { return v.val1 == -1 && v.val2 == 3;}); - - return std::pair{bres1, bres2}; - }; - - test_memory_algo{}.run(dpl_ranges::destroy, destroy_checker); - - // The same algorithms over a range which is random access and sized, but neither contiguous nor - // common. - run_archetype_view_all_policies( - dpl_ranges::uninitialized_default_construct, - [](const auto& v) { return v.val1 == 1 && v.val2 == -1; }, "uninitialized_default_construct"); - - run_archetype_view_all_policies( - dpl_ranges::uninitialized_value_construct, - [](const auto& v) { return v.val1 == 0 && v.val2 == 0; }, "uninitialized_value_construct"); - - run_archetype_view_all_policies( - dpl_ranges::destroy, [](const auto& v) { return v.val1 == -1 && v.val2 == 3; }, "destroy"); - -#endif //_ENABLE_STD_RANGES_TESTING - - return TestUtils::done(_ENABLE_STD_RANGES_TESTING); -} +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Copyright (C) Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This file incorporates work covered by the following copyright and permission +// notice: +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// +//===----------------------------------------------------------------------===// + +#include +#include + +#include "support/test_config.h" +#include "support/test_macros.h" +#include "support/utils.h" + +#if _ENABLE_STD_RANGES_TESTING +#include "std_ranges_memory_archetypes_test.h" + +namespace test_std_ranges +{ +template<> +constexpr int test_mode_id> = 1; +template<> +constexpr int test_mode_id> = 1; + +} //namespace test_std_ranges +#endif //_ENABLE_STD_RANGES_TESTING + +int +main() +{ +#if _ENABLE_STD_RANGES_TESTING + using namespace test_std_ranges; + using namespace test_std_ranges::archetypes; + namespace dpl_ranges = oneapi::dpl::ranges; + + // The single required operation is std::default_initializable. The default constructor is + // user-provided, so only val1 is written and val2 must keep the no-initialization pattern. + auto default_construct_checker = + [](const auto& res, const auto& r) { + using R = std::remove_cvref_t; + bool bres1 = (res == std::ranges::borrowed_iterator_t(std::ranges::begin(r) + std::ranges::size(r))); + bool bres2 = std::ranges::all_of(r, [](const auto& v) { return v.val1 == 1 && v.val2 == -1;}); + + return std::pair{bres1, bres2}; + }; + + test_memory_algo{}.run(dpl_ranges::uninitialized_default_construct, default_construct_checker); + + // The default constructor is defaulted on its first declaration, so value-initialization + // zero-initializes the whole object, including val2. + auto value_construct_checker = + [](const auto& res, const auto& r) { + using R = std::remove_cvref_t; + bool bres1 = (res == std::ranges::borrowed_iterator_t(std::ranges::begin(r) + std::ranges::size(r))); + bool bres2 = std::ranges::all_of(r, [](const auto& v) { return v.val1 == 0 && v.val2 == 0;}); + + return std::pair{bres1, bres2}; + }; + + test_memory_algo{}.run(dpl_ranges::uninitialized_value_construct, value_construct_checker); + + // The filler type differs from the range value type, so the only required operation is + // std::constructible_from. + auto fill_checker = + [](const auto& res, const auto& r, const auto& value) { + using R = std::remove_cvref_t; + bool bres1 = (res == std::ranges::borrowed_iterator_t(std::ranges::begin(r) + std::ranges::size(r))); + bool bres2 = std::ranges::all_of(r, [](const auto& v) { return v.val1 == -1;}) + && std::ranges::all_of(r, [value](const auto& v) { return v.val2 == value.val;}); + + return std::pair{bres1, bres2}; + }; + + test_memory_algo{}.run(dpl_ranges::uninitialized_fill, fill_checker, fill_source{2}); + + // Input and output element types are different, which the requires-clause of uninitialized_copy + // and uninitialized_move explicitly allows. copy_archetype is constructible only from + // transfer_source&, move_archetype only from transfer_source&&. + auto transfer_checker = + [](const auto& res, auto&& r_in, auto&& r_out) { + using InRange = std::remove_cvref_t; + using OutRange = std::remove_cvref_t; + + using Size = std::common_type_t, std::ranges::range_size_t>; + const Size sz = std::ranges::min((Size)std::ranges::size(r_in), (Size)std::ranges::size(r_out)); + + const bool bres1 = (res.in == std::ranges::borrowed_iterator_t(std::ranges::begin(r_in) + sz) + && res.out == std::ranges::borrowed_iterator_t(std::ranges::begin(r_out) + sz)); + + const bool bres2 = std::ranges::all_of(r_out, [](const auto& v) { return v.val1 == -1;}) + && std::ranges::equal(std::ranges::take_view(r_in, sz), std::ranges::take_view(r_out, sz), + [](const auto& v1, const auto& v2) { return v1.val2 == v2.val2;}) + && std::ranges::all_of(std::ranges::drop_view(r_out, sz), [](const auto& v) { return v.val2 == -1;}); + + return std::pair{bres1, bres2}; + }; + + test_memory_algo{}.run(dpl_ranges::uninitialized_copy, transfer_checker); + test_memory_algo{}.run(dpl_ranges::uninitialized_move, transfer_checker); + + // The single required operation is std::destructible. + auto destroy_checker = + [](const auto& res, const auto& r) { + using R = std::remove_cvref_t; + bool bres1 = (res == std::ranges::borrowed_iterator_t(std::ranges::begin(r) + std::ranges::size(r))); + bool bres2 = std::ranges::all_of(r, [](const auto& v) { return v.val1 == -1 && v.val2 == 3;}); + + return std::pair{bres1, bres2}; + }; + + test_memory_algo{}.run(dpl_ranges::destroy, destroy_checker); + + // The same algorithms over a range which is random access and sized, but neither contiguous nor + // common. + run_archetype_view_all_policies( + dpl_ranges::uninitialized_default_construct, + [](const auto& v) { return v.val1 == 1 && v.val2 == -1; }, "uninitialized_default_construct"); + + run_archetype_view_all_policies( + dpl_ranges::uninitialized_value_construct, + [](const auto& v) { return v.val1 == 0 && v.val2 == 0; }, "uninitialized_value_construct"); + + run_archetype_view_all_policies( + dpl_ranges::destroy, [](const auto& v) { return v.val1 == -1 && v.val2 == 3; }, "destroy"); + +#endif //_ENABLE_STD_RANGES_TESTING + + return TestUtils::done(_ENABLE_STD_RANGES_TESTING); +} From 4f98c62ae185f1936c6f9c710a19a3bd6af7396f Mon Sep 17 00:00:00 2001 From: Sergey Kopienko Date: Wed, 9 Sep 2026 17:31:07 +0200 Subject: [PATCH 086/148] Add the archetype definitions the missing coverage needs The follow-up commit fills the empty cells of the archetype matrix and adds a range which offers no more than the requires-clauses ask for. Everything the new calls are built from lives in the archetype headers, so it lands first and on its own; nothing here is instantiated yet, so no test changes behaviour with this commit. Element types for the calls which pass no callable at all: ordered_archetype is totally_ordered and nothing else, equality_archetype is equality_comparable and explicitly not totally_ordered, and the merge, the permute and the storable families get their own pair of the same shape. Those elements are the only way to instantiate the default comparator path of an implementation, where the ordering has to come from the element type through std::ranges::less instead of from a user comparator. Element types and callables for the write family, which had the least coverage: replaceable_archetype and remove_copy_in_archetype together with the common_type specializations and the replace_common element the value-taking algorithms need, copy_out_archetype for the copying ones, and write_pred, copy_pred and copy_equiv plus the _mut variants of all three, which take their argument by non-const reference. A callable of that shape is the only one which pins the reference down: the projected reference of archetype_view is a non-const lvalue, so an implementation which copies the element, or which hands a const one to the callable, does not compile against it. replace_proj_mut, psort_copy_comp_mut and the two psort_copy projections do the same for the remaining families. Two families are new. psort_copy_* is the pair partial_sort_copy needs: an input which is only indirectly_copyable into the output, and an output which alone is sortable, which is what the requires-clause of that algorithm asks for and no more. permutable_proj_key is a projection to an integer key, the shape which selects the radix sort on the device, so that path gets an archetype element as well. std_ranges_archetypes_base.h gains the range which is deliberately poorer than archetype_view: plain_archetype_view satisfies the very same concepts, random_access_range, sized_range and borrowed_range, through its iterator and its sentinel alone, but it does not derive from std::ranges::view_interface and therefore has no size(), no operator[], no empty() and no front(). An implementation which reaches for a member of the user range instead of going through std::ranges::begin, end or size does not compile against it. archetype_storage::view() takes the view template as a template parameter, so both ranges come from the same storage, and the has_view_interface_members concept asserts the difference between the two. It is a concept and not a requires-expression on the type itself because a requirement whose expression is non-dependent is diagnosed right away instead of being a substitution failure. archetype_iterator loses its pointer typedef and its operator->, which no iterator concept asks an iterator for. Co-Authored-By: Claude Opus 5 --- .../ranges/std_ranges_archetypes_base.h | 72 +++- .../ranges/std_ranges_archetypes_merge.h | 119 ++++++ .../ranges/std_ranges_archetypes_permute.h | 320 +++++++++++++++ .../ranges/std_ranges_archetypes_read.h | 96 +++++ .../ranges/std_ranges_archetypes_storable.h | 57 +++ .../ranges/std_ranges_archetypes_write.h | 379 ++++++++++++++++++ 6 files changed, 1036 insertions(+), 7 deletions(-) diff --git a/test/parallel_api/ranges/std_ranges_archetypes_base.h b/test/parallel_api/ranges/std_ranges_archetypes_base.h index a4b061c4096..16c9e79cf28 100644 --- a/test/parallel_api/ranges/std_ranges_archetypes_base.h +++ b/test/parallel_api/ranges/std_ranges_archetypes_base.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -40,8 +41,8 @@ // compiles and works with an archetype, the implementation does not silently require more from a // user type than it declares; otherwise the extra requirement shows up as a compilation error. // -// Each archetype keeps two observable fields, val1 and val2, so that a test can check which part of -// the raw memory has been written, exactly as the pre-existing Elem/Elem_0 types do. +// Each archetype keeps one observable field, val, so that a test can check what has been written into +// the raw memory it owns, exactly as the pre-existing Elem/Elem_0 types do. // Unary operator& is not required by any constraint, so a conforming implementation has to use // std::addressof instead of taking the address directly. Define this macro to 0 to relax the @@ -78,8 +79,8 @@ // Checks that a device copyable archetype really is accepted by SYCL without an explicit // sycl::is_device_copyable specialization. #if TEST_DPCPP_BACKEND_PRESENT -# define TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(_Name) \ - static_assert(std::is_trivially_copyable_v<_Name>); \ +# define TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(_Name) \ + static_assert(std::is_trivially_copyable_v<_Name>); \ static_assert(sycl::is_device_copyable_v<_Name>); #else # define TEST_ARCHETYPE_CHECK_DEVICE_COPYABLE(_Name) static_assert(std::is_trivially_copyable_v<_Name>); @@ -104,7 +105,9 @@ class archetype_iterator using value_type = T; using difference_type = std::ptrdiff_t; using reference = T&; - using pointer = T*; + // No pointer typedef and no operator-> on purpose: std::random_access_iterator asks for neither, + // and both of them would hand the implementation the address of an element whose operator& the + // archetypes deliberately delete. archetype_iterator() = default; explicit archetype_iterator(T* p) : ptr(p) {} @@ -112,7 +115,6 @@ class archetype_iterator T* base() const { return ptr; } reference operator*() const { return *ptr; } - pointer operator->() const { return ptr; } reference operator[](difference_type n) const { return ptr[n]; } archetype_iterator& operator++() { ++ptr; return *this; } @@ -167,12 +169,37 @@ class archetype_view : public std::ranges::view_interface> archetype_sentinel end() const { return archetype_sentinel(last); } }; +// The very same range without std::ranges::view_interface. It satisfies exactly the same concepts, +// all of them through its iterator and its sentinel alone, but it has no size(), no operator[], no +// empty(), no front() and no back(). No requires-clause of any algorithm asks for those members, so +// an implementation which reads the user range through anything but std::ranges::begin / end / size +// does not compile with it. +template +class plain_archetype_view +{ + T* first = nullptr; + T* last = nullptr; + + public: + plain_archetype_view() = default; + plain_archetype_view(T* p, std::size_t n) : first(p), last(p + n) {} + + archetype_iterator begin() const { return archetype_iterator(first); } + archetype_sentinel end() const { return archetype_sentinel(last); } +}; + } // namespace archetypes } // namespace test_std_ranges template inline constexpr bool std::ranges::enable_borrowed_range> = true; +// view_interface is what marks archetype_view as a view, so the plain range has to say so itself. +template +inline constexpr bool std::ranges::enable_borrowed_range> = true; +template +inline constexpr bool std::ranges::enable_view> = true; + namespace test_std_ranges { namespace archetypes @@ -188,6 +215,31 @@ static_assert(std::ranges::borrowed_range>); static_assert(!std::ranges::contiguous_range>); static_assert(!std::ranges::common_range>); +static_assert(std::ranges::view>); +static_assert(std::ranges::random_access_range>); +static_assert(std::ranges::sized_range>); +static_assert(std::ranges::borrowed_range>); +static_assert(!std::ranges::contiguous_range>); +static_assert(!std::ranges::common_range>); +static_assert(std::same_as>, int&>); + +// The members std::ranges::view_interface provides for a sized random access range. They are a concept +// and not a requires-expression on the type itself, because a requirement whose expression is +// non-dependent is diagnosed right away instead of being a substitution failure. back() is not in the +// list: view_interface constrains it to a common_range, which neither of the two views is. +template +concept has_view_interface_members = requires(_R& __r) { + __r.size(); + __r[0]; + __r.empty(); + __r.front(); +}; + +// All of them are deliberately missing from the plain range: its size is only reachable through the +// difference of its sentinel and its iterator, and its elements only through its iterator. +static_assert(has_view_interface_members>); +static_assert(!has_view_interface_members>); + // The two extra requirements of __nothrow_random_access_range beyond random_access_range. static_assert(std::is_lvalue_reference_v>>); static_assert(std::same_as>>, @@ -228,7 +280,13 @@ class archetype_storage std::size_t size() const { return count; } T* begin_ptr() const { return data; } - archetype_view view() const { return archetype_view(data, count); } + // The range handed to the algorithms. The view template is a parameter so that one and the same + // storage can also be presented as plain_archetype_view, see run_algo_plain. + template