diff --git a/include/oneapi/dpl/pstl/algorithm_fwd.h b/include/oneapi/dpl/pstl/algorithm_fwd.h index f0b7e6cc3d8..7406ae347e0 100644 --- a/include/oneapi/dpl/pstl/algorithm_fwd.h +++ b/include/oneapi/dpl/pstl/algorithm_fwd.h @@ -126,18 +126,18 @@ _RandomAccessIterator2 __brick_walk2_n(_RandomAccessIterator1, _Size, _RandomAcc template _ForwardIterator2 -__pattern_walk2(_Tag, _ExecutionPolicy&&, _ForwardIterator1, _ForwardIterator1, _ForwardIterator2, _Function) noexcept; +__pattern_walk2(_Tag, _ExecutionPolicy&&, _ForwardIterator1, _ForwardIterator1, _ForwardIterator2, _Function&&) noexcept; template _RandomAccessIterator2 __pattern_walk2(__parallel_tag<_IsVector>, _ExecutionPolicy&&, _RandomAccessIterator1, _RandomAccessIterator1, - _RandomAccessIterator2, _Function); + _RandomAccessIterator2, _Function&&); template _ForwardIterator2 __pattern_walk2(__parallel_forward_tag, _ExecutionPolicy&&, _ForwardIterator1, _ForwardIterator1, _ForwardIterator2, - _Function); + _Function&&); template diff --git a/include/oneapi/dpl/pstl/algorithm_impl.h b/include/oneapi/dpl/pstl/algorithm_impl.h index 64635cce5a2..c7cf8d7bff0 100644 --- a/include/oneapi/dpl/pstl/algorithm_impl.h +++ b/include/oneapi/dpl/pstl/algorithm_impl.h @@ -278,25 +278,25 @@ __brick_walk2_n(_RandomAccessIterator1 __first1, _Size __n, _RandomAccessIterato template _ForwardIterator2 __pattern_walk2(_Tag, _ExecutionPolicy&&, _ForwardIterator1 __first1, _ForwardIterator1 __last1, - _ForwardIterator2 __first2, _Function __f) noexcept + _ForwardIterator2 __first2, _Function&& __f) noexcept { static_assert(__is_serial_tag_v<_Tag>); - return __internal::__brick_walk2(__first1, __last1, __first2, __f, typename _Tag::__is_vector{}); + return __internal::__brick_walk2(__first1, __last1, __first2, std::forward<_Function>(__f), typename _Tag::__is_vector{}); } template _RandomAccessIterator2 __pattern_walk2(__parallel_tag<_IsVector>, _ExecutionPolicy&& __exec, _RandomAccessIterator1 __first1, - _RandomAccessIterator1 __last1, _RandomAccessIterator2 __first2, _Function __f) + _RandomAccessIterator1 __last1, _RandomAccessIterator2 __first2, _Function&& __f) { using __backend_tag = typename __parallel_tag<_IsVector>::__backend_tag; return __internal::__except_handler([&]() { __par_backend::__parallel_for( __backend_tag{}, ::std::forward<_ExecutionPolicy>(__exec), __first1, __last1, - [__f, __first1, __first2](_RandomAccessIterator1 __i, _RandomAccessIterator1 __j) { + [&__f, __first1, __first2](_RandomAccessIterator1 __i, _RandomAccessIterator1 __j) { __internal::__brick_walk2(__i, __j, __first2 + (__i - __first1), __f, _IsVector{}); }); return __first2 + (__last1 - __first1); @@ -306,7 +306,7 @@ __pattern_walk2(__parallel_tag<_IsVector>, _ExecutionPolicy&& __exec, _RandomAcc template _ForwardIterator2 __pattern_walk2(__parallel_forward_tag, _ExecutionPolicy&& __exec, _ForwardIterator1 __first1, - _ForwardIterator1 __last1, _ForwardIterator2 __first2, _Function __f) + _ForwardIterator1 __last1, _ForwardIterator2 __first2, _Function&& __f) { using __backend_tag = typename __parallel_forward_tag::__backend_tag; @@ -346,10 +346,10 @@ template _RandomAccessIterator2 __pattern_walk2_n(__parallel_tag<_IsVector> __tag, _ExecutionPolicy&& __exec, _RandomAccessIterator1 __first1, - _Size __n, _RandomAccessIterator2 __first2, _Function __f) + _Size __n, _RandomAccessIterator2 __first2, _Function&& __f) { - return __internal::__pattern_walk2(__tag, ::std::forward<_ExecutionPolicy>(__exec), __first1, __first1 + __n, - __first2, __f); + return __internal::__pattern_walk2(__tag, std::forward<_ExecutionPolicy>(__exec), __first1, __first1 + __n, + __first2, std::forward<_Function>(__f)); } template diff --git a/include/oneapi/dpl/pstl/algorithm_ranges_impl.h b/include/oneapi/dpl/pstl/algorithm_ranges_impl.h index e0c5d161e40..c54b8a3da63 100644 --- a/include/oneapi/dpl/pstl/algorithm_ranges_impl.h +++ b/include/oneapi/dpl/pstl/algorithm_ranges_impl.h @@ -126,17 +126,25 @@ __pattern_find_if(_Tag __tag, _ExecutionPolicy&& __exec, _R&& __r, _Pred __pred, { static_assert(__is_parallel_tag_v<_Tag> || typename _Tag::__is_vector{}); + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred>( + __pred); + return oneapi::dpl::__internal::__pattern_find_if( __tag, std::forward<_ExecutionPolicy>(__exec), std::ranges::begin(__r), std::ranges::begin(__r) + std::ranges::size(__r), - oneapi::dpl::__internal::__unary_op<_Pred, _Proj>{__pred, __proj}); + oneapi::dpl::__internal::__unary_op{__relax_non_const_pred, __proj}); } template std::ranges::borrowed_iterator_t<_R> __pattern_find_if(__serial_tag, _ExecutionPolicy&&, _R&& __r, _Pred __pred, _Proj __proj) { - return std::ranges::find_if(std::forward<_R>(__r), __pred, __proj); + return std::ranges::find_if( + std::forward<_R>(__r), + oneapi::dpl::__internal::__get_relax_non_const_pred>( + __pred), + __proj); } //--------------------------------------------------------------------------------------------------------------------- @@ -151,11 +159,17 @@ __pattern_find_first_of(_Tag __tag, _ExecutionPolicy&& __exec, _R1&& __r1, _R2&& { static_assert(__is_parallel_tag_v<_Tag> || typename _Tag::__is_vector{}); + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __pred); + return oneapi::dpl::__internal::__pattern_find_first_of( __tag, std::forward<_ExecutionPolicy>(__exec), std::ranges::begin(__r1), std::ranges::begin(__r1) + std::ranges::size(__r1), std::ranges::begin(__r2), std::ranges::begin(__r2) + std::ranges::size(__r2), - oneapi::dpl::__internal::__binary_op<_Pred, _Proj1, _Proj2>{__pred, __proj1, __proj2}); + oneapi::dpl::__internal::__binary_op{__relax_non_const_pred, + __proj1, __proj2}); } template @@ -163,7 +177,13 @@ std::ranges::borrowed_iterator_t<_R1> __pattern_find_first_of(__serial_tag, _ExecutionPolicy&&, _R1&& __r1, _R2&& __r2, _Pred __pred, _Proj1 __proj1, _Proj2 __proj2) { - return std::ranges::find_first_of(std::forward<_R1>(__r1), std::forward<_R2>(__r2), __pred, __proj1, __proj2); + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __pred); + + return std::ranges::find_first_of(std::forward<_R1>(__r1), std::forward<_R2>(__r2), __relax_non_const_pred, __proj1, + __proj2); } //--------------------------------------------------------------------------------------------------------------------- @@ -187,9 +207,15 @@ __pattern_find_end(_Tag __tag, _ExecutionPolicy&& __exec, _R1&& __r1, _R2&& __r2 auto __first2 = std::ranges::begin(__r2); auto __last2 = __first2 + __n2; + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __pred); + auto __it = oneapi::dpl::__internal::__pattern_find_end( __tag, std::forward<_ExecutionPolicy>(__exec), __first1, __last1, __first2, __last2, - oneapi::dpl::__internal::__binary_op<_Pred, _Proj1, _Proj2>{__pred, __proj1, __proj2}); + oneapi::dpl::__internal::__binary_op{__relax_non_const_pred, + __proj1, __proj2}); return {__it, __it + (__it == __last1 ? 0 : __n2)}; } @@ -199,7 +225,12 @@ std::ranges::borrowed_subrange_t<_R1> __pattern_find_end(__serial_tag, _ExecutionPolicy&&, _R1&& __r1, _R2&& __r2, _Pred __pred, _Proj1 __proj1, _Proj2 __proj2) { - return std::ranges::find_end(std::forward<_R1>(__r1), std::forward<_R2>(__r2), __pred, __proj1, __proj2); + return std::ranges::find_end( + std::forward<_R1>(__r1), std::forward<_R2>(__r2), + oneapi::dpl::__internal::__get_relax_non_const_pred, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __pred), + __proj1, __proj2); } //--------------------------------------------------------------------------------------------------------------------- @@ -212,17 +243,25 @@ __pattern_any_of(_Tag __tag, _ExecutionPolicy&& __exec, _R&& __r, _Pred __pred, { static_assert(__is_parallel_tag_v<_Tag> || typename _Tag::__is_vector{}); - return oneapi::dpl::__internal::__pattern_any_of(__tag, std::forward<_ExecutionPolicy>(__exec), - std::ranges::begin(__r), - std::ranges::begin(__r) + std::ranges::size(__r), - oneapi::dpl::__internal::__unary_op<_Pred, _Proj>{__pred, __proj}); + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred>( + __pred); + + return oneapi::dpl::__internal::__pattern_any_of( + __tag, std::forward<_ExecutionPolicy>(__exec), std::ranges::begin(__r), + std::ranges::begin(__r) + std::ranges::size(__r), + oneapi::dpl::__internal::__unary_op{__relax_non_const_pred, __proj}); } template bool __pattern_any_of(__serial_tag, _ExecutionPolicy&&, _R&& __r, _Pred __pred, _Proj __proj) { - return std::ranges::any_of(std::forward<_R>(__r), __pred, __proj); + return std::ranges::any_of( + std::forward<_R>(__r), + oneapi::dpl::__internal::__get_relax_non_const_pred>( + __pred), + __proj); } //--------------------------------------------------------------------------------------------------------------------- @@ -235,10 +274,16 @@ __pattern_adjacent_find_ranges(_Tag __tag, _ExecutionPolicy&& __exec, _R&& __r, { static_assert(__is_parallel_tag_v<_Tag> || typename _Tag::__is_vector{}); + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred, + oneapi::dpl::__internal::__projected_value_t<_Proj, _R>>( + __pred); + return oneapi::dpl::__internal::__pattern_adjacent_find( __tag, std::forward<_ExecutionPolicy>(__exec), std::ranges::begin(__r), std::ranges::begin(__r) + std::ranges::size(__r), - oneapi::dpl::__internal::__binary_op<_Pred, _Proj, _Proj>{__pred, __proj, __proj}, + oneapi::dpl::__internal::__binary_op{__relax_non_const_pred, + __proj, __proj}, oneapi::dpl::__internal::__first_semantic()); } @@ -247,7 +292,12 @@ std::ranges::borrowed_iterator_t<_R> __pattern_adjacent_find_ranges(__serial_tag, _ExecutionPolicy&&, _R&& __r, _Pred __pred, _Proj __proj) { - return std::ranges::adjacent_find(std::forward<_R>(__r), __pred, __proj); + return std::ranges::adjacent_find( + std::forward<_R>(__r), + oneapi::dpl::__internal::__get_relax_non_const_pred, + oneapi::dpl::__internal::__projected_value_t<_Proj, _R>>( + __pred), + __proj); } //--------------------------------------------------------------------------------------------------------------------- @@ -269,9 +319,15 @@ __pattern_search(_Tag __tag, _ExecutionPolicy&& __exec, _R1&& __r1, _R2&& __r2, auto __first2 = std::ranges::begin(__r2); auto __last2 = __first2 + __n2; + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __pred); + auto __res = oneapi::dpl::__internal::__pattern_search( __tag, std::forward<_ExecutionPolicy>(__exec), __first1, __last1, __first2, __last2, - oneapi::dpl::__internal::__binary_op<_Pred, _Proj1, _Proj2>{__pred, __proj1, __proj2}); + oneapi::dpl::__internal::__binary_op{__relax_non_const_pred, + __proj1, __proj2}); return {__res, __res == __last1 ? __res : __res + __n2}; } @@ -281,7 +337,12 @@ std::ranges::borrowed_subrange_t<_R1> __pattern_search(__serial_tag, _ExecutionPolicy&&, _R1&& __r1, _R2&& __r2, _Pred __pred, _Proj1 __proj1, _Proj2 __proj2) { - return std::ranges::search(std::forward<_R1>(__r1), std::forward<_R2>(__r2), __pred, __proj1, __proj2); + return std::ranges::search( + std::forward<_R1>(__r1), std::forward<_R2>(__r2), + oneapi::dpl::__internal::__get_relax_non_const_pred, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __pred), + __proj1, __proj2); } //--------------------------------------------------------------------------------------------------------------------- @@ -295,10 +356,15 @@ __pattern_search_n(_Tag __tag, _ExecutionPolicy&& __exec, _R&& __r, std::ranges: { static_assert(__is_parallel_tag_v<_Tag> || typename _Tag::__is_vector{}); + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred, + _T>(__pred); + auto __res = oneapi::dpl::__internal::__pattern_search_n( __tag, std::forward<_ExecutionPolicy>(__exec), std::ranges::begin(__r), std::ranges::begin(__r) + std::ranges::size(__r), __count, __value, - oneapi::dpl::__internal::__binary_op<_Pred, _Proj, oneapi::dpl::identity>{__pred, __proj}); + oneapi::dpl::__internal::__binary_op{ + __relax_non_const_pred, __proj}); return {__res, __res == std::ranges::end(__r) ? __res : __res + __count}; } @@ -308,7 +374,11 @@ std::ranges::borrowed_subrange_t<_R> __pattern_search_n(__serial_tag, _ExecutionPolicy&&, _R&& __r, std::ranges::range_difference_t<_R> __count, const _T& __value, _Pred __pred, _Proj __proj) { - return std::ranges::search_n(std::forward<_R>(__r), __count, __value, __pred, __proj); + return std::ranges::search_n( + std::forward<_R>(__r), __count, __value, + oneapi::dpl::__internal::__get_relax_non_const_pred, + _T>(__pred), + __proj); } //--------------------------------------------------------------------------------------------------------------------- @@ -321,17 +391,25 @@ __pattern_count_if(_Tag __tag, _ExecutionPolicy&& __exec, _R&& __r, _Pred __pred { static_assert(__is_parallel_tag_v<_Tag> || typename _Tag::__is_vector{}); - return oneapi::dpl::__internal::__pattern_count(__tag, std::forward<_ExecutionPolicy>(__exec), - std::ranges::begin(__r), - std::ranges::begin(__r) + std::ranges::size(__r), - oneapi::dpl::__internal::__unary_op<_Pred, _Proj>{__pred, __proj}); + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred>( + __pred); + + return oneapi::dpl::__internal::__pattern_count( + __tag, std::forward<_ExecutionPolicy>(__exec), std::ranges::begin(__r), + std::ranges::begin(__r) + std::ranges::size(__r), + oneapi::dpl::__internal::__unary_op{__relax_non_const_pred, __proj}); } template std::ranges::range_difference_t<_R> __pattern_count_if(__serial_tag, _ExecutionPolicy&&, _R&& __r, _Pred __pred, _Proj __proj) { - return std::ranges::count_if(std::forward<_R>(__r), __pred, __proj); + return std::ranges::count_if( + std::forward<_R>(__r), + oneapi::dpl::__internal::__get_relax_non_const_pred>( + __pred), + __proj); } //--------------------------------------------------------------------------------------------------------------------- @@ -369,19 +447,31 @@ __pattern_equal(_Tag __tag, _ExecutionPolicy&& __exec, _R1&& __r1, _R2&& __r2, _ { static_assert(__is_parallel_tag_v<_Tag> || typename _Tag::__is_vector{}); + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __pred); + return oneapi::dpl::__internal::__pattern_equal( __tag, std::forward<_ExecutionPolicy>(__exec), std::ranges::begin(__r1), std::ranges::begin(__r1) + std::ranges::size(__r1), std::ranges::begin(__r2), std::ranges::begin(__r2) + std::ranges::size(__r2), - oneapi::dpl::__internal::__binary_op<_Pred, _Proj1, _Proj2>{__pred, __proj1, __proj2}); + oneapi::dpl::__internal::__binary_op{__relax_non_const_pred, + __proj1, __proj2}); } template bool -__pattern_equal(__serial_tag, _ExecutionPolicy&&, _R1&& __r1, _R2&& __r2, _Pred __pred, _Proj1 __proj1, _Proj2 __proj2) +__pattern_equal(__serial_tag, _ExecutionPolicy&&, _R1&& __r1, _R2&& __r2, _Pred __pred, + _Proj1 __proj1, _Proj2 __proj2) { - return std::ranges::equal(std::forward<_R1>(__r1), std::forward<_R2>(__r2), __pred, __proj1, __proj2); + return std::ranges::equal( + std::forward<_R1>(__r1), std::forward<_R2>(__r2), + oneapi::dpl::__internal::__get_relax_non_const_pred, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __pred), + __proj1, __proj2); } //--------------------------------------------------------------------------------------------------------------------- @@ -395,11 +485,17 @@ __pattern_lexicographical_compare(_Tag __tag, _ExecutionPolicy&& __exec, _R1&& _ { static_assert(__is_parallel_tag_v<_Tag> || typename _Tag::__is_vector{}); + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __comp); + return oneapi::dpl::__internal::__pattern_lexicographical_compare( __tag, std::forward<_ExecutionPolicy>(__exec), std::ranges::begin(__r1), std::ranges::begin(__r1) + std::ranges::size(__r1), std::ranges::begin(__r2), std::ranges::begin(__r2) + std::ranges::size(__r2), - oneapi::dpl::__internal::__binary_op<_Comp, _Proj1, _Proj2>{__comp, __proj1, __proj2}); + oneapi::dpl::__internal::__binary_op{__relax_non_const_comp, + __proj1, __proj2}); } template @@ -407,8 +503,12 @@ bool __pattern_lexicographical_compare(__serial_tag, _ExecutionPolicy&&, _R1&& __r1, _R2&& __r2, _Comp __comp, _Proj1 __proj1, _Proj2 __proj2) { - return std::ranges::lexicographical_compare(std::forward<_R1>(__r1), std::forward<_R2>(__r2), - __comp, __proj1, __proj2); + return std::ranges::lexicographical_compare( + std::forward<_R1>(__r1), std::forward<_R2>(__r2), + oneapi::dpl::__internal::__get_relax_non_const_comp, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __comp), + __proj1, __proj2); } //--------------------------------------------------------------------------------------------------------------------- @@ -421,11 +521,16 @@ __pattern_is_sorted(_Tag __tag, _ExecutionPolicy&& __exec, _R&& __r, _Comp __com { static_assert(__is_parallel_tag_v<_Tag> || typename _Tag::__is_vector{}); + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp); + return oneapi::dpl::__internal::__pattern_adjacent_find( __tag, std::forward<_ExecutionPolicy>(__exec), std::ranges::begin(__r), std::ranges::begin(__r) + std::ranges::size(__r), oneapi::dpl::__internal::__reorder_pred( - oneapi::dpl::__internal::__binary_op<_Comp, _Proj, _Proj>{__comp, __proj, __proj}), + oneapi::dpl::__internal::__binary_op{ + __relax_non_const_comp, __proj, __proj}), oneapi::dpl::__internal::__or_semantic()) == __r.end(); } @@ -433,7 +538,11 @@ template , _ExecutionPolicy&&, _R&& __r, _Comp __comp, _Proj __proj) { - return std::ranges::is_sorted(std::forward<_R>(__r), __comp, __proj); + return std::ranges::is_sorted( + std::forward<_R>(__r), + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp), + __proj); } //--------------------------------------------------------------------------------------------------------------------- @@ -447,12 +556,16 @@ __pattern_sort_ranges(_Tag __tag, _ExecutionPolicy&& __exec, _R&& __r, _Comp __c { static_assert(__is_parallel_tag_v<_Tag> || typename _Tag::__is_vector{}); - auto __first = std::ranges::begin(__r); - auto __last = __first + std::ranges::size(__r); + auto [__first, __last] = oneapi::dpl::__ranges::__bounds(__r); + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp); oneapi::dpl::__internal::__pattern_sort( __tag, std::forward<_ExecutionPolicy>(__exec), __first, __last, - oneapi::dpl::__internal::__binary_op<_Comp, _Proj, _Proj>{__comp, __proj, __proj}, __leaf_sort); + oneapi::dpl::__internal::__binary_op{__relax_non_const_comp, + __proj, __proj}, + __leaf_sort); return __last; } @@ -462,7 +575,11 @@ std::ranges::borrowed_iterator_t<_R> __pattern_sort_ranges(__serial_tag, _ExecutionPolicy&&, _R&& __r, _Comp __comp, _Proj __proj, _LeafSort __leaf_sort) { - return __leaf_sort(std::forward<_R>(__r), __comp, __proj); + return __leaf_sort( + std::forward<_R>(__r), + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp), + __proj); } //--------------------------------------------------------------------------------------------------------------------- @@ -478,9 +595,14 @@ __pattern_partial_sort_ranges(_Tag __tag, _ExecutionPolicy&& __exec, _R&& __r, s auto [__first, __last] = oneapi::dpl::__ranges::__bounds(__r); + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp); + oneapi::dpl::__internal::__pattern_partial_sort( __tag, std::forward<_ExecutionPolicy>(__exec), __first, __middle, __last, - oneapi::dpl::__internal::__binary_op<_Comp, _Proj, _Proj>{__comp, __proj, __proj}); + oneapi::dpl::__internal::__binary_op{__relax_non_const_comp, + __proj, __proj}); return __last; } @@ -490,7 +612,11 @@ std::ranges::borrowed_iterator_t<_R> __pattern_partial_sort_ranges(__serial_tag, _ExecutionPolicy&& __exec, _R&& __r, std::ranges::iterator_t<_R> __middle, _Comp __comp, _Proj __proj) { - return std::ranges::partial_sort(std::forward<_R>(__r), __middle, __comp, __proj); + return std::ranges::partial_sort( + std::forward<_R>(__r), __middle, + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp), + __proj); } //--------------------------------------------------------------------------------------------------------------------- @@ -507,10 +633,14 @@ __pattern_partial_sort_copy_ranges(_Tag __tag, _ExecutionPolicy&& __exec, _R&& _ auto [__first, __last] = oneapi::dpl::__ranges::__bounds(__r); auto [__out_first, __out_last] = oneapi::dpl::__ranges::__bounds(__out_r); + auto __relax_non_const_comp = oneapi::dpl::__internal::__get_relax_non_const_comp< + oneapi::dpl::__internal::__projected_value_t<_Proj2, _OutR>>(__comp); + // __pattern_partial_sort_copy sorts after copying, so _Proj1 is not used auto __out_finish = oneapi::dpl::__internal::__pattern_partial_sort_copy( __tag, std::forward<_ExecutionPolicy>(__exec), __first, __last, __out_first, __out_last, - oneapi::dpl::__internal::__binary_op<_Comp, _Proj2, _Proj2>{__comp, __proj2, __proj2}); + oneapi::dpl::__internal::__binary_op{__relax_non_const_comp, + __proj2, __proj2}); return {__last, __out_finish}; } @@ -522,8 +652,11 @@ __pattern_partial_sort_copy_ranges(__serial_tag<_IsVector>, _ExecutionPolicy&& _ _Comp __comp, _Proj1 __proj1, _Proj2 __proj2) { // Use the standard implementation for both seq and unseq policies - return std::ranges::partial_sort_copy(std::forward<_R>(__r), std::forward<_OutR>(__out_r), __comp, __proj1, - __proj2); + return std::ranges::partial_sort_copy(std::forward<_R>(__r), std::forward<_OutR>(__out_r), + oneapi::dpl::__internal::__get_relax_non_const_comp< + oneapi::dpl::__internal::__projected_value_t<_Proj1, _R>, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _OutR>>(__comp), + __proj1, __proj2); } //--------------------------------------------------------------------------------------------------------------------- @@ -536,17 +669,26 @@ __pattern_is_heap(_Tag __tag, _ExecutionPolicy&& __exec, _R&& __r, _Comp __comp, { static_assert(__is_parallel_tag_v<_Tag> || typename _Tag::__is_vector{}); + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp); + return oneapi::dpl::__internal::__pattern_is_heap( __tag, std::forward<_ExecutionPolicy>(__exec), std::ranges::begin(__r), std::ranges::begin(__r) + std::ranges::size(__r), - oneapi::dpl::__internal::__binary_op<_Comp, _Proj, _Proj>{__comp, __proj, __proj}); + oneapi::dpl::__internal::__binary_op{__relax_non_const_comp, + __proj, __proj}); } template bool __pattern_is_heap(__serial_tag, _ExecutionPolicy&&, _R&& __r, _Comp __comp, _Proj __proj) { - return std::ranges::is_heap(std::forward<_R>(__r), __comp, __proj); + return std::ranges::is_heap( + std::forward<_R>(__r), + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp), + __proj); } //--------------------------------------------------------------------------------------------------------------------- @@ -559,10 +701,15 @@ __pattern_is_heap_until(_Tag __tag, _ExecutionPolicy&& __exec, _R&& __r, _Comp _ { static_assert(__is_parallel_tag_v<_Tag> || typename _Tag::__is_vector{}); + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp); + return oneapi::dpl::__internal::__pattern_is_heap_until( __tag, std::forward<_ExecutionPolicy>(__exec), std::ranges::begin(__r), std::ranges::begin(__r) + std::ranges::size(__r), - oneapi::dpl::__internal::__binary_op<_Comp, _Proj, _Proj>{__comp, __proj, __proj}); + oneapi::dpl::__internal::__binary_op{__relax_non_const_comp, + __proj, __proj}); } template @@ -570,7 +717,11 @@ std::ranges::borrowed_iterator_t<_R> __pattern_is_heap_until(__serial_tag, _ExecutionPolicy&&, _R&& __r, _Comp __comp, _Proj __proj) { - return std::ranges::is_heap_until(std::forward<_R>(__r), __comp, __proj); + return std::ranges::is_heap_until( + std::forward<_R>(__r), + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp), + __proj); } //--------------------------------------------------------------------------------------------------------------------- @@ -583,10 +734,15 @@ __pattern_min_element(_Tag __tag, _ExecutionPolicy&& __exec, _R&& __r, _Comp __c { static_assert(__is_parallel_tag_v<_Tag> || typename _Tag::__is_vector{}); + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp); + return oneapi::dpl::__internal::__pattern_min_element( __tag, std::forward<_ExecutionPolicy>(__exec), std::ranges::begin(__r), std::ranges::begin(__r) + std::ranges::size(__r), - oneapi::dpl::__internal::__binary_op<_Comp, _Proj, _Proj>{__comp, __proj, __proj}); + oneapi::dpl::__internal::__binary_op{__relax_non_const_comp, + __proj, __proj}); } template @@ -594,7 +750,11 @@ std::ranges::borrowed_iterator_t<_R> __pattern_min_element(__serial_tag, _ExecutionPolicy&&, _R&& __r, _Comp __comp, _Proj __proj) { - return std::ranges::min_element(std::forward<_R>(__r), __comp, __proj); + return std::ranges::min_element( + std::forward<_R>(__r), + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp), + __proj); } //--------------------------------------------------------------------------------------------------------------------- @@ -605,6 +765,7 @@ template __pattern_min(_Tag __tag, _ExecutionPolicy&& __exec, _R&& __r, _Comp __comp, _Proj __proj) { + // calls oneapi::dpl::__internal::__get_relax_non_const_comp inside return *__pattern_min_element(__tag, std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), __comp, __proj); } @@ -618,10 +779,15 @@ __pattern_minmax_element(_Tag __tag, _ExecutionPolicy&& __exec, _R&& __r, _Comp { static_assert(__is_parallel_tag_v<_Tag> || typename _Tag::__is_vector{}); + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp); + return oneapi::dpl::__internal::__pattern_minmax_element( __tag, std::forward<_ExecutionPolicy>(__exec), std::ranges::begin(__r), std::ranges::begin(__r) + std::ranges::size(__r), - oneapi::dpl::__internal::__binary_op<_Comp, _Proj, _Proj>{__comp, __proj, __proj}); + oneapi::dpl::__internal::__binary_op{__relax_non_const_comp, + __proj, __proj}); } template @@ -629,7 +795,11 @@ auto __pattern_minmax_element(__serial_tag, _ExecutionPolicy&&, _R&& __r, _Comp __comp, _Proj __proj) { - return std::ranges::minmax_element(std::forward<_R>(__r), __comp, __proj); + return std::ranges::minmax_element( + std::forward<_R>(__r), + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp), + __proj); } //--------------------------------------------------------------------------------------------------------------------- @@ -640,8 +810,11 @@ template , std::ranges::range_value_t<_R>> __pattern_minmax(_Tag __tag, _ExecutionPolicy&& __exec, _R&& __r, _Comp __comp, _Proj __proj) { - auto [__it_min, __it_max] = - __pattern_minmax_element(__tag, std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), __comp, __proj); + auto [__it_min, __it_max] = __pattern_minmax_element( + __tag, std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp), + __proj); return {*__it_min, *__it_max}; } @@ -686,20 +859,25 @@ __pattern_copy_if_ranges(__parallel_tag<_IsVector> __tag, _ExecutionPolicy&& __e _Size __sz_in = std::ranges::size(__in_r); _Size __sz_out = std::ranges::size(__out_r); + auto __relax_non_const_pred = oneapi::dpl::__internal::__get_relax_non_const_pred< + oneapi::dpl::__internal::__projected_value_t<_Proj, _InRange>>(__pred); + // TODO: test if redirecting to "regular" copy_if for sufficient output performs better if (__sz_in > 0 && __sz_out > 0) { auto /*std::pair*/ __res = oneapi::dpl::__internal::__pattern_bounded_copy_if( __tag, std::forward<_ExecutionPolicy>(__exec), __first_in, __sz_in, __first_out, __sz_out, - oneapi::dpl::__internal::__unary_op<_Pred, _Proj>{__pred, __proj}); + oneapi::dpl::__internal::__unary_op{__relax_non_const_pred, + __proj}); return {__res.first, __res.second}; } else if (__sz_in > 0) { __first_in = __pattern_find_if(__tag, std::forward<_ExecutionPolicy>(__exec), std::forward<_InRange>(__in_r), - __pred, __proj); + __relax_non_const_pred, __proj); } + return {__first_in, __first_out}; } @@ -708,9 +886,13 @@ std::ranges::copy_if_result, std::ran __pattern_copy_if_ranges(__serial_tag, _ExecutionPolicy&&, _InRange&& __in_r, _OutRange&& __out_r, _Pred __pred, _Proj __proj) { + auto __relax_non_const_pred = oneapi::dpl::__internal::__get_relax_non_const_pred< + oneapi::dpl::__internal::__projected_value_t<_Proj, _InRange>>(__pred); + auto /*std::pair*/ __res = oneapi::dpl::__internal::__brick_bounded_copy_if( std::ranges::begin(__in_r), std::ranges::size(__in_r), std::ranges::begin(__out_r), std::ranges::size(__out_r), - oneapi::dpl::__internal::__unary_op<_Pred, _Proj>{__pred, __proj}, /*vector=*/std::true_type{}); + oneapi::dpl::__internal::__unary_op{__relax_non_const_pred, __proj}, + /*vector=*/std::true_type{}); return {__res.first, __res.second}; } @@ -724,9 +906,13 @@ __pattern_copy_if_ranges(__serial_tag, _ExecutionP auto __it_out = std::ranges::begin(__out_r); auto __end_in = std::ranges::end(__in_r); auto __end_out = std::ranges::end(__out_r); + + auto __relax_non_const_pred = oneapi::dpl::__internal::__get_relax_non_const_pred< + oneapi::dpl::__internal::__projected_value_t<_Proj, _InRange>>(__pred); + for (; __it_in != __end_in; ++__it_in) { - if (std::invoke(__pred, std::invoke(__proj, *__it_in))) + if (std::invoke(__relax_non_const_pred, std::invoke(__proj, *__it_in))) { if (__it_out != __end_out) { @@ -749,8 +935,7 @@ __pattern_fill(_Tag __tag, _ExecutionPolicy&& __exec, _R&& __r, const _T& __valu { static_assert(__is_parallel_tag_v<_Tag> || typename _Tag::__is_vector{}); - const auto __first = std::ranges::begin(__r); - const auto __last = __first + std::ranges::size(__r); + auto [__first, __last] = oneapi::dpl::__ranges::__bounds(__r); oneapi::dpl::__internal::__pattern_fill(__tag, std::forward<_ExecutionPolicy>(__exec), __first, __last, __value); return __last; @@ -814,7 +999,11 @@ __pattern_merge_ranges(_Tag __tag, _ExecutionPolicy&& __exec, _R1&& __r1, _R2&& auto [__it1, __it2, __it3] = __merge_path_out_lim( __tag, std::forward<_ExecutionPolicy>(__exec), __first1, __first1 + __n1, __first2, __first2 + __n2, __first3, - __first3 + std::min<_IndexCommon>(__n1 + __n2, __n3), __comp, __proj1, __proj2); + __first3 + std::min<_IndexCommon>(__n1 + __n2, __n3), + oneapi::dpl::__internal::__get_relax_non_const_comp, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __comp), + __proj1, __proj2); return {__it1, __it2, __it3}; } @@ -832,9 +1021,14 @@ __pattern_inplace_merge_ranges(_Tag __tag, _ExecutionPolicy&& __exec, _R&& __r, auto [__first, __last] = oneapi::dpl::__ranges::__bounds(__r); + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp); + oneapi::dpl::__internal::__pattern_inplace_merge( __tag, std::forward<_ExecutionPolicy>(__exec), __first, __middle, __last, - oneapi::dpl::__internal::__binary_op<_Comp, _Proj, _Proj>{__comp, __proj, __proj}); + oneapi::dpl::__internal::__binary_op{__relax_non_const_comp, + __proj, __proj}); return __last; } @@ -844,8 +1038,15 @@ std::ranges::borrowed_iterator_t<_R> __pattern_inplace_merge_ranges(__serial_tag, _ExecutionPolicy&& __exec, _R&& __r, std::ranges::iterator_t<_R> __middle, _Comp __comp, _Proj __proj) { - std::ranges::inplace_merge(__r, __middle, __comp, __proj); - return oneapi::dpl::__ranges::__end(__r); + [[maybe_unused]] auto [__first, __last] = oneapi::dpl::__ranges::__bounds(__r); + + std::ranges::inplace_merge( + __r, __middle, + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp), + __proj); + + return __last; } //--------------------------------------------------------------------------------------------------------------------- @@ -857,7 +1058,12 @@ bool __brick_includes(_R1&& __r1, _R2&& __r2, _Comp __comp, _Proj1 __proj1, _Proj2 __proj2, /*__is_vector=*/std::false_type) noexcept { - return std::ranges::includes(std::forward<_R1>(__r1), std::forward<_R2>(__r2), __comp, __proj1, __proj2); + return std::ranges::includes( + std::forward<_R1>(__r1), std::forward<_R2>(__r2), + oneapi::dpl::__internal::__get_relax_non_const_comp, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __comp), + __proj1, __proj2); } template @@ -866,7 +1072,12 @@ __brick_includes(_R1&& __r1, _R2&& __r2, _Comp __comp, _Proj1 __proj1, _Proj2 __ /*__is_vector=*/std::true_type) noexcept { _PSTL_PRAGMA_MESSAGE("Vectorized algorithm unimplemented, redirected to serial"); - return std::ranges::includes(std::forward<_R1>(__r1), std::forward<_R2>(__r2), __comp, __proj1, __proj2); + return std::ranges::includes( + std::forward<_R1>(__r1), std::forward<_R2>(__r2), + oneapi::dpl::__internal::__get_relax_non_const_comp, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __comp), + __proj1, __proj2); } template ); - return __brick_includes(std::forward<_R1>(__r1), std::forward<_R2>(__r2), __comp, __proj1, __proj2, - typename _Tag::__is_vector{}); + return __brick_includes( + std::forward<_R1>(__r1), std::forward<_R2>(__r2), + oneapi::dpl::__internal::__get_relax_non_const_comp, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __comp), + __proj1, __proj2, typename _Tag::__is_vector{}); } template __tag, _ExecutionPolicy&& __exec, _ const auto __n1 = std::ranges::size(__r1); const auto __n2 = std::ranges::size(__r2); + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __comp); + // use serial algorithm if (__n1 + __n2 <= oneapi::dpl::__internal::__set_algo_cut_off) - return std::ranges::includes(std::forward<_R1>(__r1), std::forward<_R2>(__r2), __comp, __proj1, __proj2); + return std::ranges::includes(std::forward<_R1>(__r1), std::forward<_R2>(__r2), __relax_non_const_comp, __proj1, + __proj2); auto __first1 = std::ranges::begin(__r1); auto __last1 = __first1 + __n1; @@ -911,30 +1132,30 @@ __pattern_includes(__parallel_tag<_IsVector> __tag, _ExecutionPolicy&& __exec, _ if (__first1 == __last1 || __last2 - __first2 > __last1 - __first1 || // {1}: [**********] or [**********] // {2}: [***********] [***********] - std::invoke(__comp, std::invoke(__proj2, *__first2), std::invoke(__proj1, *__first1)) || - std::invoke(__comp, std::invoke(__proj1, *(__last1 - 1)), std::invoke(__proj2, *(__last2 - 1)))) + std::invoke(__relax_non_const_comp, std::invoke(__proj2, *__first2), std::invoke(__proj1, *__first1)) || + std::invoke(__relax_non_const_comp, std::invoke(__proj1, *(__last1 - 1)), std::invoke(__proj2, *(__last2 - 1)))) return false; __first1 += oneapi::dpl::__internal::__pstl_lower_bound(__first1, _DifferenceType1{0}, __last1 - __first1, __first2, - __comp, __proj1, __proj2); + __relax_non_const_comp, __proj1, __proj2); if (__first1 == __last1) return false; if (__last2 - __first2 == 1) - return !std::invoke(__comp, std::invoke(__proj1, *__first1), std::invoke(__proj2, *__first2)) && - !std::invoke(__comp, std::invoke(__proj2, *__first2), std::invoke(__proj1, *__first1)); + return !std::invoke(__relax_non_const_comp, std::invoke(__proj1, *__first1), std::invoke(__proj2, *__first2)) && + !std::invoke(__relax_non_const_comp, std::invoke(__proj2, *__first2), std::invoke(__proj1, *__first1)); return !__internal::__parallel_or( __tag, std::forward<_ExecutionPolicy>(__exec), __first2, __last2, - [__first1, __last1, __first2, __last2, __comp, __proj1, __proj2](_RandomAccessIterator2 __i, - _RandomAccessIterator2 __j) { + [__first1, __last1, __first2, __last2, __relax_non_const_comp, __proj1, __proj2](_RandomAccessIterator2 __i, + _RandomAccessIterator2 __j) { assert(__j > __i); //1. moving boundaries to "consume" subsequence of equal elements - auto __is_equal_sorted = [&__comp, __proj2](_RandomAccessIterator2 __a, + auto __is_equal_sorted = [& , __proj2](_RandomAccessIterator2 __a, _RandomAccessIterator2 __b) -> bool { - //enough one call of __comp due to compared couple belongs to one sorted sequence - return !std::invoke(__comp, std::invoke(__proj2, *__a), std::invoke(__proj2, *__b)); + //enough one call of __relax_non_const_comp due to compared couple belongs to one sorted sequence + return !std::invoke(__relax_non_const_comp, std::invoke(__proj2, *__a), std::invoke(__proj2, *__b)); }; //1.1 left bound, case "aaa[aaaxyz...]" - searching "x" @@ -944,20 +1165,21 @@ __pattern_includes(__parallel_tag<_IsVector> __tag, _ExecutionPolicy&& __exec, _ if (__is_equal_sorted(__i, __j - 1)) return false; - __i += oneapi::dpl::__internal::__pstl_upper_bound(__i, _DifferenceType2{0}, __last2 - __i, __i, __comp, - __proj2, __proj2); + __i += oneapi::dpl::__internal::__pstl_upper_bound(__i, _DifferenceType2{0}, __last2 - __i, __i, + __relax_non_const_comp, __proj2, __proj2); } //1.2 right bound, case "[...aaa]aaaxyz" - searching "x" if (__j < __last2 && __is_equal_sorted(__j - 1, __j)) - __j += oneapi::dpl::__internal::__pstl_upper_bound(__j, _DifferenceType2{0}, __last2 - __j, __j, __comp, - __proj2, __proj2); + __j += oneapi::dpl::__internal::__pstl_upper_bound(__j, _DifferenceType2{0}, __last2 - __j, __j, + __relax_non_const_comp, __proj2, __proj2); //2. testing is __a subsequence of the second range included into the first range - auto __b = __first1 + oneapi::dpl::__internal::__pstl_lower_bound( - __first1, _DifferenceType1{0}, __last1 - __first1, __i, __comp, __proj1, __proj2); + auto __b = __first1 + oneapi::dpl::__internal::__pstl_lower_bound(__first1, _DifferenceType1{0}, + __last1 - __first1, __i, + __relax_non_const_comp, __proj1, __proj2); - return !std::ranges::includes(__b, __last1, __i, __j, __comp, __proj1, __proj2); + return !std::ranges::includes(__b, __last1, __i, __j, __relax_non_const_comp, __proj1, __proj2); }); } @@ -1075,8 +1297,12 @@ __pattern_set_union(_Tag, _ExecutionPolicy&&, _R1&& __r1, _R2&& __r2, _OutRange& { static_assert(__is_serial_tag_v<_Tag>); - return __brick_set_union(std::forward<_R1>(__r1), std::forward<_R2>(__r2), std::forward<_OutRange>(__out_r), __comp, - __proj1, __proj2, typename _Tag::__is_vector{}); + return __brick_set_union( + std::forward<_R1>(__r1), std::forward<_R2>(__r2), std::forward<_OutRange>(__out_r), + oneapi::dpl::__internal::__get_relax_non_const_comp, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __comp), + __proj1, __proj2, typename _Tag::__is_vector{}); } template __tag, _ExecutionPolicy&& __exec, auto [__first2, __last2, __n2] = oneapi::dpl::__ranges::__bounds_and_size(__r2); auto [__result1, __result2] = oneapi::dpl::__ranges::__bounds(__out_r); + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __comp); + // use serial algorithm if (__n1 + __n2 <= oneapi::dpl::__internal::__set_algo_cut_off) return __serial_set_union(std::forward<_R1>(__r1), std::forward<_R2>(__r2), std::forward<_OutRange>(__out_r), - __comp, __proj1, __proj2); + __relax_non_const_comp, __proj1, __proj2); return oneapi::dpl::__internal::__parallel_set_union_op( __tag, std::forward<_ExecutionPolicy>(__exec), __first1, __last1, __first2, __last2, __result1, __result2, - __comp, __proj1, __proj2, [](auto&&... __args) { + __relax_non_const_comp, __proj1, __proj2, [](auto&&... __args) { return oneapi::dpl::__utils::__set_union_construct<__BrickCopyConstruct<_IsVector>>( std::forward(__args)...); }); @@ -1192,8 +1423,12 @@ __pattern_set_intersection(_Tag, _ExecutionPolicy&&, _R1&& __r1, _R2&& __r2, _Ou { static_assert(__is_serial_tag_v<_Tag>); - return __brick_set_intersection(std::forward<_R1>(__r1), std::forward<_R2>(__r2), std::forward<_OutRange>(__out_r), - __comp, __proj1, __proj2, typename _Tag::__is_vector{}); + return __brick_set_intersection( + std::forward<_R1>(__r1), std::forward<_R2>(__r2), std::forward<_OutRange>(__out_r), + oneapi::dpl::__internal::__get_relax_non_const_comp, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __comp), + __proj1, __proj2, typename _Tag::__is_vector{}); } template __tag, _ExecutionPolicy&& _ if (__n1 == 0 || __n2 == 0) return {__first1, __first2, __result1}; + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __comp); + // testing whether the sequences are intersected auto __left_bound_seq_1 = __first1 + oneapi::dpl::__internal::__pstl_lower_bound(__first1, _DifferenceType1{0}, __last1 - __first1, - __first2, __comp, __proj1, __proj2); + __first2, __relax_non_const_comp, __proj1, __proj2); //{1} < {2}: seq 2 is wholly greater than seq 1, so, the intersection is empty if (__left_bound_seq_1 == __last1) return {__last1, __first2, __result1}; @@ -1235,7 +1475,7 @@ __pattern_set_intersection(__parallel_tag<_IsVector> __tag, _ExecutionPolicy&& _ // testing whether the sequences are intersected auto __left_bound_seq_2 = __first2 + oneapi::dpl::__internal::__pstl_lower_bound(__first2, _DifferenceType2{0}, __last2 - __first2, - __first1, __comp, __proj2, __proj1); + __first1, __relax_non_const_comp, __proj2, __proj1); //{2} < {1}: seq 1 is wholly greater than seq 2, so, the intersection is empty if (__left_bound_seq_2 == __last2) return {__first1, __last2, __result1}; @@ -1249,7 +1489,7 @@ __pattern_set_intersection(__parallel_tag<_IsVector> __tag, _ExecutionPolicy&& _ return __internal::__except_handler([&]() { return __internal::__parallel_set_op( __tag, std::forward<_ExecutionPolicy>(__exec), __left_bound_seq_1, __last1, __first2, __last2, - __result1, __result2, __comp, __proj1, __proj2, __size_func, [](auto&&... __args) { + __result1, __result2, __relax_non_const_comp, __proj1, __proj2, __size_func, [](auto&&... __args) { return oneapi::dpl::__utils::__set_intersection_construct( std::forward(__args)...); }); @@ -1263,7 +1503,7 @@ __pattern_set_intersection(__parallel_tag<_IsVector> __tag, _ExecutionPolicy&& _ return __internal::__except_handler([&]() { return __internal::__parallel_set_op( __tag, std::forward<_ExecutionPolicy>(__exec), __first1, __last1, __left_bound_seq_2, __last2, - __result1, __result2, __comp, __proj1, __proj2, __size_func, [](auto&&... __args) { + __result1, __result2, __relax_non_const_comp, __proj1, __proj2, __size_func, [](auto&&... __args) { return oneapi::dpl::__utils::__set_intersection_construct( std::forward(__args)...); }); @@ -1272,8 +1512,8 @@ __pattern_set_intersection(__parallel_tag<_IsVector> __tag, _ExecutionPolicy&& _ // [left_bound_seq_1; last1) and [left_bound_seq_2; last2) - use serial algorithm return __serial_set_intersection(std::ranges::subrange(__left_bound_seq_1, __last1), - std::ranges::subrange(__left_bound_seq_2, __last2), __out_r, __comp, __proj1, - __proj2); + std::ranges::subrange(__left_bound_seq_2, __last2), __out_r, + __relax_non_const_comp, __proj1, __proj2); } //--------------------------------------------------------------------------------------------------------------------- @@ -1363,8 +1603,12 @@ __pattern_set_difference(_Tag, _ExecutionPolicy&&, _R1&& __r1, _R2&& __r2, _OutR { static_assert(__is_serial_tag_v<_Tag>); - return __brick_set_difference(std::forward<_R1>(__r1), std::forward<_R2>(__r2), std::forward<_OutRange>(__out_r), - __comp, __proj1, __proj2, typename _Tag::__is_vector{}); + return __brick_set_difference( + std::forward<_R1>(__r1), std::forward<_R2>(__r2), std::forward<_OutRange>(__out_r), + oneapi::dpl::__internal::__get_relax_non_const_comp, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __comp), + __proj1, __proj2, typename _Tag::__is_vector{}); } template __tag, _ExecutionPolicy&& __e if (__n1 == 0) return oneapi::dpl::__utils::__create_set_difference_result(__first1, __first2, __result1); + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __comp); + // {1} \ {}: parallel copying just first sequence if (__n2 == 0) { @@ -1401,7 +1650,7 @@ __pattern_set_difference(__parallel_tag<_IsVector> __tag, _ExecutionPolicy&& __e // testing whether the sequences are intersected auto __left_bound_seq_1 = __first1 + oneapi::dpl::__internal::__pstl_lower_bound(__first1, _DifferenceType1{0}, __last1 - __first1, - __first2, __comp, __proj1, __proj2); + __first2, __relax_non_const_comp, __proj1, __proj2); //{1} < {2}: seq 2 is wholly greater than seq 1, so, parallel copying just first sequence if (__left_bound_seq_1 == __last1) { @@ -1415,7 +1664,7 @@ __pattern_set_difference(__parallel_tag<_IsVector> __tag, _ExecutionPolicy&& __e // testing whether the sequences are intersected auto __left_bound_seq_2 = __first2 + oneapi::dpl::__internal::__pstl_lower_bound(__first2, _DifferenceType2{0}, __last2 - __first2, - __first1, __comp, __proj2, __proj1); + __first1, __relax_non_const_comp, __proj2, __proj1); //{2} < {1}: seq 1 is wholly greater than seq 2, so, parallel copying just first sequence if (__left_bound_seq_2 == __last2) { @@ -1433,7 +1682,7 @@ __pattern_set_difference(__parallel_tag<_IsVector> __tag, _ExecutionPolicy&& __e //we know proper offset due to [first2; left_bound_seq_2) < [first1; last1) auto [__it1, __it2, __it_out] = __internal::__parallel_set_op( __tag, std::forward<_ExecutionPolicy>(__exec), __first1, __last1, __left_bound_seq_2, __last2, __result1, - __result2, __comp, __proj1, __proj2, __size_func, [](auto&&... __args) { + __result2, __relax_non_const_comp, __proj1, __proj2, __size_func, [](auto&&... __args) { return oneapi::dpl::__utils::__set_difference_construct<__BrickCopyConstruct<_IsVector>>( std::forward(__args)...); }); @@ -1443,7 +1692,7 @@ __pattern_set_difference(__parallel_tag<_IsVector> __tag, _ExecutionPolicy&& __e // use serial algorithm return __serial_set_difference(std::forward<_R1>(__r1), std::ranges::subrange(__left_bound_seq_2, __last2), - std::forward<_OutRange>(__out_r), __comp, __proj1, __proj2); + std::forward<_OutRange>(__out_r), __relax_non_const_comp, __proj1, __proj2); } //--------------------------------------------------------------------------------------------------------------------- @@ -1549,9 +1798,12 @@ __pattern_set_symmetric_difference(_Tag, _ExecutionPolicy&&, _R1&& __r1, _R2&& _ { static_assert(__is_serial_tag_v<_Tag>); - return __brick_set_symmetric_difference(std::forward<_R1>(__r1), std::forward<_R2>(__r2), - std::forward<_OutRange>(__out_r), __comp, __proj1, __proj2, - typename _Tag::__is_vector{}); + return __brick_set_symmetric_difference( + std::forward<_R1>(__r1), std::forward<_R2>(__r2), std::forward<_OutRange>(__out_r), + oneapi::dpl::__internal::__get_relax_non_const_comp, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __comp), + __proj1, __proj2, typename _Tag::__is_vector{}); } template __tag, _ExecutionPo auto [__first2, __last2, __n2] = oneapi::dpl::__ranges::__bounds_and_size(__r2); auto [__result1, __result2] = oneapi::dpl::__ranges::__bounds(__out_r); + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __comp); + // use serial algorithm if (__n1 + __n2 <= oneapi::dpl::__internal::__set_algo_cut_off) return __serial_set_symmetric_difference(std::forward<_R1>(__r1), std::forward<_R2>(__r2), - std::forward<_OutRange>(__out_r), __comp, __proj1, __proj2); + std::forward<_OutRange>(__out_r), __relax_non_const_comp, __proj1, + __proj2); return oneapi::dpl::__internal::__parallel_set_union_op( __tag, std::forward<_ExecutionPolicy>(__exec), __first1, __last1, __first2, __last2, __result1, __result2, - __comp, __proj1, __proj2, [](auto&&... __args) { + __relax_non_const_comp, __proj1, __proj2, [](auto&&... __args) { return oneapi::dpl::__utils::__set_symmetric_difference_construct<__BrickCopyConstruct<_IsVector>>( std::forward(__args)...); }); @@ -1596,9 +1854,15 @@ __pattern_mismatch(_Tag __tag, _ExecutionPolicy&& __exec, _R1&& __r1, _R2&& __r2 auto __first1 = std::ranges::begin(__r1); auto __first2 = std::ranges::begin(__r2); + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __pred); + const auto& [first, second] = oneapi::dpl::__internal::__pattern_mismatch( __tag, std::forward<_ExecutionPolicy>(__exec), __first1, __first1 + std::ranges::size(__r1), __first2, - __first2 + std::ranges::size(__r2), oneapi::dpl::__internal::__binary_op{__pred, __proj1, __proj2}); + __first2 + std::ranges::size(__r2), + oneapi::dpl::__internal::__binary_op{__relax_non_const_pred, __proj1, __proj2}); return {first, second}; } @@ -1608,7 +1872,12 @@ __mismatch_return_t<_R1, _R2> __pattern_mismatch(__serial_tag, _ExecutionPolicy&&, _R1&& __r1, _R2&& __r2, _Pred __pred, _Proj1 __proj1, _Proj2 __proj2) { - return std::ranges::mismatch(std::forward<_R1>(__r1), std::forward<_R2>(__r2), __pred, __proj1, __proj2); + return std::ranges::mismatch( + std::forward<_R1>(__r1), std::forward<_R2>(__r2), + oneapi::dpl::__internal::__get_relax_non_const_pred, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __pred), + __proj1, __proj2); } //--------------------------------------------------------------------------------------------------------------------- @@ -1622,10 +1891,15 @@ __pattern_nth_element(_Tag __tag, _ExecutionPolicy&& __exec, _R&& __r, std::rang { static_assert(__is_parallel_tag_v<_Tag> || typename _Tag::__is_vector{}); + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp); + auto [__beg, __end] = oneapi::dpl::__ranges::__bounds(__r); oneapi::dpl::__internal::__pattern_nth_element( __tag, std::forward<_ExecutionPolicy>(__exec), __beg, __nth, __end, - oneapi::dpl::__internal::__binary_op<_Comp, _Proj, _Proj>{__comp, __proj, __proj}); + oneapi::dpl::__internal::__binary_op{__relax_non_const_comp, + __proj, __proj}); return __end; } @@ -1635,7 +1909,11 @@ std::ranges::borrowed_iterator_t<_R> __pattern_nth_element(__serial_tag, _ExecutionPolicy&&, _R&& __r, std::ranges::iterator_t<_R> __nth, _Comp __comp, _Proj __proj) { - return std::ranges::nth_element(std::forward<_R>(__r), __nth, __comp, __proj); + return std::ranges::nth_element( + std::forward<_R>(__r), __nth, + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp), + __proj); } //--------------------------------------------------------------------------------------------------------------------- @@ -1650,9 +1928,13 @@ __pattern_is_partitioned(_Tag __tag, _ExecutionPolicy&& __exec, _R&& __r, _Pred auto [__first, __last] = oneapi::dpl::__ranges::__bounds(__r); + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred>( + __pred); + return oneapi::dpl::__internal::__pattern_is_partitioned( __tag, std::forward<_ExecutionPolicy>(__exec), __first, __last, - oneapi::dpl::__internal::__unary_op<_Pred, _Proj>{__pred, __proj}); + oneapi::dpl::__internal::__unary_op{__relax_non_const_pred, __proj}); } template @@ -1660,7 +1942,11 @@ bool __pattern_is_partitioned(__serial_tag, _ExecutionPolicy&&, _R&& __r, _Pred __pred, _Proj __proj) { - return std::ranges::is_partitioned(std::forward<_R>(__r), __pred, __proj); + return std::ranges::is_partitioned( + std::forward<_R>(__r), + oneapi::dpl::__internal::__get_relax_non_const_pred>( + __pred), + __proj); } //--------------------------------------------------------------------------------------------------------------------- @@ -1675,9 +1961,13 @@ __pattern_remove_if(_Tag __tag, _ExecutionPolicy&& __exec, _R&& __r, _Pred __pre auto __end = std::ranges::begin(__r) + std::ranges::size(__r); + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred>( + __pred); + auto __it = oneapi::dpl::__internal::__pattern_remove_if( __tag, std::forward<_ExecutionPolicy>(__exec), std::ranges::begin(__r), __end, - oneapi::dpl::__internal::__unary_op<_Pred, _Proj>{__pred, __proj}); + oneapi::dpl::__internal::__unary_op{__relax_non_const_pred, __proj}); return {__it, __end}; } @@ -1687,7 +1977,11 @@ std::ranges::borrowed_subrange_t<_R> __pattern_remove_if(__serial_tag, _ExecutionPolicy&&, _R&& __r, _Pred __pred, _Proj __proj) { - return std::ranges::remove_if(std::forward<_R>(__r), __pred, __proj); + return std::ranges::remove_if( + std::forward<_R>(__r), + oneapi::dpl::__internal::__get_relax_non_const_pred>( + __pred), + __proj); } //--------------------------------------------------------------------------------------------------------------------- @@ -1700,8 +1994,7 @@ __pattern_reverse(_Tag __tag, _ExecutionPolicy&& __exec, _R&& __r) { static_assert(__is_parallel_tag_v<_Tag> || typename _Tag::__is_vector{}); - auto __first = std::ranges::begin(__r); - auto __last = __first + std::ranges::size(__r); + auto [__first, __last] = oneapi::dpl::__ranges::__bounds(__r); oneapi::dpl::__internal::__pattern_reverse(__tag, std::forward<_ExecutionPolicy>(__exec), __first, __last); } @@ -1737,6 +2030,27 @@ __pattern_reverse_copy(__serial_tag, _ExecutionPol std::ranges::reverse_copy(std::forward<_InRange>(__in_r), std::ranges::begin(__out_r)); } +//--------------------------------------------------------------------------------------------------------------------- +// __pattern_rotate_copy +//--------------------------------------------------------------------------------------------------------------------- + +// __in_r is the whole input range while __out_r is already trimmed to the number of elements to write on the caller +// side. The iterator-based pattern honors that number, so there is no separate serial overload calling +// std::ranges::rotate_copy, which would always write the whole input. +template +void +__pattern_rotate_copy(_Tag __tag, _ExecutionPolicy&& __exec, _InRange&& __in_r, _OutRange&& __out_r, + std::size_t __shift) +{ + auto [__first_in, __last_in] = oneapi::dpl::__ranges::__bounds(__in_r); + + [[maybe_unused]] auto __stop_out = oneapi::dpl::__internal::__pattern_rotate_copy( + __tag, std::forward<_ExecutionPolicy>(__exec), __first_in, __first_in + __shift, __last_in, + std::ranges::begin(__out_r), std::ranges::size(__out_r)); + + assert(__stop_out == std::ranges::end(__out_r)); +} + //--------------------------------------------------------------------------------------------------------------------- // __pattern_replace_copy_if //--------------------------------------------------------------------------------------------------------------------- @@ -1750,8 +2064,12 @@ __pattern_replace_copy_if(_Tag __tag, _ExecutionPolicy&& __exec, _InRange&& __r, static_assert(__is_parallel_tag_v<_Tag> || typename _Tag::__is_vector{}); assert(std::ranges::size(__r) <= std::ranges::size(__out_r)); - oneapi::dpl::__internal::__replace_copy_functor<_T, oneapi::dpl::__internal::__unary_op<_Pred, _Proj>> - __f{__new_value, {__pred, __proj}}; + auto __relax_non_const_pred = oneapi::dpl::__internal::__get_relax_non_const_pred< + oneapi::dpl::__internal::__projected_value_t<_Proj, _InRange>>(__pred); + + oneapi::dpl::__internal::__replace_copy_functor< + decltype(__new_value), oneapi::dpl::__internal::__unary_op> + __f{__new_value, {__relax_non_const_pred, __proj}}; oneapi::dpl::__internal::__pattern_walk2(__tag, std::forward<_ExecutionPolicy>(__exec), std::ranges::begin(__r), std::ranges::begin(__r) + std::ranges::size(__r), @@ -1763,7 +2081,11 @@ void __pattern_replace_copy_if(__serial_tag, _ExecutionPolicy&&, _InRange&& __r, _OutRange&& __out_r, _Pred __pred, const _T& __new_value, _Proj __proj) { - std::ranges::replace_copy_if(std::forward<_InRange>(__r), std::ranges::begin(__out_r), __pred, __new_value, __proj); + auto __relax_non_const_pred = oneapi::dpl::__internal::__get_relax_non_const_pred< + oneapi::dpl::__internal::__projected_value_t<_Proj, _InRange>>(__pred); + + std::ranges::replace_copy_if(std::forward<_InRange>(__r), std::ranges::begin(__out_r), __relax_non_const_pred, + __new_value, __proj); } //--------------------------------------------------------------------------------------------------------------------- @@ -1822,9 +2144,14 @@ __pattern_unique(_Tag __tag, _ExecutionPolicy&& __exec, _R&& __r, _Comp __comp, auto __beg = std::ranges::begin(__r); auto __end = __beg + std::ranges::size(__r); + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp); + auto __it = oneapi::dpl::__internal::__pattern_unique( __tag, std::forward<_ExecutionPolicy>(__exec), __beg, __end, - oneapi::dpl::__internal::__binary_op<_Comp, _Proj, _Proj>{__comp, __proj, __proj}); + oneapi::dpl::__internal::__binary_op{__relax_non_const_comp, + __proj, __proj}); return {__it, __end}; } @@ -1833,7 +2160,11 @@ template __pattern_unique(__serial_tag, _ExecutionPolicy&&, _R&& __r, _Comp __comp, _Proj __proj) { - return std::ranges::unique(std::forward<_R>(__r), __comp, __proj); + return std::ranges::unique( + std::forward<_R>(__r), + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp), + __proj); } //--------------------------------------------------------------------------------------------------------------------- @@ -1853,9 +2184,15 @@ __pattern_unique_copy(__parallel_tag<_IsVector> __tag, _ExecutionPolicy&& __exec _DiffType __sz_in = std::ranges::ssize(__r); _DiffType __sz_out = std::ranges::ssize(__out_r); + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp); + auto /*std::pair*/ __res = oneapi::dpl::__internal::__pattern_bounded_unique_copy( __tag, std::forward<_ExecutionPolicy>(__exec), std::ranges::begin(__r), __sz_in, std::ranges::begin(__out_r), - __sz_out, oneapi::dpl::__internal::__binary_op<_Comp, _Proj, _Proj>{__comp, __proj, __proj}); + __sz_out, + oneapi::dpl::__internal::__binary_op{__relax_non_const_comp, + __proj, __proj}); return {__res.first, __res.second}; } @@ -1865,9 +2202,15 @@ __unique_copy_return_t<_R, _OutR> __pattern_unique_copy(__serial_tag, _ExecutionPolicy&&, _R&& __r, _OutR&& __out_r, _Comp __comp, _Proj __proj) { + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp); + auto /*std::pair*/ __res = oneapi::dpl::__internal::__brick_bounded_unique_copy( std::ranges::begin(__r), std::ranges::ssize(__r), std::ranges::begin(__out_r), std::ranges::ssize(__out_r), - oneapi::dpl::__internal::__binary_op<_Comp, _Proj, _Proj>{__comp, __proj, __proj}, /*vector=*/std::true_type{}); + oneapi::dpl::__internal::__binary_op{__relax_non_const_comp, + __proj, __proj}, + /*vector=*/std::true_type{}); return {__res.first, __res.second}; } @@ -1881,7 +2224,12 @@ __pattern_unique_copy(__serial_tag, _ExecutionPoli auto __it_out = std::ranges::begin(__out_r); auto __end_in = std::ranges::end(__r); auto __end_out = std::ranges::end(__out_r); - auto __not_comp = std::not_fn(__comp); + + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp); + auto __not_comp = std::not_fn(__relax_non_const_comp); + for (; __it_out != __end_out && __it_in != __end_in; ++__it_out) { *__it_out = *__it_in; @@ -1906,9 +2254,13 @@ __pattern_partition(_Tag __tag, _ExecutionPolicy&& __exec, _R&& __r, _Pred __pre auto [__first, __last] = oneapi::dpl::__ranges::__bounds(__r); - auto __middle = - oneapi::dpl::__internal::__pattern_partition(__tag, std::forward<_ExecutionPolicy>(__exec), __first, __last, - oneapi::dpl::__internal::__unary_op<_Pred, _Proj>{__pred, __proj}); + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred>( + __pred); + + auto __middle = oneapi::dpl::__internal::__pattern_partition( + __tag, std::forward<_ExecutionPolicy>(__exec), __first, __last, + oneapi::dpl::__internal::__unary_op{__relax_non_const_pred, __proj}); return {__middle, __last}; } @@ -1918,7 +2270,11 @@ std::ranges::borrowed_subrange_t<_R> __pattern_partition(__serial_tag, _ExecutionPolicy&&, _R&& __r, _Pred __pred, _Proj __proj) { - return std::ranges::partition(std::forward<_R>(__r), __pred, __proj); + return std::ranges::partition( + std::forward<_R>(__r), + oneapi::dpl::__internal::__get_relax_non_const_pred>( + __pred), + __proj); } //--------------------------------------------------------------------------------------------------------------------- @@ -1933,9 +2289,13 @@ __pattern_stable_partition(_Tag __tag, _ExecutionPolicy&& __exec, _R&& __r, _Pre auto [__first, __last] = oneapi::dpl::__ranges::__bounds(__r); + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred>( + __pred); + auto __middle = oneapi::dpl::__internal::__pattern_stable_partition( __tag, std::forward<_ExecutionPolicy>(__exec), __first, __last, - oneapi::dpl::__internal::__unary_op<_Pred, _Proj>{__pred, __proj}); + oneapi::dpl::__internal::__unary_op{__relax_non_const_pred, __proj}); return {__middle, __last}; } @@ -1945,7 +2305,11 @@ std::ranges::borrowed_subrange_t<_R> __pattern_stable_partition(__serial_tag, _ExecutionPolicy&&, _R&& __r, _Pred __pred, _Proj __proj) { - return std::ranges::stable_partition(std::forward<_R>(__r), __pred, __proj); + return std::ranges::stable_partition( + std::forward<_R>(__r), + oneapi::dpl::__internal::__get_relax_non_const_pred>( + __pred), + __proj); } //--------------------------------------------------------------------------------------------------------------------- @@ -1988,10 +2352,13 @@ std::ranges::partition_copy_result, __pattern_partition_copy_ranges(__serial_tag<_IsVector>, _ExecutionPolicy&&, _InRange&& __in_r, _OutRange1&& __out_true_r, _OutRange2&& __out_false_r, _Pred __pred, _Proj __proj) { + auto __relax_non_const_pred = oneapi::dpl::__internal::__get_relax_non_const_pred< + oneapi::dpl::__internal::__projected_value_t<_Proj, _InRange>>(__pred); + auto [__it_in, __it_out1, __it_out2] = __brick_bounded_partition_copy( std::ranges::begin(__in_r), std::ranges::end(__in_r), std::ranges::begin(__out_true_r), std::ranges::end(__out_true_r), std::ranges::begin(__out_false_r), std::ranges::end(__out_false_r), - [=](auto __it, std::size_t /*__i*/) -> bool { return std::invoke(__pred, std::invoke(__proj, *__it)); }, + [=](auto __it, std::size_t /*__i*/) -> bool { return std::invoke(__relax_non_const_pred, std::invoke(__proj, *__it)); }, _IsVector{}); return {__it_in, __it_out1, __it_out2}; } @@ -2005,7 +2372,12 @@ __pattern_partition_copy_ranges(__parallel_tag<_IsVector> __tag, _ExecutionPolic _OutRange1&& __out_true_r, _OutRange2&& __out_false_r, _Pred __pred, _Proj __proj) { using __backend_tag = typename __parallel_tag<_IsVector>::__backend_tag; - __internal::__pred_at_index __idx_pred{__internal::__unary_op<_Pred, _Proj>{__pred, __proj}}; + + auto __relax_non_const_pred = oneapi::dpl::__internal::__get_relax_non_const_pred< + oneapi::dpl::__internal::__projected_value_t<_Proj, _InRange>>(__pred); + + __internal::__pred_at_index __idx_pred{ + __internal::__unary_op{__relax_non_const_pred, __proj}}; const std::intptr_t __n = std::ranges::size(__in_r); using _OutputPos = std::pair; diff --git a/include/oneapi/dpl/pstl/glue_algorithm_ranges_impl.h b/include/oneapi/dpl/pstl/glue_algorithm_ranges_impl.h index 10350ed36e9..e9314dc43fc 100644 --- a/include/oneapi/dpl/pstl/glue_algorithm_ranges_impl.h +++ b/include/oneapi/dpl/pstl/glue_algorithm_ranges_impl.h @@ -147,8 +147,9 @@ struct __internal::__find_if_fn operator()(_ExecutionPolicy&& __exec, _R&& __r, _Pred __pred, _Proj __proj = {}) const { const auto __dispatch_tag = oneapi::dpl::__ranges::__select_backend(__exec); - return oneapi::dpl::__internal::__ranges::__pattern_find_if(__dispatch_tag, - std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), __pred, __proj); + return oneapi::dpl::__internal::__ranges::__pattern_find_if( + __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), + oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, _Pred>(__pred), __proj); } }; //__find_if_fn inline constexpr __internal::__find_if_fn find_if; @@ -202,7 +203,9 @@ struct __internal::__find_last_if_fn { std::ranges::reverse_view __reverse_r{__r}; - auto __res = oneapi::dpl::ranges::find_if(std::forward<_ExecutionPolicy>(__exec), __reverse_r, __pred, __proj); + auto __res = oneapi::dpl::ranges::find_if( + std::forward<_ExecutionPolicy>(__exec), __reverse_r, + oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, _Pred>(__pred), __proj); auto __last = std::ranges::begin(__r) + std::ranges::size(__r); return {(__res == __reverse_r.end()) ? __last : __res.base() - 1, __last}; @@ -263,7 +266,7 @@ struct __internal::__find_first_of_fn const auto __dispatch_tag = oneapi::dpl::__ranges::__select_backend(__exec); return oneapi::dpl::__internal::__ranges::__pattern_find_first_of( __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), std::forward<_R1>(__r1), std::forward<_R2>(__r2), - __pred, __proj1, __proj2); + oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, _Pred>(__pred), __proj1, __proj2); } }; //__find_first_of_fn inline constexpr __internal::__find_first_of_fn find_first_of; @@ -285,7 +288,7 @@ struct __internal::__find_end_fn const auto __dispatch_tag = oneapi::dpl::__ranges::__select_backend(__exec); return oneapi::dpl::__internal::__ranges::__pattern_find_end( __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), std::forward<_R1>(__r1), std::forward<_R2>(__r2), - __pred, __proj1, __proj2); + oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, _Pred>(__pred), __proj1, __proj2); } }; //__find_end_fn inline constexpr __internal::__find_end_fn find_end; @@ -302,8 +305,9 @@ struct __internal::__any_of_fn operator()(_ExecutionPolicy&& __exec, _R&& __r, _Pred __pred, _Proj __proj = {}) const { const auto __dispatch_tag = oneapi::dpl::__ranges::__select_backend(__exec); - return oneapi::dpl::__internal::__ranges::__pattern_any_of(__dispatch_tag, - std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), __pred, __proj); + return oneapi::dpl::__internal::__ranges::__pattern_any_of( + __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), + oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, _Pred>(__pred), __proj); } }; //__any_of_fn inline constexpr __internal::__any_of_fn any_of; @@ -338,7 +342,7 @@ struct __internal::__none_of_fn operator()(_ExecutionPolicy&& __exec, _R&& __r, _Pred __pred, _Proj __proj = {}) const { return !oneapi::dpl::ranges::any_of(std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), - __pred, __proj); + oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, _Pred>(__pred), __proj); } }; //__none_of_fn inline constexpr __internal::__none_of_fn none_of; @@ -357,8 +361,9 @@ struct __internal::__adjacent_find_fn operator()(_ExecutionPolicy&& __exec, _R&& __r, _Pred __pred = {}, _Proj __proj = {}) const { const auto __dispatch_tag = oneapi::dpl::__ranges::__select_backend(__exec); - return oneapi::dpl::__internal::__ranges::__pattern_adjacent_find_ranges(__dispatch_tag, - std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), __pred, __proj); + return oneapi::dpl::__internal::__ranges::__pattern_adjacent_find_ranges( + __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), + oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, _Pred>(__pred), __proj); } }; //__adjacent_find_fn inline constexpr __internal::__adjacent_find_fn adjacent_find; @@ -378,9 +383,9 @@ struct __internal::__search_fn _Proj2 __proj2 = {}) const { const auto __dispatch_tag = oneapi::dpl::__ranges::__select_backend(__exec); - return oneapi::dpl::__internal::__ranges::__pattern_search(__dispatch_tag, - std::forward<_ExecutionPolicy>(__exec), std::forward<_R1>(__r1), std::forward<_R2>(__r2), __pred, __proj1, - __proj2); + return oneapi::dpl::__internal::__ranges::__pattern_search( + __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), std::forward<_R1>(__r1), std::forward<_R2>(__r2), + oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, _Pred>(__pred), __proj1, __proj2); } }; //__search_fn inline constexpr __internal::__search_fn search; @@ -398,8 +403,9 @@ struct __internal::__search_n_fn _Pred __pred = {}, _Proj __proj = {}) const { const auto __dispatch_tag = oneapi::dpl::__ranges::__select_backend(__exec); - return oneapi::dpl::__internal::__ranges::__pattern_search_n(__dispatch_tag, - std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), __count, __value, __pred, __proj); + return oneapi::dpl::__internal::__ranges::__pattern_search_n( + __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), __count, __value, + oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, _Pred>(__pred), __proj); } }; //__search_n_fn inline constexpr __internal::__search_n_fn search_n; @@ -437,8 +443,11 @@ struct __internal::__contains_subrange_fn _Proj2 __proj2 = {}) const { // To ensure no dangling subrange is returned, __r1 may not be forwarded - return std::ranges::empty(__r2) || !(oneapi::dpl::ranges::search(std::forward<_ExecutionPolicy>(__exec), __r1, __r2, - __pred, __proj1, __proj2).empty()); + return std::ranges::empty(__r2) || + !(oneapi::dpl::ranges::search(std::forward<_ExecutionPolicy>(__exec), __r1, __r2, + oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, _Pred>(__pred), + __proj1, __proj2) + .empty()); } }; inline constexpr __internal::__contains_subrange_fn contains_subrange; @@ -455,8 +464,9 @@ struct __internal::__count_if_fn operator()(_ExecutionPolicy&& __exec, _R&& __r, _Pred __pred, _Proj __proj = {}) const { const auto __dispatch_tag = oneapi::dpl::__ranges::__select_backend(__exec); - return oneapi::dpl::__internal::__ranges::__pattern_count_if(__dispatch_tag, - std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), __pred, __proj); + return oneapi::dpl::__internal::__ranges::__pattern_count_if( + __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), + oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, _Pred>(__pred), __proj); } }; //__count_if_fn inline constexpr __internal::__count_if_fn count_if; @@ -497,8 +507,9 @@ struct __internal::__equal_fn else { const auto __dispatch_tag = oneapi::dpl::__ranges::__select_backend(__exec); - return oneapi::dpl::__internal::__ranges::__pattern_equal(__dispatch_tag, - std::forward<_ExecutionPolicy>(__exec), std::forward<_R1>(__r1), std::forward<_R2>(__r2), __pred, + return oneapi::dpl::__internal::__ranges::__pattern_equal( + __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), std::forward<_R1>(__r1), + std::forward<_R2>(__r2), oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, _Pred>(__pred), __proj1, __proj2); } } @@ -560,8 +571,11 @@ struct __internal::__is_sorted_until_fn { auto __last = std::ranges::begin(__r) + std::ranges::size(__r); + auto __relax_non_const_comp = oneapi::dpl::__internal::__get_relax_non_const_comp< + oneapi::dpl::__internal::__projected_value_t<_Proj, _R>>(__comp); + auto __it = oneapi::dpl::ranges::adjacent_find(std::forward<_ExecutionPolicy>(__exec), __r, - oneapi::dpl::__internal::__reorder_pred<_Comp>(__comp), __proj); + oneapi::dpl::__internal::__reorder_pred(__relax_non_const_comp), __proj); return __it == __last ? __last : ++__it; } }; //__is_sorted_until_fn @@ -729,8 +743,11 @@ struct __internal::__max_element_fn std::ranges::borrowed_iterator_t<_R> operator()(_ExecutionPolicy&& __exec, _R&& __r, _Comp __comp = {}, _Proj __proj = {}) const { + auto __relax_non_const_comp = oneapi::dpl::__internal::__get_relax_non_const_comp< + oneapi::dpl::__internal::__projected_value_t<_Proj, _R>>(__comp); + return oneapi::dpl::ranges::min_element(std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), - oneapi::dpl::__internal::__reorder_pred(__comp), __proj); + oneapi::dpl::__internal::__reorder_pred(__relax_non_const_comp), __proj); } }; //__max_element_fn inline constexpr __internal::__max_element_fn max_element; @@ -785,8 +802,11 @@ struct __internal::__max_fn { assert(std::ranges::size(__r) > 0); + auto __relax_non_const_comp = oneapi::dpl::__internal::__get_relax_non_const_comp< + oneapi::dpl::__internal::__projected_value_t<_Proj, _R>>(__comp); + return oneapi::dpl::ranges::min(std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), - oneapi::dpl::__internal::__reorder_pred(__comp), __proj); + oneapi::dpl::__internal::__reorder_pred(__relax_non_const_comp), __proj); } }; //__max_fn inline constexpr __internal::__max_fn max; @@ -852,8 +872,10 @@ struct __internal::__copy_if_fn { const auto __dispatch_tag = oneapi::dpl::__ranges::__select_backend(__exec); // No minimum common size is calculated here, because the size of the output range is unknown - return oneapi::dpl::__internal::__ranges::__pattern_copy_if_ranges(__dispatch_tag, std::forward<_ExecutionPolicy>(__exec), - std::forward<_R>(__r), std::forward<_OutR>(__result), __pred, __proj); + return oneapi::dpl::__internal::__ranges::__pattern_copy_if_ranges( + __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), + std::forward<_OutR>(__result), oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, _Pred>(__pred), + __proj); } }; //__copy_if_fn inline constexpr __internal::__copy_if_fn copy_if; @@ -1123,12 +1145,16 @@ struct __internal::__replace_if_fn std::ranges::borrowed_iterator_t<_R> operator()(_ExecutionPolicy&& __exec, _R&& __r, _Pred __pred, const _T& __new_value, _Proj __proj = {}) const { + auto __relax_non_const_pred = oneapi::dpl::__internal::__get_relax_non_const_pred< + oneapi::dpl::__internal::__projected_value_t<_Proj, _R>>(__pred); + return oneapi::dpl::ranges::for_each( std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), oneapi::dpl::__internal::__replace_functor< oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, const _T>, - oneapi::dpl::__internal::__unary_op<_Pred, _Proj>>( - __new_value, oneapi::dpl::__internal::__unary_op<_Pred, _Proj>{__pred, __proj})); + oneapi::dpl::__internal::__unary_op>( + __new_value, oneapi::dpl::__internal::__unary_op{ + __relax_non_const_pred, __proj})); } }; //__replace_if_fn inline constexpr __internal::__replace_if_fn replace_if; @@ -1178,8 +1204,9 @@ struct __internal::__replace_copy_if_fn oneapi::dpl::__internal::__ranges::__pattern_replace_copy_if( __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), std::ranges::take_view(__r, __size), - std::ranges::take_view(__result, __size), __pred, - oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, const _T>{__new_value}, __proj); + std::ranges::take_view(__result, __size), + oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, _Pred>(__pred), + oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, const _T>(__new_value), __proj); return {std::ranges::begin(__r) + __size, std::ranges::begin(__result) + __size}; } @@ -1324,10 +1351,11 @@ struct __internal::__rotate_copy_fn auto __first_out = std::ranges::begin(__result); const std::size_t __min_size = std::min(__in_size, std::ranges::size(__result)); - auto __stop_out = oneapi::dpl::__internal::__pattern_rotate_copy( - __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), __first_in, __middle, __last_in, __first_out, - __min_size); - assert(__stop_out == __first_out + __min_size); + auto __stop_out = __first_out + __min_size; + + oneapi::dpl::__internal::__ranges::__pattern_rotate_copy( + __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), std::ranges::subrange(__first_in, __last_in), + std::ranges::subrange(__first_out, __stop_out), std::size_t(__middle - __first_in)); if (__min_size < std::size_t(__last_in - __middle)) return {__middle + __min_size, __first_in, __stop_out}; @@ -1410,7 +1438,8 @@ struct __internal::__mismatch_fn { const auto& [__it_1, __it_2] = oneapi::dpl::__internal::__ranges::__pattern_mismatch( __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), std::forward<_R1>(__r1), - std::forward<_R2>(__r2), __pred, __proj1, __proj2); + std::forward<_R2>(__r2), oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, _Pred>(__pred), + __proj1, __proj2); return {__it_1, __it_2}; } @@ -1419,8 +1448,8 @@ struct __internal::__mismatch_fn auto __sized_range = std::ranges::subrange(std::ranges::begin(__r1), std::ranges::begin(__r1) + std::ranges::size(__r2)); const auto& [__it_1, __it_2] = oneapi::dpl::__internal::__ranges::__pattern_mismatch( - __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), __sized_range, std::forward<_R2>(__r2), __pred, - __proj1, __proj2); + __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), __sized_range, std::forward<_R2>(__r2), + oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, _Pred>(__pred), __proj1, __proj2); return {std::ranges::begin(__r1) + (__it_1 - __sized_range.begin()), __it_2}; } @@ -1431,8 +1460,8 @@ struct __internal::__mismatch_fn auto __sized_range = std::ranges::subrange(std::ranges::begin(__r2), std::ranges::begin(__r2) + std::ranges::size(__r1)); const auto& [__it_1, __it_2] = oneapi::dpl::__internal::__ranges::__pattern_mismatch( - __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), std::forward<_R1>(__r1), __sized_range, __pred, - __proj1, __proj2); + __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), std::forward<_R1>(__r1), __sized_range, + oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, _Pred>(__pred), __proj1, __proj2); return {__it_1, std::ranges::begin(__r2) + (__it_2 - __sized_range.begin())}; } @@ -1455,8 +1484,11 @@ struct __internal::__starts_with_fn _Proj2 __proj2 = {}) const { // To ensure no dangling iterator is returned, __r2 may not be forwarded - return std::ranges::end(__r2) == oneapi::dpl::ranges::mismatch(std::forward<_ExecutionPolicy>(__exec), __r1, - __r2, __pred, __proj1, __proj2).in2; + return std::ranges::end(__r2) == + oneapi::dpl::ranges::mismatch(std::forward<_ExecutionPolicy>(__exec), __r1, __r2, + oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, _Pred>(__pred), + __proj1, __proj2) + .in2; } }; inline constexpr __internal::__starts_with_fn starts_with; @@ -1484,8 +1516,9 @@ struct __internal::__ends_with_fn #else auto __r1_dropped = std::views::all(__r1) | std::views::drop(__size_diff); #endif - return oneapi::dpl::ranges::equal(std::forward<_ExecutionPolicy>(__exec), std::move(__r1_dropped), - std::forward<_R2>(__r2), __pred, __proj1, __proj2); + return oneapi::dpl::ranges::equal( + std::forward<_ExecutionPolicy>(__exec), std::move(__r1_dropped), std::forward<_R2>(__r2), + oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, _Pred>(__pred), __proj1, __proj2); } }; inline constexpr __internal::__ends_with_fn ends_with; @@ -1503,8 +1536,9 @@ struct __internal::__remove_if_fn operator()(_ExecutionPolicy&& __exec, _R&& __r, _Pred __pred, _Proj __proj = {}) const { const auto __dispatch_tag = oneapi::dpl::__ranges::__select_backend(__exec); - return oneapi::dpl::__internal::__ranges::__pattern_remove_if(__dispatch_tag, - std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), __pred, __proj); + return oneapi::dpl::__internal::__ranges::__pattern_remove_if( + __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), + oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, _Pred>(__pred), __proj); } }; //__remove_if_fn inline constexpr __internal::__remove_if_fn remove_if; @@ -1631,7 +1665,8 @@ struct __internal::__partition_fn { const auto __dispatch_tag = oneapi::dpl::__ranges::__select_backend(__exec); return oneapi::dpl::__internal::__ranges::__pattern_partition( - __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), __pred, __proj); + __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), + oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, _Pred>(__pred), __proj); } }; //__partition_fn inline constexpr __internal::__partition_fn partition; @@ -1648,7 +1683,8 @@ struct __internal::__stable_partition_fn { const auto __dispatch_tag = oneapi::dpl::__ranges::__select_backend(__exec); return oneapi::dpl::__internal::__ranges::__pattern_stable_partition( - __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), __pred, __proj); + __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), + oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, _Pred>(__pred), __proj); } }; //__stable_partition_fn inline constexpr __internal::__stable_partition_fn stable_partition; @@ -1674,7 +1710,8 @@ struct __internal::__partition_copy_fn const auto __dispatch_tag = oneapi::dpl::__ranges::__select_backend(__exec); return oneapi::dpl::__internal::__ranges::__pattern_partition_copy_ranges( __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), - std::forward<_OutR1>(__out_true_r), std::forward<_OutR2>(__out_false_r), __pred, __proj); + std::forward<_OutR1>(__out_true_r), std::forward<_OutR2>(__out_false_r), + oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, _Pred>(__pred), __proj); } }; //__partition_copy_fn inline constexpr __internal::__partition_copy_fn partition_copy; @@ -1712,7 +1749,8 @@ struct __internal::__is_partitioned_fn { const auto __dispatch_tag = oneapi::dpl::__ranges::__select_backend(__exec); return oneapi::dpl::__internal::__ranges::__pattern_is_partitioned( - __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), __pred, __proj); + __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), std::forward<_R>(__r), + oneapi::dpl::__internal::__ref_or_copy<_ExecutionPolicy, _Pred>(__pred), __proj); } }; //__is_partitioned_fn inline constexpr __internal::__is_partitioned_fn is_partitioned; @@ -2269,10 +2307,14 @@ is_sorted_until(_ExecutionPolicy&& __exec, _Range&& __rng, _Compare __comp) { const auto __dispatch_tag = oneapi::dpl::__ranges::__select_backend(__exec, __rng); - auto __view = views::all_read(::std::forward<_Range>(__rng)); + auto __view = views::all_read(std::forward<_Range>(__rng)); + + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>(__comp); + const auto __res = oneapi::dpl::__internal::__ranges::__pattern_adjacent_find( - __dispatch_tag, ::std::forward<_ExecutionPolicy>(__exec), __view, - oneapi::dpl::__internal::__reorder_pred<_Compare>(__comp), oneapi::dpl::__internal::__first_semantic()); + __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), __view, + oneapi::dpl::__internal::__reorder_pred(__relax_non_const_comp), oneapi::dpl::__internal::__first_semantic()); return __res == __view.size() ? __res : __res + 1; } @@ -2291,10 +2333,14 @@ is_sorted(_ExecutionPolicy&& __exec, _Range&& __rng, _Compare __comp) { const auto __dispatch_tag = oneapi::dpl::__ranges::__select_backend(__exec, __rng); - auto __view = views::all_read(::std::forward<_Range>(__rng)); + auto __view = views::all_read(std::forward<_Range>(__rng)); + + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>(__comp); + return oneapi::dpl::__internal::__ranges::__pattern_adjacent_find( - __dispatch_tag, ::std::forward<_ExecutionPolicy>(__exec), __view, - oneapi::dpl::__internal::__reorder_pred<_Compare>(__comp), + __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), __view, + oneapi::dpl::__internal::__reorder_pred(__relax_non_const_comp), oneapi::dpl::__internal::__or_semantic()) == __view.size(); } @@ -2394,17 +2440,19 @@ template oneapi::dpl::__internal::__enable_if_execution_policy<_ExecutionPolicy, oneapi::dpl::__internal::__difference_t<_Range>> max_element(_ExecutionPolicy&& __exec, _Range&& __rng, _Compare __comp) { - return min_element(::std::forward<_ExecutionPolicy>(__exec), ::std::forward<_Range>(__rng), - oneapi::dpl::__internal::__reorder_pred<_Compare>(__comp)); + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>(__comp); + + return min_element(std::forward<_ExecutionPolicy>(__exec), std::forward<_Range>(__rng), + oneapi::dpl::__internal::__reorder_pred(__relax_non_const_comp)); } template oneapi::dpl::__internal::__enable_if_execution_policy<_ExecutionPolicy, oneapi::dpl::__internal::__difference_t<_Range>> max_element(_ExecutionPolicy&& __exec, _Range&& __rng) { - return min_element(::std::forward<_ExecutionPolicy>(__exec), ::std::forward<_Range>(__rng), - oneapi::dpl::__internal::__reorder_pred( - oneapi::dpl::__internal::__pstl_less())); + return min_element(std::forward<_ExecutionPolicy>(__exec), std::forward<_Range>(__rng), + oneapi::dpl::__internal::__reorder_pred(oneapi::dpl::__internal::__pstl_less())); } template diff --git a/include/oneapi/dpl/pstl/hetero/algorithm_ranges_impl_hetero.h b/include/oneapi/dpl/pstl/hetero/algorithm_ranges_impl_hetero.h index ed87c0f4a98..b8b63faa68a 100644 --- a/include/oneapi/dpl/pstl/hetero/algorithm_ranges_impl_hetero.h +++ b/include/oneapi/dpl/pstl/hetero/algorithm_ranges_impl_hetero.h @@ -242,7 +242,13 @@ bool __pattern_equal(__hetero_tag<_BackendTag> __tag, _ExecutionPolicy&& __exec, _R1&& __r1, _R2&& __r2, _Pred __pred, _Proj1 __proj1, _Proj2 __proj2) { - oneapi::dpl::__internal::__binary_op<_Pred, _Proj1, _Proj2> __pred_2(__pred, __proj1, __proj2); + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __pred); + + oneapi::dpl::__internal::__binary_op __pred_2( + __relax_non_const_pred, __proj1, __proj2); return oneapi::dpl::__internal::__ranges::__pattern_equal( __tag, ::std::forward<_ExecutionPolicy>(__exec), @@ -274,8 +280,12 @@ __pattern_lexicographical_compare(__hetero_tag<_BackendTag>, _ExecutionPolicy&& using _ReduceValueType = std::int32_t; __pattern_lexicographical_compare_reduce_fn<_ReduceValueType> __reduce_fn; - __pattern_lexicographical_compare_transform_fn<__binary_op<_Comp, _Proj1, _Proj2>, _ReduceValueType> - __transform_fn{__binary_op{__comp, __proj1, __proj2}}; + __binary_op __pred_2{ + oneapi::dpl::__internal::__get_relax_non_const_comp, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __comp), + __proj1, __proj2}; + __pattern_lexicographical_compare_transform_fn __transform_fn{__pred_2}; auto __ret_idx = oneapi::dpl::__par_backend_hetero::__parallel_transform_reduce<_ReduceValueType, std::false_type /*is_commutative*/>( @@ -316,7 +326,11 @@ template __pattern_find_if(__hetero_tag<_BackendTag> __tag, _ExecutionPolicy&& __exec, _R&& __r, _Pred __pred, _Proj __proj) { - oneapi::dpl::__internal::__unary_op<_Pred, _Proj> __pred_1{__pred, __proj}; + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred>( + __pred); + + oneapi::dpl::__internal::__unary_op __pred_1{__relax_non_const_pred, __proj}; auto __idx = oneapi::dpl::__internal::__ranges::__pattern_find_if(__tag, std::forward<_ExecutionPolicy>(__exec), oneapi::dpl::__ranges::views::all_read(__r), __pred_1); @@ -373,7 +387,13 @@ std::ranges::borrowed_subrange_t<_R1> __pattern_find_end(__hetero_tag<_BackendTag> __tag, _ExecutionPolicy&& __exec, _R1&& __r1, _R2&& __r2, _Pred __pred, _Proj1 __proj1, _Proj2 __proj2) { - oneapi::dpl::__internal::__binary_op<_Pred, _Proj1, _Proj2> __bin_pred{__pred, __proj1, __proj2}; + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __pred); + + oneapi::dpl::__internal::__binary_op __bin_pred{ + __relax_non_const_pred, __proj1, __proj2}; auto __idx = oneapi::dpl::__internal::__ranges::__pattern_find_end(__tag, std::forward<_ExecutionPolicy>(__exec), oneapi::dpl::__ranges::views::all_read(__r1), oneapi::dpl::__ranges::views::all_read(__r2), __bin_pred); @@ -421,7 +441,12 @@ std::ranges::borrowed_iterator_t<_R1> __pattern_find_first_of(__hetero_tag<_BackendTag> __tag, _ExecutionPolicy&& __exec, _R1&& __r1, _R2&& __r2, _Pred __pred, _Proj1 __proj1, _Proj2 __proj2) { - oneapi::dpl::__internal::__binary_op<_Pred, _Proj1, _Proj2> __bin_pred{__pred, __proj1, __proj2}; + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __pred); + oneapi::dpl::__internal::__binary_op __bin_pred{ + __relax_non_const_pred, __proj1, __proj2}; auto __idx = oneapi::dpl::__internal::__ranges::__pattern_find_first_of(__tag, std::forward<_ExecutionPolicy>(__exec), oneapi::dpl::__ranges::views::all_read(__r1), oneapi::dpl::__ranges::views::all_read(__r2), __bin_pred); @@ -455,7 +480,11 @@ template __tag, _ExecutionPolicy&& __exec, _R&& __r, _Pred __pred, _Proj __proj) { - oneapi::dpl::__internal::__unary_op<_Pred, _Proj> __pred_1{__pred, __proj}; + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred>( + __pred); + oneapi::dpl::__internal::__unary_op __pred_1{__relax_non_const_pred, + __proj}; return oneapi::dpl::__internal::__ranges::__pattern_any_of(__tag, std::forward<_ExecutionPolicy>(__exec), oneapi::dpl::__ranges::views::all_read(std::forward<_R>(__r)), __pred_1); @@ -507,7 +536,12 @@ std::ranges::borrowed_subrange_t<_R1> __pattern_search(__hetero_tag<_BackendTag> __tag, _ExecutionPolicy&& __exec, _R1&& __r1, _R2&& __r2, _Pred __pred, _Proj1 __proj1, _Proj2 __proj2) { - oneapi::dpl::__internal::__binary_op<_Pred, _Proj1, _Proj2> __pred_2{__pred, __proj1, __proj2}; + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __pred); + oneapi::dpl::__internal::__binary_op __pred_2{ + __relax_non_const_pred, __proj1, __proj2}; auto __idx = oneapi::dpl::__internal::__ranges::__pattern_search(__tag, std::forward<_ExecutionPolicy>(__exec), oneapi::dpl::__ranges::views::all_read(__r1), @@ -559,7 +593,11 @@ std::ranges::borrowed_subrange_t<_R> __pattern_search_n(__hetero_tag<_BackendTag> __tag, _ExecutionPolicy&& __exec, _R&& __r, std::ranges::range_difference_t<_R> __count, const _T& __value, _Pred __pred, _Proj __proj) { - oneapi::dpl::__internal::__binary_op<_Pred, _Proj, std::identity> __pred_2{__pred, __proj, std::identity{}}; + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred, + _T>(__pred); + oneapi::dpl::__internal::__binary_op __pred_2{ + __relax_non_const_pred, __proj, std::identity{}}; auto __idx = oneapi::dpl::__internal::__ranges::__pattern_search_n(__tag, std::forward<_ExecutionPolicy>(__exec), oneapi::dpl::__ranges::views::all_read(__r), __count, __value, __pred_2); @@ -622,7 +660,12 @@ std::ranges::borrowed_iterator_t<_R> __pattern_adjacent_find_ranges(__hetero_tag<_BackendTag> __tag, _ExecutionPolicy&& __exec, _R&& __r, _Pred __pred, _Proj __proj) { - oneapi::dpl::__internal::__binary_op<_Pred, _Proj, _Proj> __pred_2{__pred, __proj, __proj}; + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred, + oneapi::dpl::__internal::__projected_value_t<_Proj, _R>>( + __pred); + oneapi::dpl::__internal::__binary_op __pred_2{ + __relax_non_const_pred, __proj, __proj}; auto __idx = oneapi::dpl::__internal::__ranges::__pattern_adjacent_find(__tag, std::forward<_ExecutionPolicy>(__exec), @@ -662,7 +705,10 @@ template __pattern_count_if(__hetero_tag<_BackendTag> __tag, _ExecutionPolicy&& __exec, _R&& __r, _Pred __pred, _Proj __proj) { - oneapi::dpl::__internal::__unary_op<_Pred, _Proj> __pred_1{__pred, __proj}; + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred>( + __pred); + oneapi::dpl::__internal::__unary_op __pred_1{__relax_non_const_pred, __proj}; return oneapi::dpl::__internal::__ranges::__pattern_count( __tag, std::forward<_ExecutionPolicy>(__exec), oneapi::dpl::__ranges::views::all_read(std::forward<_R>(__r)), @@ -713,15 +759,18 @@ __pattern_copy_if_ranges(__hetero_tag<_BackendTag> __tag, _ExecutionPolicy&& __e if (__n == 0) return {std::ranges::begin(__in_r), std::ranges::begin(__out_r)}; + auto __relax_non_const_pred = oneapi::dpl::__internal::__get_relax_non_const_pred< + oneapi::dpl::__internal::__projected_value_t<_Proj, _InRange>>(__pred); + _Size __n_out = oneapi::dpl::__ranges::__size(__out_r); if (__n_out == 0) { auto __found_it = __pattern_find_if(__tag, std::forward<_ExecutionPolicy>(__exec), - std::forward<_InRange>(__in_r), __pred, __proj); + std::forward<_InRange>(__in_r), __relax_non_const_pred, __proj); return {__found_it, std::ranges::begin(__out_r)}; } - oneapi::dpl::__internal::__unary_op<_Pred, _Proj> __pred_1{__pred, __proj}; + oneapi::dpl::__internal::__unary_op __pred_1{__relax_non_const_pred, __proj}; std::array<_Size, 2> __stops = oneapi::dpl::__par_backend_hetero::__parallel_copy_if( _BackendTag{}, std::forward<_ExecutionPolicy>(__exec), @@ -743,9 +792,13 @@ __pattern_partition(__hetero_tag<_BackendTag> __tag, _ExecutionPolicy&& __exec, { auto [__first, __last] = oneapi::dpl::__ranges::__bounds(__r); - auto __middle = - oneapi::dpl::__internal::__pattern_partition(__tag, std::forward<_ExecutionPolicy>(__exec), __first, __last, - oneapi::dpl::__internal::__unary_op<_Pred, _Proj>{__pred, __proj}); + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred>( + __pred); + + auto __middle = oneapi::dpl::__internal::__pattern_partition( + __tag, std::forward<_ExecutionPolicy>(__exec), __first, __last, + oneapi::dpl::__internal::__unary_op{__relax_non_const_pred, __proj}); return {__middle, __last}; } @@ -763,9 +816,13 @@ __pattern_stable_partition(__hetero_tag<_BackendTag> __tag, _ExecutionPolicy&& _ { auto [__first, __last] = oneapi::dpl::__ranges::__bounds(__r); + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred>( + __pred); + auto __middle = oneapi::dpl::__internal::__pattern_stable_partition( __tag, std::forward<_ExecutionPolicy>(__exec), __first, __last, - oneapi::dpl::__internal::__unary_op<_Pred, _Proj>{__pred, __proj}); + oneapi::dpl::__internal::__unary_op{__relax_non_const_pred, __proj}); return {__middle, __last}; } @@ -790,7 +847,11 @@ __pattern_partition_copy_ranges(__hetero_tag<_BackendTag> __tag, _ExecutionPolic if (__in_size == 0 || (__out1_size == 0 && __out2_size == 0)) return {__in_first, __out1_first, __out2_first}; - oneapi::dpl::__internal::__unary_op<_Pred, _Proj> __pred_1{__pred, __proj}; + auto __relax_non_const_pred = oneapi::dpl::__internal::__get_relax_non_const_pred< + oneapi::dpl::__internal::__projected_value_t<_Proj, _InRange>>(__pred); + oneapi::dpl::__internal::__unary_op __pred_1{__relax_non_const_pred, + __proj}; + std::array __stops = oneapi::dpl::__par_backend_hetero::__parallel_partition_copy( _BackendTag{}, std::forward<_ExecutionPolicy>(__exec), @@ -814,9 +875,13 @@ __pattern_nth_element(__hetero_tag<_BackendTag> __tag, _ExecutionPolicy&& __exec { auto [__first, __last] = oneapi::dpl::__ranges::__bounds(__r); + auto __relax_non_const_comp = oneapi::dpl::__internal::__get_relax_non_const_comp< + oneapi::dpl::__internal::__projected_value_t<_Proj, _Range>>(__comp); + oneapi::dpl::__internal::__pattern_nth_element( __tag, std::forward<_ExecutionPolicy>(__exec), __first, __nth, __last, - oneapi::dpl::__internal::__binary_op<_Comp, _Proj, _Proj>{__comp, __proj, __proj}); + oneapi::dpl::__internal::__binary_op{__relax_non_const_comp, + __proj, __proj}); return __last; } @@ -834,9 +899,12 @@ __pattern_is_partitioned(__hetero_tag<_BackendTag> __tag, _ExecutionPolicy&& __e { auto [__first, __last] = oneapi::dpl::__ranges::__bounds(__r); + auto __relax_non_const_pred = oneapi::dpl::__internal::__get_relax_non_const_pred< + oneapi::dpl::__internal::__projected_value_t<_Proj, _Range>>(__pred); + return oneapi::dpl::__internal::__pattern_is_partitioned( __tag, std::forward<_ExecutionPolicy>(__exec), __first, __last, - oneapi::dpl::__internal::__unary_op<_Pred, _Proj>{__pred, __proj}); + oneapi::dpl::__internal::__unary_op{__relax_non_const_pred, __proj}); } #endif // #if _ONEDPL_CPP20_RANGES_PRESENT @@ -873,7 +941,10 @@ template __pattern_remove_if(__hetero_tag<_BackendTag> __tag, _ExecutionPolicy&& __exec, _R&& __r, _Pred __pred, _Proj __proj) { - oneapi::dpl::__internal::__unary_op<_Pred, _Proj> __pred_1{__pred, __proj}; + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred>( + __pred); + oneapi::dpl::__internal::__unary_op __pred_1{__relax_non_const_pred, __proj}; auto __n = oneapi::dpl::__ranges::__size(__r); auto __idx = oneapi::dpl::__internal::__ranges::__pattern_remove_if( @@ -893,7 +964,11 @@ template __tag, _ExecutionPolicy&& __exec, _R&& __r, _Comp __comp, _Proj __proj) { - oneapi::dpl::__internal::__binary_op<_Comp, _Proj, _Proj> __pred_2{__comp, __proj, __proj}; + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp); + oneapi::dpl::__internal::__binary_op __pred_2{ + __relax_non_const_comp, __proj, __proj}; return oneapi::dpl::__internal::__ranges::__pattern_adjacent_find( __tag, std::forward<_ExecutionPolicy>(__exec), oneapi::dpl::__ranges::views::all_read(__r), @@ -908,7 +983,7 @@ template void __pattern_reverse(__hetero_tag<_BackendTag>, _ExecutionPolicy&& __exec, _R&& __r) { - const auto __n = oneapi::dpl::__ranges::__size(__r); + const std::size_t __n = oneapi::dpl::__ranges::__size(__r); if (__n <= 1) return; @@ -939,6 +1014,29 @@ __pattern_reverse_copy(__hetero_tag<_BackendTag>, _ExecutionPolicy&& __exec, _In .__checked_deferrable_wait(); } +//------------------------------------------------------------------------ +// rotate_copy +//------------------------------------------------------------------------ +template +void +__pattern_rotate_copy(__hetero_tag<_BackendTag>, _ExecutionPolicy&& __exec, _InRange&& __in_r, _OutRange&& __out_r, + std::size_t __shift) +{ + const std::size_t __n = oneapi::dpl::__ranges::__size(__in_r); + const std::size_t __n_out = oneapi::dpl::__ranges::__size(__out_r); + + assert(__n_out <= __n); // the output range must be trimmed on the caller side + + if (__n == 0 || __n_out == 0) + return; + + oneapi::dpl::__par_backend_hetero::__parallel_for( + _BackendTag{}, std::forward<_ExecutionPolicy>(__exec), unseq_backend::__rotate_copy{__n_out, __n, __shift}, + __n_out, oneapi::dpl::__ranges::__get_subscription_view(std::forward<_InRange>(__in_r)), + oneapi::dpl::__ranges::__get_subscription_view(std::forward<_OutRange>(__out_r))) + .__checked_deferrable_wait(); +} + //------------------------------------------------------------------------ // replace_copy //------------------------------------------------------------------------ @@ -949,8 +1047,12 @@ __pattern_replace_copy_if(__hetero_tag<_BackendTag> __tag, _ExecutionPolicy&& __ _OutRange&& __out_r, _Pred __pred, const _T& __new_value, _Proj __proj) { assert(std::ranges::size(__r) <= std::ranges::size(__out_r)); - oneapi::dpl::__internal::__replace_copy_functor> - __f{__new_value, {__pred, __proj}}; + + auto __relax_non_const_pred = oneapi::dpl::__internal::__get_relax_non_const_pred< + oneapi::dpl::__internal::__projected_value_t<_Proj, _InRange>>(__pred); + oneapi::dpl::__internal::__replace_copy_functor< + const _T, oneapi::dpl::__internal::__unary_op> + __f{__new_value, {__relax_non_const_pred, __proj}}; oneapi::dpl::__internal::__ranges::__pattern_walk_n(__tag, std::forward<_ExecutionPolicy>(__exec), __f, oneapi::dpl::__ranges::views::all_read(std::forward<_InRange>(__r)), @@ -1025,7 +1127,11 @@ __pattern_unique_copy(__hetero_tag<_BackendTag>, _ExecutionPolicy&& __exec, _R&& if (__n == 0 || __n_out == 0) return {std::ranges::begin(__r), std::ranges::begin(__out_r)}; - oneapi::dpl::__internal::__binary_op<_Comp, _Proj, _Proj> __pred_2{__comp, __proj, __proj}; + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp); + oneapi::dpl::__internal::__binary_op __pred_2{ + __relax_non_const_comp, __proj, __proj}; std::array<_Size, 2> __stops = oneapi::dpl::__par_backend_hetero::__parallel_unique_copy( _BackendTag{}, std::forward<_ExecutionPolicy>(__exec), oneapi::dpl::__ranges::views::all_read(__r), @@ -1074,7 +1180,11 @@ template __pattern_unique(__hetero_tag<_BackendTag> __tag, _ExecutionPolicy&& __exec, _R&& __r, _Comp __comp, _Proj __proj) { - oneapi::dpl::__internal::__binary_op<_Comp, _Proj, _Proj> __pred_2{__comp, __proj, __proj}; + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp); + oneapi::dpl::__internal::__binary_op __pred_2{ + __relax_non_const_comp, __proj, __proj}; auto __beg = std::ranges::begin(__r); auto __end = __beg + std::ranges::size(__r); @@ -1095,7 +1205,11 @@ template __tag, _ExecutionPolicy&& __exec, _R&& __r, _Comp __comp, _Proj __proj) { - oneapi::dpl::__internal::__binary_op<_Comp, _Proj, _Proj> __pred_2{__comp, __proj, __proj}; + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp); + oneapi::dpl::__internal::__binary_op __pred_2{ + __relax_non_const_comp, __proj, __proj}; auto __beg = std::ranges::begin(__r); auto __end = __beg + std::ranges::size(__r); @@ -1115,7 +1229,11 @@ std::ranges::borrowed_iterator_t<_R> __pattern_is_heap_until(__hetero_tag<_BackendTag> __tag, _ExecutionPolicy&& __exec, _R&& __r, _Comp __comp, _Proj __proj) { - oneapi::dpl::__internal::__binary_op<_Comp, _Proj, _Proj> __pred_2{__comp, __proj, __proj}; + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp); + oneapi::dpl::__internal::__binary_op __pred_2{ + __relax_non_const_comp, __proj, __proj}; auto __beg = std::ranges::begin(__r); auto __end = __beg + std::ranges::size(__r); @@ -1176,7 +1294,11 @@ __pattern_merge(__hetero_tag<_BackendTag> __tag, _ExecutionPolicy&& __exec, _Ran _BackendTag{}, ::std::forward<_ExecutionPolicy>(__exec), oneapi::dpl::__ranges::__get_subscription_view(std::forward<_Range1>(__rng1)), oneapi::dpl::__ranges::__get_subscription_view(std::forward<_Range2>(__rng2)), - oneapi::dpl::__ranges::__get_subscription_view(std::forward<_Range3>(__rng3)), __comp, __proj1, __proj2); + oneapi::dpl::__ranges::__get_subscription_view(std::forward<_Range3>(__rng3)), + oneapi::dpl::__internal::__get_relax_non_const_comp< + oneapi::dpl::__internal::__projected_value_t<_Proj1, _Range1>, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _Range2>>(__comp), + __proj1, __proj2); auto __val = __res.get(); return {__val.first, __val.second}; @@ -1198,8 +1320,11 @@ __pattern_merge_ranges(__hetero_tag<_BackendTag> __tag, _ExecutionPolicy&& __exe const std::pair __res = oneapi::dpl::__internal::__ranges::__pattern_merge( __tag, std::forward<_ExecutionPolicy>(__exec), oneapi::dpl::__ranges::views::all_read(__r1), - oneapi::dpl::__ranges::views::all_read(__r2), oneapi::dpl::__ranges::views::all_write(__out_r), __comp, __proj1, - __proj2); + oneapi::dpl::__ranges::views::all_read(__r2), oneapi::dpl::__ranges::views::all_write(__out_r), + oneapi::dpl::__internal::__get_relax_non_const_comp, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __comp), + __proj1, __proj2); return {std::ranges::begin(__r1) + __res.first, std::ranges::begin(__r2) + __res.second, std::ranges::begin(__out_r) + __n_out}; @@ -1218,9 +1343,14 @@ __pattern_inplace_merge_ranges(__hetero_tag<_Tag> __tag, _ExecutionPolicy&& __ex { auto [__first, __last] = oneapi::dpl::__ranges::__bounds(__r); + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp); + oneapi::dpl::__internal::__pattern_inplace_merge( __tag, std::forward<_ExecutionPolicy>(__exec), __first, __middle, __last, - oneapi::dpl::__internal::__binary_op<_Comp, _Proj, _Proj>{__comp, __proj, __proj}); + oneapi::dpl::__internal::__binary_op{__relax_non_const_comp, + __proj, __proj}); return __last; } @@ -1250,13 +1380,19 @@ __pattern_includes(__hetero_tag<_BackendTag>, _ExecutionPolicy&& __exec, _R1&& _ if (__n1 == 0 || __n2 > __n1) return false; - using __brick_include_type = unseq_backend::__brick_includes; + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __comp); + + using __brick_include_type = unseq_backend::__brick_includes; using _TagType = __par_backend_hetero::__parallel_or_tag; using __size_calc = oneapi::dpl::__ranges::__second_size_calc; return !oneapi::dpl::__par_backend_hetero::__parallel_find_or( _BackendTag{}, std::forward<_ExecutionPolicy>(__exec), - __brick_include_type{__n1, __n2, __comp, __proj1, __proj2}, _TagType{}, __size_calc{}, + __brick_include_type{__n1, __n2, __relax_non_const_comp, __proj1, __proj2}, _TagType{}, __size_calc{}, oneapi::dpl::__ranges::__get_subscription_view(std::forward<_R1>(__r1)), oneapi::dpl::__ranges::__get_subscription_view(std::forward<_R2>(__r2))); } @@ -1313,7 +1449,11 @@ __pattern_set_union(__hetero_tag<_BackendTag> __tag, _ExecutionPolicy&& __exec, const auto [__stop1, __stop2, __stop3] = __par_backend_hetero::__parallel_set_op( _BackendTag{}, unseq_backend::_UnionTag{}, std::forward<_ExecutionPolicy>(__exec), oneapi::dpl::__ranges::__get_subscription_view(__r1), oneapi::dpl::__ranges::__get_subscription_view(__r2), - oneapi::dpl::__ranges::__get_subscription_view(__out_r), __comp, __proj1, __proj2); + oneapi::dpl::__ranges::__get_subscription_view(__out_r), + oneapi::dpl::__internal::__get_relax_non_const_comp, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __comp), + __proj1, __proj2); return {__first1 + __stop1, __first2 + __stop2, __result + __stop3}; } @@ -1336,7 +1476,11 @@ __pattern_set_intersection(__hetero_tag<_BackendTag>, _ExecutionPolicy&& __exec, const auto [__stop1, __stop2, __stop3] = __par_backend_hetero::__parallel_set_op( _BackendTag{}, unseq_backend::_IntersectionTag{}, std::forward<_ExecutionPolicy>(__exec), oneapi::dpl::__ranges::__get_subscription_view(__r1), oneapi::dpl::__ranges::__get_subscription_view(__r2), - oneapi::dpl::__ranges::__get_subscription_view(__out_r), __comp, __proj1, __proj2); + oneapi::dpl::__ranges::__get_subscription_view(__out_r), + oneapi::dpl::__internal::__get_relax_non_const_comp, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __comp), + __proj1, __proj2); return {__first1 + __stop1, __first2 + __stop2, __result + __stop3}; } @@ -1376,7 +1520,11 @@ __pattern_set_difference(__hetero_tag<_BackendTag> __tag, _ExecutionPolicy&& __e const auto [__stop1, __stop2, __stop3] = __par_backend_hetero::__parallel_set_op( _BackendTag{}, unseq_backend::_DifferenceTag{}, std::forward<_ExecutionPolicy>(__exec), oneapi::dpl::__ranges::__get_subscription_view(__r1), oneapi::dpl::__ranges::__get_subscription_view(__r2), - oneapi::dpl::__ranges::__get_subscription_view(__out_r), __comp, __proj1, __proj2); + oneapi::dpl::__ranges::__get_subscription_view(__out_r), + oneapi::dpl::__internal::__get_relax_non_const_comp, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __comp), + __proj1, __proj2); return oneapi::dpl::__utils::__create_set_difference_result(__first1 + __stop1, __first2 + __stop2, __result + __stop3); @@ -1435,7 +1583,11 @@ __pattern_set_symmetric_difference(__hetero_tag<_BackendTag> __tag, _ExecutionPo const auto [__stop1, __stop2, __stop3] = __par_backend_hetero::__parallel_set_op( _BackendTag{}, unseq_backend::_SymmetricDifferenceTag{}, std::forward<_ExecutionPolicy>(__exec), oneapi::dpl::__ranges::__get_subscription_view(__r1), oneapi::dpl::__ranges::__get_subscription_view(__r2), - oneapi::dpl::__ranges::__get_subscription_view(__out_r), __comp, __proj1, __proj2); + oneapi::dpl::__ranges::__get_subscription_view(__out_r), + oneapi::dpl::__internal::__get_relax_non_const_comp, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __comp), + __proj1, __proj2); return {__first1 + __stop1, __first2 + __stop2, __result + __stop3}; } @@ -1452,7 +1604,12 @@ __pattern_mismatch(__hetero_tag<_BackendTag>, _ExecutionPolicy&& __exec, _R1&& _ if (std::ranges::empty(__r1) || std::ranges::empty(__r2)) return {std::ranges::begin(__r1), std::ranges::begin(__r2)}; - oneapi::dpl::__internal::__binary_op<_Pred, _Proj1, _Proj2> __bin_pred{__pred, __proj1, __proj2}; + auto __relax_non_const_pred = + oneapi::dpl::__internal::__get_relax_non_const_pred, + oneapi::dpl::__internal::__projected_value_t<_Proj2, _R2>>( + __pred); + oneapi::dpl::__internal::__binary_op __bin_pred{ + __relax_non_const_pred, __proj1, __proj2}; using __bin_pred_type = decltype(__bin_pred); using _IndexType = std::make_unsigned_t< @@ -1485,7 +1642,10 @@ __pattern_stable_sort(__hetero_tag<_BackendTag>, _ExecutionPolicy&& __exec, _Ran { __par_backend_hetero::__parallel_stable_sort( _BackendTag{}, ::std::forward<_ExecutionPolicy>(__exec), - oneapi::dpl::__ranges::__get_subscription_view(std::forward<_Range>(__rng)), __comp, __proj) + oneapi::dpl::__ranges::__get_subscription_view(std::forward<_Range>(__rng)), + oneapi::dpl::__internal::__get_relax_non_const_comp< + oneapi::dpl::__internal::__projected_value_t<_Proj, _Range>>(__comp), + __proj) .__checked_deferrable_wait(); } } @@ -1497,8 +1657,11 @@ std::ranges::borrowed_iterator_t<_R> __pattern_sort_ranges(__hetero_tag<_BackendTag> __tag, _ExecutionPolicy&& __exec, _R&& __r, _Comp __comp, _Proj __proj, _LeafSort = {}) { - oneapi::dpl::__internal::__ranges::__pattern_stable_sort(__tag, std::forward<_ExecutionPolicy>(__exec), - oneapi::dpl::__ranges::views::all(__r), __comp, __proj); + oneapi::dpl::__internal::__ranges::__pattern_stable_sort( + __tag, std::forward<_ExecutionPolicy>(__exec), oneapi::dpl::__ranges::views::all(__r), + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp), + __proj); return std::ranges::begin(__r) + oneapi::dpl::__ranges::__size(__r); } @@ -1512,9 +1675,14 @@ __pattern_partial_sort_ranges(__hetero_tag<_BackendTag> __tag, _ExecutionPolicy& { auto [__first, __last] = oneapi::dpl::__ranges::__bounds(__r); + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp); + oneapi::dpl::__internal::__pattern_partial_sort( __tag, std::forward<_ExecutionPolicy>(__exec), __first, __middle, __last, - oneapi::dpl::__internal::__binary_op<_Comp, _Proj, _Proj>{__comp, __proj, __proj}); + oneapi::dpl::__internal::__binary_op{__relax_non_const_comp, + __proj, __proj}); return __last; } @@ -1530,10 +1698,14 @@ __pattern_partial_sort_copy_ranges(__hetero_tag<_BackendTag> __tag, _ExecutionPo auto [__first1, __last1] = oneapi::dpl::__ranges::__bounds(__r); auto [__out_it, __out_end] = oneapi::dpl::__ranges::__bounds(__out_r); + auto __relax_non_const_comp = oneapi::dpl::__internal::__get_relax_non_const_comp< + oneapi::dpl::__internal::__projected_value_t<_Proj2, _OutR>>(__comp); + // __pattern_partial_sort_copy sorts after copying, so _Proj1 is not used auto __out_finish = oneapi::dpl::__internal::__pattern_partial_sort_copy( __tag, std::forward<_ExecutionPolicy>(__exec), __first1, __last1, __out_it, __out_end, - oneapi::dpl::__internal::__binary_op<_Comp, _Proj2, _Proj2>{__comp, __proj2, __proj2}); + oneapi::dpl::__internal::__binary_op{__relax_non_const_comp, + __proj2, __proj2}); return {__last1, __out_finish}; } @@ -1553,9 +1725,13 @@ __pattern_min_element_impl(_BackendTag __tag, _ExecutionPolicy&& __exec, _Range& using _IndexValueType = oneapi::dpl::__internal::__difference_t<_Range>; using _ReduceValueType = oneapi::dpl::__internal::tuple<_IndexValueType, _IteratorValueType>; + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>(__comp); + // This operator doesn't track the lowest found index in case of equal min. or max. values. Thus, this operator is // not commutative. - __pattern_min_element_reduce_fn<_ReduceValueType, _Compare> __reduce_fn{__comp}; + __pattern_min_element_reduce_fn<_ReduceValueType, decltype(__relax_non_const_comp)> __reduce_fn{ + __relax_non_const_comp}; oneapi::dpl::__internal::__pattern_min_element_transform_fn<_ReduceValueType> __transform_fn; [[maybe_unused]] auto [__idx, __val] = @@ -1577,9 +1753,10 @@ __pattern_min_element(__hetero_tag<_BackendTag>, _ExecutionPolicy&& __exec, _Ran if (oneapi::dpl::__ranges::__size(__rng) < 2) return 0; - [[maybe_unused]] auto [__idx, __val] = - __pattern_min_element_impl(_BackendTag{}, std::forward<_ExecutionPolicy>(__exec), - oneapi::dpl::__ranges::__get_subscription_view(std::forward<_Range>(__rng)), __comp); + [[maybe_unused]] auto [__idx, __val] = __pattern_min_element_impl( + _BackendTag{}, std::forward<_ExecutionPolicy>(__exec), + oneapi::dpl::__ranges::__get_subscription_view(std::forward<_Range>(__rng)), + oneapi::dpl::__internal::__get_relax_non_const_comp>(__comp)); return __idx; } @@ -1589,7 +1766,11 @@ template __pattern_min_element(__hetero_tag<_BackendTag> __tag, _ExecutionPolicy&& __exec, _R&& __r, _Comp __comp, _Proj __proj) { - oneapi::dpl::__internal::__binary_op<_Comp, _Proj, _Proj> __comp_2{__comp, __proj, __proj}; + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp); + oneapi::dpl::__internal::__binary_op __comp_2{ + __relax_non_const_comp, __proj, __proj}; const auto __idx = oneapi::dpl::__internal::__ranges::__pattern_min_element( __tag, std::forward<_ExecutionPolicy>(__exec), oneapi::dpl::__ranges::views::all_read(__r), __comp_2); @@ -1601,11 +1782,14 @@ template __pattern_min(__hetero_tag<_BackendTag>, _ExecutionPolicy&& __exec, _R&& __r, _Comp __comp, _Proj __proj) { - oneapi::dpl::__internal::__binary_op<_Comp, _Proj, _Proj> __comp_2{__comp, __proj, __proj}; + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp); + oneapi::dpl::__internal::__binary_op __comp_2{ + __relax_non_const_comp, __proj, __proj}; - [[maybe_unused]] const auto& [__idx, __val] = - __pattern_min_element_impl(_BackendTag{}, std::forward<_ExecutionPolicy>(__exec), - oneapi::dpl::__ranges::views::all_read(__r), __comp_2); + [[maybe_unused]] const auto& [__idx, __val] = __pattern_min_element_impl( + _BackendTag{}, std::forward<_ExecutionPolicy>(__exec), oneapi::dpl::__ranges::views::all_read(__r), __comp_2); return __val; } @@ -1629,7 +1813,10 @@ __pattern_minmax_element_impl(_BackendTag, _ExecutionPolicy&& __exec, _Range&& _ // This operator doesn't track the lowest found index in case of equal min. values and the highest found index in // case of equal max. values. Thus, this operator is not commutative. - oneapi::dpl::__internal::__pattern_minmax_element_reduce_fn<_Compare, _ReduceValueType> __reduce_fn{__comp}; + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>(__comp); + oneapi::dpl::__internal::__pattern_minmax_element_reduce_fn + __reduce_fn{__relax_non_const_comp}; // TODO: Doesn't work with `zip_iterator`. // In that case the first and the second arguments of `_ReduceValueType` will be @@ -1657,7 +1844,8 @@ __pattern_minmax_element(__hetero_tag<_BackendTag>, _ExecutionPolicy&& __exec, _ [[maybe_unused]] const auto& [__res_min, __res_max] = __pattern_minmax_element_impl( _BackendTag{}, std::forward<_ExecutionPolicy>(__exec), - oneapi::dpl::__ranges::__get_subscription_view(std::forward<_Range>(__rng)), __comp); + oneapi::dpl::__ranges::__get_subscription_view(std::forward<_Range>(__rng)), + oneapi::dpl::__internal::__get_relax_non_const_comp>(__comp)); [[maybe_unused]] const auto& [__idx_min, __min] = __res_min; [[maybe_unused]] const auto& [__idx_max, __max] = __res_max; @@ -1671,11 +1859,14 @@ std::pair, std::ranges::borrowed_iterator_t __pattern_minmax_element(__hetero_tag<_BackendTag> __tag, _ExecutionPolicy&& __exec, _R&& __r, _Comp __comp, _Proj __proj) { - oneapi::dpl::__internal::__binary_op<_Comp, _Proj, _Proj> __comp_2{__comp, __proj, __proj}; + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp); + oneapi::dpl::__internal::__binary_op __comp_2{ + __relax_non_const_comp, __proj, __proj}; - const auto [__min_idx, __max_idx] = - oneapi::dpl::__internal::__ranges::__pattern_minmax_element(__tag, std::forward<_ExecutionPolicy>(__exec), - oneapi::dpl::__ranges::views::all_read(__r), __comp_2); + const auto [__min_idx, __max_idx] = oneapi::dpl::__internal::__ranges::__pattern_minmax_element( + __tag, std::forward<_ExecutionPolicy>(__exec), oneapi::dpl::__ranges::views::all_read(__r), __comp_2); return {std::ranges::begin(__r) + __min_idx, std::ranges::begin(__r) + __max_idx}; } @@ -1684,7 +1875,11 @@ template , std::ranges::range_value_t<_R>> __pattern_minmax(__hetero_tag<_BackendTag>, _ExecutionPolicy&& __exec, _R&& __r, _Comp __comp, _Proj __proj) { - oneapi::dpl::__internal::__binary_op<_Comp, _Proj, _Proj> __comp_2{__comp, __proj, __proj}; + auto __relax_non_const_comp = + oneapi::dpl::__internal::__get_relax_non_const_comp>( + __comp); + oneapi::dpl::__internal::__binary_op __comp_2{ + __relax_non_const_comp, __proj, __proj}; [[maybe_unused]] const auto& [__res_min, __res_max] = __pattern_minmax_element_impl(_BackendTag{}, std::forward<_ExecutionPolicy>(__exec), diff --git a/include/oneapi/dpl/pstl/hetero/dpcpp/parallel_backend_sycl.h b/include/oneapi/dpl/pstl/hetero/dpcpp/parallel_backend_sycl.h index 48fe2693dbb..69eaf18402c 100644 --- a/include/oneapi/dpl/pstl/hetero/dpcpp/parallel_backend_sycl.h +++ b/include/oneapi/dpl/pstl/hetero/dpcpp/parallel_backend_sycl.h @@ -254,10 +254,6 @@ struct __parallel_copy_if_single_group_functor<__internal::__optional_kernel_nam _Assign __assign, std::size_t __max_wg_size) { assert(__max_wg_size <= std::numeric_limits::max()); - // This type is used as a workaround for when an internal tuple is assigned to std::tuple, such as - // with zip_iterator - using __tuple_type = typename oneapi::dpl::__internal::__get_tuple_type< - std::decay_t, std::decay_t>::__type; __result_storage<_Size> __result{__q, 2}; @@ -295,7 +291,8 @@ struct __parallel_copy_if_single_group_functor<__internal::__optional_kernel_nam if (__lacc[__idx]) { _ValueType __out_idx = __lacc[__idx + __n_uniform]; if (__out_idx < __n_out) - __assign(static_cast<__tuple_type>(__in_rng[__idx]), __out_rng[__out_idx]); + __assign(oneapi::dpl::__internal::__tuple_type_cast(__in_rng[__idx], __out_rng), + __out_rng[__out_idx]); if (__out_idx == __n_out) __lacc[2 * __n_uniform] = __idx; // the actual stop position in the input } diff --git a/include/oneapi/dpl/pstl/hetero/dpcpp/parallel_backend_sycl_radix_sort.h b/include/oneapi/dpl/pstl/hetero/dpcpp/parallel_backend_sycl_radix_sort.h index f0b1b11b6fd..6d1be77fc77 100644 --- a/include/oneapi/dpl/pstl/hetero/dpcpp/parallel_backend_sycl_radix_sort.h +++ b/include/oneapi/dpl/pstl/hetero/dpcpp/parallel_backend_sycl_radix_sort.h @@ -22,6 +22,7 @@ #include #include #include // for std::invoke +#include // for std::addressof #include "sycl_defs.h" #include "parallel_backend_sycl_utils.h" diff --git a/include/oneapi/dpl/pstl/hetero/dpcpp/parallel_backend_sycl_radix_sort_one_wg.h b/include/oneapi/dpl/pstl/hetero/dpcpp/parallel_backend_sycl_radix_sort_one_wg.h index 46ef6f9eec0..4a4757328c5 100644 --- a/include/oneapi/dpl/pstl/hetero/dpcpp/parallel_backend_sycl_radix_sort_one_wg.h +++ b/include/oneapi/dpl/pstl/hetero/dpcpp/parallel_backend_sycl_radix_sort_one_wg.h @@ -16,10 +16,6 @@ #ifndef _ONEDPL_parallel_backend_sycl_radix_sort_one_wg_H #define _ONEDPL_parallel_backend_sycl_radix_sort_one_wg_H -#include "sycl_traits.h" //SYCL traits specialization for some oneDPL types. - -#include // for std::invoke - //The file is an internal file and the code of that file is included by a major file into the following namespaces: //namespace oneapi //{ @@ -120,7 +116,7 @@ struct __subgroup_radix_sort { const uint16_t __idx = __wi * __block_size + __i; if (__idx < __n) - new (&__values[__i]) _ValueT(__src[__idx]); + new (std::addressof(__values[__i])) _ValueT(__src[__idx]); } } @@ -308,7 +304,7 @@ struct __subgroup_radix_sort { const std::uint16_t __r = __indices[__i]; if (__r < __n) - new (&__exchange_lacc[__r]) _ValT(std::move(__values.__v[__i])); + new (std::addressof(__exchange_lacc[__r])) _ValT(std::move(__values.__v[__i])); } } else diff --git a/include/oneapi/dpl/pstl/hetero/dpcpp/parallel_backend_sycl_reduce_then_scan.h b/include/oneapi/dpl/pstl/hetero/dpcpp/parallel_backend_sycl_reduce_then_scan.h index 4ecb2343ac7..43b80422cf6 100644 --- a/include/oneapi/dpl/pstl/hetero/dpcpp/parallel_backend_sycl_reduce_then_scan.h +++ b/include/oneapi/dpl/pstl/hetero/dpcpp/parallel_backend_sycl_reduce_then_scan.h @@ -150,36 +150,27 @@ struct __write_to_id_if return (__carry_in + __max_write_offset > __out_size); } + // __v holds the element to write by value and is taken by a non-const reference, because the output element type + // may be assignable from a non-const lvalue of the input one only, see oneapi::dpl::__internal::__pstl_assign. template void - operator()(_OutRng& __out_rng, _SizeType __id, const _ValueType& __v) const + operator()(_OutRng& __out_rng, _SizeType __id, _ValueType& __v) const { - // Use of an explicit cast to our internal tuple type is required to resolve conversion issues between our - // internal tuple and std::tuple. If the underlying type is not a tuple, then the type will just be passed - // through. - using _ConvertedTupleType = - typename oneapi::dpl::__internal::__get_tuple_type(__v))>, - std::decay_t>::__type; if (std::get<1>(__v)) - __assign(static_cast<_ConvertedTupleType>(std::get<2>(__v)), __out_rng[std::get<0>(__v) - 1 + __offset]); + __assign(oneapi::dpl::__internal::__tuple_type_cast(std::get<2>(__v), __out_rng), + __out_rng[std::get<0>(__v) - 1 + __offset]); } template void - operator()(_OutRng& __out_rng, _SizeType __id, const _ValueType& __v, _OnOOBReached __on_oob_reached) const + operator()(_OutRng& __out_rng, _SizeType __id, _ValueType& __v, _OnOOBReached __on_oob_reached) const { - // Use of an explicit cast to our internal tuple type is required to resolve conversion issues between our - // internal tuple and std::tuple. If the underlying type is not a tuple, then the type will just be passed - // through. - using _ConvertedTupleType = - typename oneapi::dpl::__internal::__get_tuple_type(__v))>, - std::decay_t>::__type; if (std::get<1>(__v)) { const std::size_t __out_idx = std::get<0>(__v) - 1 + __offset; if (__out_idx < __out_size) - __assign(static_cast<_ConvertedTupleType>(std::get<2>(__v)), __out_rng[__out_idx]); + __assign(oneapi::dpl::__internal::__tuple_type_cast(std::get<2>(__v), __out_rng), __out_rng[__out_idx]); if (__out_idx == __out_size) __on_oob_reached(__id, __id); } @@ -216,18 +207,6 @@ struct __write_partitioned || (__start_idx - __mask_prefix) + __max_write_offset > __out2_size); } - template - static constexpr auto - __tuple_type_cast(_ValueType&& __value, _Range&&) - { - // An explicit cast to our internal tuple type is required to resolve conversion issues between an - // internal tuple and a std::tuple. If the underlying type is not a tuple, it just passes through. - // _Range is only used for type deduction; no runtime access occurs. - using _ConvertedType = typename oneapi::dpl::__internal::__get_tuple_type< - std::decay_t<_ValueType>, std::decay_t()[0])>>::__type; - return static_cast<_ConvertedType>(std::forward<_ValueType>(__value)); - } - template void operator()(_OutRng& __out_rng, _SizeType __id, const _ValueType& __v) const @@ -235,9 +214,9 @@ struct __write_partitioned const auto& [__mask_prefix, __mask, __value] = __v; auto& [__out1_rng, __out2_rng] = __out_rng; // unpack the tuple of outputs if (__mask) - __out1_rng[__mask_prefix - 1] = __tuple_type_cast(__value, __out1_rng); + __out1_rng[__mask_prefix - 1] = oneapi::dpl::__internal::__tuple_type_cast(__value, __out1_rng); else - __out2_rng[__id - __mask_prefix] = __tuple_type_cast(__value, __out2_rng); + __out2_rng[__id - __mask_prefix] = oneapi::dpl::__internal::__tuple_type_cast(__value, __out2_rng); } template @@ -259,9 +238,9 @@ struct __write_partitioned if (__target_idx_in_bound) { if (__mask) - __out1_rng[__out1_idx] = __tuple_type_cast(__value, __out1_rng); + __out1_rng[__out1_idx] = oneapi::dpl::__internal::__tuple_type_cast(__value, __out1_rng); else - __out2_rng[__out2_idx] = __tuple_type_cast(__value, __out2_rng); + __out2_rng[__out2_idx] = oneapi::dpl::__internal::__tuple_type_cast(__value, __out2_rng); } // If out-of-bound conditions detected, report stop positions in the input and the 1st output if (__oob_reached) @@ -1688,7 +1667,9 @@ __scan_through_elements_helper(const sycl::nd_item<1>& __ndi, _GenInput __gen_in __iters_per_item * __sg_size * _TempData::__max_outputs_per_input; if (__write_op.__oob_write_possible(__max_write_offset, __subgroup_start_id, __sub_group_carry)) { - auto __bounded_write_op = [&](std::size_t __id, const auto& __v) { + // __v is taken by a non-const reference to let a write operation assign from a non-const lvalue + // of the input element, which is all std::indirectly_copyable requires it to be assignable from + auto __bounded_write_op = [&](std::size_t __id, auto& __v) { if constexpr (__is_temp_data_required) __write_op(__out_rng, __id, __v, __temp_data, __on_oob_reached); else @@ -1702,7 +1683,7 @@ __scan_through_elements_helper(const sycl::nd_item<1>& __ndi, _GenInput __gen_in } } - auto __unbounded_write_op = [&](std::size_t __id, const auto& __v) { + auto __unbounded_write_op = [&](std::size_t __id, auto& __v) { if constexpr (__is_temp_data_required) __write_op(__out_rng, __id, __v, __temp_data); else diff --git a/include/oneapi/dpl/pstl/hetero/dpcpp/sycl_traits.h b/include/oneapi/dpl/pstl/hetero/dpcpp/sycl_traits.h index 138100c9340..f4fd57fc87f 100644 --- a/include/oneapi/dpl/pstl/hetero/dpcpp/sycl_traits.h +++ b/include/oneapi/dpl/pstl/hetero/dpcpp/sycl_traits.h @@ -63,6 +63,9 @@ class __not_pred; template class __reorder_pred; +template +class __relax_const_pred; + template class __pred_at_index; @@ -178,6 +181,12 @@ struct sycl::is_device_copyable<_ONEDPL_SPECIALIZE_FOR(oneapi::dpl::__internal:: { }; +template +struct sycl::is_device_copyable<_ONEDPL_SPECIALIZE_FOR(oneapi::dpl::__internal::__relax_const_pred, _Pred)> + : oneapi::dpl::__internal::__are_all_device_copyable<_Pred> +{ +}; + template struct sycl::is_device_copyable<_ONEDPL_SPECIALIZE_FOR(oneapi::dpl::__internal::__pred_at_index, _Pred)> : oneapi::dpl::__internal::__are_all_device_copyable<_Pred> diff --git a/include/oneapi/dpl/pstl/hetero/memory_ranges_impl_hetero.h b/include/oneapi/dpl/pstl/hetero/memory_ranges_impl_hetero.h index aff26df24d0..cb9b7b3cd03 100644 --- a/include/oneapi/dpl/pstl/hetero/memory_ranges_impl_hetero.h +++ b/include/oneapi/dpl/pstl/hetero/memory_ranges_impl_hetero.h @@ -183,8 +183,7 @@ __pattern_uninitialized_fill(__hetero_tag<_BackendTag> __tag, _ExecutionPolicy&& { using _ValueType = std::ranges::range_value_t<_R>; - auto __first = std::ranges::begin(__r); - auto __last = __first + std::ranges::size(__r); + auto [__first, __last] = oneapi::dpl::__ranges::__bounds(__r); if constexpr (oneapi::dpl::__internal::__trivial_uninitialized_fill<_ValueType, _T>) { diff --git a/include/oneapi/dpl/pstl/hetero/utils_hetero.h b/include/oneapi/dpl/pstl/hetero/utils_hetero.h index 6961803be53..234d4e82689 100644 --- a/include/oneapi/dpl/pstl/hetero/utils_hetero.h +++ b/include/oneapi/dpl/pstl/hetero/utils_hetero.h @@ -135,14 +135,11 @@ struct __pattern_lexicographical_compare_transform_fn _ReduceValueType operator()(_TGroupIdx __gidx, _TAcc1 __acc1, _TAcc2 __acc2) const { - auto const& __s1_val = __acc1[__gidx]; - auto const& __s2_val = __acc2[__gidx]; - - _ReduceValueType __is_s1_val_less = bool(std::invoke(__comp, __s1_val, __s2_val)); + _ReduceValueType __is_s1_val_less = bool(std::invoke(__comp, __acc1[__gidx], __acc2[__gidx])); _ReduceValueType __is_s1_val_greater = - bool(oneapi::dpl::__internal::__reorder_pred<_Compare>{__comp}(__s1_val, __s2_val)); + bool(oneapi::dpl::__internal::__reorder_pred<_Compare>{__comp}(__acc1[__gidx], __acc2[__gidx])); - // 1 if __s1_val < __s2_val, -1 if __s2_val < __s1_val, 0 if __s1_val == __s2_val + // 1 if __acc1[__gidx] < __acc2[__gidx], -1 if __acc2[__gidx] < __acc1[__gidx], 0 if __acc1[__gidx] == __acc2[__gidx] return __is_s1_val_less - __is_s1_val_greater; } }; diff --git a/include/oneapi/dpl/pstl/memory_ranges_impl.h b/include/oneapi/dpl/pstl/memory_ranges_impl.h index 9805d6dc329..87a9a53e9e3 100644 --- a/include/oneapi/dpl/pstl/memory_ranges_impl.h +++ b/include/oneapi/dpl/pstl/memory_ranges_impl.h @@ -49,8 +49,7 @@ __pattern_uninitialized_default_construct(_Tag __tag, _ExecutionPolicy&& __exec, using _ValueType = std::ranges::range_value_t<_R>; - auto __first = std::ranges::begin(__r); - auto __last = __first + std::ranges::size(__r); + auto [__first, __last] = oneapi::dpl::__ranges::__bounds(__r); if constexpr (!std::is_trivially_default_constructible_v<_ValueType>) { @@ -81,8 +80,8 @@ __pattern_uninitialized_value_construct(_Tag __tag, _ExecutionPolicy&& __exec, _ using _ValueType = std::ranges::range_value_t<_R>; - auto __first = std::ranges::begin(__r); - auto __last = __first + std::ranges::size(__r); + auto [__first, __last] = oneapi::dpl::__ranges::__bounds(__r); + if constexpr (oneapi::dpl::__internal::__trivial_uninitialized_value_construct<_ValueType>) { oneapi::dpl::__internal::__pattern_fill(__tag, std::forward<_ExecutionPolicy>(__exec), __first, __last, @@ -215,8 +214,7 @@ __pattern_uninitialized_fill(_Tag __tag, _ExecutionPolicy&& __exec, _R&& __r, co using _ValueType = std::ranges::range_value_t<_R>; - auto __first = std::ranges::begin(__r); - auto __last = __first + std::ranges::size(__r); + auto [__first, __last] = oneapi::dpl::__ranges::__bounds(__r); if constexpr (oneapi::dpl::__internal::__trivial_uninitialized_fill<_ValueType, _T>) { @@ -253,8 +251,7 @@ __pattern_destroy(_Tag __tag, _ExecutionPolicy&& __exec, _R&& __r) using _ValueType = std::ranges::range_value_t<_R>; - auto __first = std::ranges::begin(__r); - auto __last = __first + std::ranges::size(__r); + auto [__first, __last] = oneapi::dpl::__ranges::__bounds(__r); if constexpr (!std::is_trivially_destructible_v<_ValueType>) { @@ -269,7 +266,12 @@ template std::ranges::borrowed_iterator_t<_R> __pattern_destroy(__serial_tag, _ExecutionPolicy&&, _R&& __r) { - return std::ranges::destroy(std::forward<_R>(__r)); + // The iterator and sentinel overload is called with one iterator type instead of passing the range as a + // whole: a sized random access range is not required to be a common range, and libc++ (checked up to + // version 18) rejects std::ranges::destroy of a range whose sentinel type differs from its iterator type. + auto [__first, __last] = oneapi::dpl::__ranges::__bounds(__r); + + return std::ranges::destroy(__first, __last); } } // namespace __ranges diff --git a/include/oneapi/dpl/pstl/omp/parallel_stable_sort.h b/include/oneapi/dpl/pstl/omp/parallel_stable_sort.h index 0e97f66020a..cd9250c8ee0 100644 --- a/include/oneapi/dpl/pstl/omp/parallel_stable_sort.h +++ b/include/oneapi/dpl/pstl/omp/parallel_stable_sort.h @@ -78,6 +78,40 @@ struct __move_range return oneapi::dpl::__omp_backend::__sort_details::__parallel_move_range(__first1, __last1, __d_first); } }; + +//! Move construct the elements of [__first1, __last1) into the uninitialized memory at __d_first +template +_OutputIterator +__parallel_uninitialized_move_range(_RandomAccessIterator __first1, _RandomAccessIterator __last1, + _OutputIterator __d_first) +{ + std::size_t __size = __last1 - __first1; + + // Perform serial moving of small chunks + + if (__size <= __default_chunk_size) + { + return std::uninitialized_move(__first1, __last1, __d_first); + } + + // Perform parallel moving of larger chunks + + auto __policy = + oneapi::dpl::__omp_backend::__chunk_partitioner(__first1, __last1, omp_get_num_threads(), __default_chunk_size); + + _ONEDPL_PRAGMA(omp taskloop) + for (std::size_t __chunk = 0; __chunk < __policy.__n_chunks; ++__chunk) + { + oneapi::dpl::__omp_backend::__process_chunk( + __policy, __first1, __chunk, [&](auto __chunk_first, auto __chunk_last) { + auto __chunk_offset = __chunk_first - __first1; + auto __output_it = __d_first + __chunk_offset; + std::uninitialized_move(__chunk_first, __chunk_last, __output_it); + }); + } + + return __d_first + __size; +} } // namespace __sort_details template @@ -86,8 +120,6 @@ __parallel_stable_sort_body(_RandomAccessIterator __xs, _RandomAccessIterator __ _LeafSort __leaf_sort) { using _ValueType = typename std::iterator_traits<_RandomAccessIterator>::value_type; - using _VecType = typename std::vector<_ValueType>; - using _OutputIterator = typename _VecType::iterator; using _MoveValue = oneapi::dpl::__omp_backend::__sort_details::__move_value; using _MoveRange = oneapi::dpl::__omp_backend::__sort_details::__move_range; @@ -103,22 +135,27 @@ __parallel_stable_sort_body(_RandomAccessIterator __xs, _RandomAccessIterator __ [&]() { __parallel_stable_sort_body(__xs, __mid, __comp, __leaf_sort); }, [&]() { __parallel_stable_sort_body(__mid, __xe, __comp, __leaf_sort); }); - // Perform a parallel merge of the sorted ranges into __output_data. - _VecType __output_data(__size); + // Move the two sorted halves into a raw buffer. A buffer of uninitialized memory filled by move + // construction is used instead of a container of __size elements, because a sortable value type is + // not required to be default constructible. + oneapi::dpl::__omp_backend::__buffer<_ValueType> __buf(__size); + _ValueType* __input_data = __buf.get(); + oneapi::dpl::__omp_backend::__sort_details::__parallel_uninitialized_move_range(__xs, __xe, __input_data); + _ValueType* __input_mid = __input_data + (__mid - __xs); + + // Perform a parallel merge of the buffered ranges back into the original source range, whose elements + // are still alive, so that plain move assignment is all the merge needs. _MoveValue __move_value; _MoveRange __move_range; __utils::__serial_move_merge __merge(__size); oneapi::dpl::__omp_backend::__parallel_merge_body( - __mid - __xs, __xe - __mid, __xs, __mid, __mid, __xe, __output_data.begin(), __comp, - [&__merge, &__move_value, &__move_range](_RandomAccessIterator __as, _RandomAccessIterator __ae, - _RandomAccessIterator __bs, _RandomAccessIterator __be, - _OutputIterator __cs, _Compare __comp) { + __mid - __xs, __xe - __mid, __input_data, __input_mid, __input_mid, __input_data + __size, __xs, __comp, + [&__merge, &__move_value, &__move_range](_ValueType* __as, _ValueType* __ae, _ValueType* __bs, + _ValueType* __be, _RandomAccessIterator __cs, _Compare __comp) { __merge(__as, __ae, __bs, __be, __cs, __comp, __move_value, __move_value, __move_range, __move_range); }); - // Move the values from __output_data back in the original source range. - oneapi::dpl::__omp_backend::__sort_details::__parallel_move_range(__output_data.begin(), __output_data.end(), - __xs); + __utils::__serial_destroy()(__input_data, __input_data + __size); } } diff --git a/include/oneapi/dpl/pstl/tuple_impl.h b/include/oneapi/dpl/pstl/tuple_impl.h index 7191ed95d5c..e26d897b4a8 100644 --- a/include/oneapi/dpl/pstl/tuple_impl.h +++ b/include/oneapi/dpl/pstl/tuple_impl.h @@ -687,6 +687,24 @@ struct __get_tuple_type, _Other> using __type = typename oneapi::dpl::__internal::tuple<_Ts...>::tuple_type; }; +// Casts __value to the tuple type an element of __dest_rng can be assigned from, which resolves conversion issues +// between our internal tuple and std::tuple, as with zip_iterator. When no conversion is needed, __value is passed +// through as is instead: an explicit cast would make a copy of it and would thereby require more of its type than +// the algorithm does, since std::indirectly_copyable only asks for an assignment from a non-const lvalue of the +// input element. __dest_rng is used for type deduction only and is never accessed. +// The pass-through returns a reference to __value, so the result has to be consumed within the same full expression. +template +constexpr decltype(auto) +__tuple_type_cast(_ValueType&& __value, _Rng&& __dest_rng) +{ + using _ConvertedType = + typename __get_tuple_type, std::decay_t>::__type; + if constexpr (std::is_same_v<_ConvertedType, std::decay_t<_ValueType>>) + return std::forward<_ValueType>(__value); + else + return static_cast<_ConvertedType>(std::forward<_ValueType>(__value)); +} + // Converts std::tuple to the internal tuple template struct __repacked_tuple diff --git a/include/oneapi/dpl/pstl/unseq_backend_simd.h b/include/oneapi/dpl/pstl/unseq_backend_simd.h index d466e4d2c4f..a5a9c5c3609 100644 --- a/include/oneapi/dpl/pstl/unseq_backend_simd.h +++ b/include/oneapi/dpl/pstl/unseq_backend_simd.h @@ -804,10 +804,11 @@ __simd_find_first_of(_ForwardIterator1 __first, _ForwardIterator1 __last, _Forwa // Otherwise, vice versa. if (__n1 < __n2) { - auto __u_pred = - [__pred, __first](auto&& __val) mutable { return __pred(std::forward(__val), *__first); }; for (; __first != __last; ++__first) { + auto __u_pred = [__first, &__pred](auto&& __val) { + return __pred(*__first, std::forward(__val)); + }; if (__unseq_backend::__simd_or(__s_first, __n2, __u_pred)) { return __first; @@ -816,17 +817,20 @@ __simd_find_first_of(_ForwardIterator1 __first, _ForwardIterator1 __last, _Forwa } else { - for (; __s_first != __s_last; ++__s_first) + // The result is the earliest element of the first range matching any element of the second one, so + // every element of the second range has to be tried and the earliest match kept. Only the part of + // the first range before the best match found so far can improve it, and __simd_first returns the + // end of the range it is given when it finds nothing, which is that same best match, so its + // result can be taken as is. + _ForwardIterator1 __res = __last; + for (; __s_first != __s_last && __res != __first; ++__s_first) { - const auto __result = __unseq_backend::__simd_first( - __first, _DifferencType(0), __n1, [__s_first, &__pred](_ForwardIterator1 __it, _DifferencType __i) { - return __pred(__it[__i], *__s_first); - }); - if (__result != __last) - { - return __result; - } + __res = __unseq_backend::__simd_first(__first, _DifferencType(0), __res - __first, + [__s_first, &__pred](_ForwardIterator1 __it, _DifferencType __i) { + return __pred(__it[__i], *__s_first); + }); } + return __res; } return __last; } diff --git a/include/oneapi/dpl/pstl/utils.h b/include/oneapi/dpl/pstl/utils.h index f078c681202..e9745ea954c 100644 --- a/include/oneapi/dpl/pstl/utils.h +++ b/include/oneapi/dpl/pstl/utils.h @@ -165,11 +165,13 @@ class __pstl_assign public: // rvalue reference used for output parameter to allow assignment of std::tuple of references. // The output is the second argument because the output range is passed to the algorithm as the second range. + // The input is forwarded rather than taken by a const reference, because std::indirectly_copyable only asks for + // an assignment from a non-const lvalue of the input element, which a const parameter would rule out. template void - operator()(const _Xp& __x, _Yp&& __y) const + operator()(_Xp&& __x, _Yp&& __y) const { - ::std::forward<_Yp>(__y) = __x; + std::forward<_Yp>(__y) = std::forward<_Xp>(__x); } }; @@ -481,6 +483,57 @@ struct __replace_if_fun const _T __new_value; }; +template +using __mutable_lvalue_t = std::remove_const_t>&; + +template +constexpr __mutable_lvalue_t<_T> +__as_mutable_lvalue(_T&& __x) noexcept +{ + return const_cast<__mutable_lvalue_t<_T>>(__x); +} + +template +class __relax_const_pred +{ + mutable _Pred _M_pred; + + public: + explicit __relax_const_pred(_Pred __pred) : _M_pred(std::move(__pred)) {} + + template ...>, int> = 0> + std::invoke_result_t<_Pred&, __mutable_lvalue_t<_Args>...> + operator()(_Args&&... __args) const + { + return std::invoke(_M_pred, __as_mutable_lvalue(std::forward<_Args>(__args))...); + } +}; + +template +inline constexpr bool __pred_wants_mutable_args_v = + sizeof...(_T) > 0 && !std::is_invocable_v<_Pred&, const _T&...> && std::is_invocable_v<_Pred&, _T&...>; + +template +constexpr auto +__get_relax_non_const_pred(_Pred&& __pred) +{ + using _PredType = std::remove_reference_t<_Pred>; + + if constexpr (__pred_wants_mutable_args_v<_PredType, _T...>) + return __relax_const_pred<_PredType>{std::forward<_Pred>(__pred)}; + else + return std::forward<_Pred>(__pred); +} + +// A comparator is just a binary predicate here; the only reason for a separate name is that a parameter pack +// cannot have a default argument, so the homogeneous comparison comp(_T&, _T&) is spelled with one type. +template +constexpr auto +__get_relax_non_const_comp(_Comp&& __comp) +{ + return __get_relax_non_const_pred<_T, _U>(std::forward<_Comp>(__comp)); +} + //! Like ::std::next, but with specialization for dpcpp case template _Iter diff --git a/include/oneapi/dpl/pstl/utils_ranges.h b/include/oneapi/dpl/pstl/utils_ranges.h index 9400b036010..e2fc2bc8790 100644 --- a/include/oneapi/dpl/pstl/utils_ranges.h +++ b/include/oneapi/dpl/pstl/utils_ranges.h @@ -200,8 +200,10 @@ get_value_type(...) template using __value_t = decltype(oneapi::dpl::__internal::get_value_type<_R>(0)); +//The projection is applied to a non-const lvalue, exactly like 'std::projected_value_t' does it: the standard +//indirect callable concepts require a projection to be invocable with 'iter_value_t&', not with an rvalue. template -using __projected_value_t = std::remove_cv_t>>>; +using __projected_value_t = std::remove_cv_t&>>>; #if _ONEDPL_CPP20_RANGES_PRESENT //The following '__range_size' type trait should be used in only the context with std::common_type diff --git a/test/parallel_api/algorithm/alg.nonmodifying/find_first_of.pass.cpp b/test/parallel_api/algorithm/alg.nonmodifying/find_first_of.pass.cpp index 4cb58200ffb..592167c45c3 100644 --- a/test/parallel_api/algorithm/alg.nonmodifying/find_first_of.pass.cpp +++ b/test/parallel_api/algorithm/alg.nonmodifying/find_first_of.pass.cpp @@ -101,6 +101,105 @@ test(Predicate pred) in1.begin() + max_n1 / 10, pred); } +// The data of test() above pins none of the answers: all the elements of its first range are equal, so +// whenever a match exists it is at the very first of them, and all of its predicates are symmetric. An +// implementation which compares the first element of the first range only, or which passes the two +// elements to the predicate in the opposite order, answers exactly like a correct one on every case +// test() generates. The cases below drop both properties: the first range holds distinct values, its +// matching elements are away from its front, and one of the predicates tells the two argument orders +// apart. +// +// They also compare the position of the answer instead of the iterator, because EXPECT_EQ cannot print +// an iterator: a failure of the two functors above reports only "expected (unable to log value)", which +// says nothing about which element was returned. +template +struct test_find_first_of_position +{ + template + void + operator()(ExecutionPolicy&& exec, Iterator1 b, Iterator1 e, Iterator2 bsub, Iterator2 esub) + { + using namespace std; + // distance, not a subtraction: invoke_on_all_policies instantiates this with the forward and + // bidirectional iterator wrappers of the test suite as well + const auto expected = distance(b, find_first_of(b, e, bsub, esub)); + const auto actual = distance(b, find_first_of(std::forward(exec), b, e, bsub, esub)); + EXPECT_EQ(expected, actual, "wrong position from find_first_of"); + } +}; + +template +struct test_find_first_of_position_predicate +{ + template + void + operator()(ExecutionPolicy&& exec, Iterator1 b, Iterator1 e, Iterator2 bsub, Iterator2 esub, Predicate pred) + { + using namespace std; + const auto expected = distance(b, find_first_of(b, e, bsub, esub, pred)); + const auto actual = distance(b, find_first_of(std::forward(exec), b, e, bsub, esub, pred)); + EXPECT_EQ(expected, actual, "wrong position from find_first_of with a predicate"); + } +}; + +// A vectorized find_first_of walks whichever of the two ranges is the shorter one in its inner loop, so +// it has one such loop per order of the two lengths. This is the order with the first range being the +// shorter one, the one the SIMD brick serves with its per element scan of the second range. +template +void +test_match_away_from_the_front() +{ + // is_successor(x, y) and is_successor(y, x) hold for different elements of the first range + auto is_successor = [](const T x, const T y) { return x == T(y + 1); }; + + const ::std::size_t sizes[] = {2, 3, 7, 16, 41, 130}; + for (const auto n1 : sizes) + { + // distinct values, and T(0), which the second range is filled with, is not one of them + Sequence in1(n1, [](::std::size_t i) { return T(10 + i); }); + + // The second range is the longer one. Its two matching elements are planted in the order + // opposite to the one their counterparts have in the first range, so the answer is the earliest + // match in the first range rather than the first match met while walking the second one. + Sequence in2(n1 + 3, [](::std::size_t) { return T(0); }); + in2[0] = T(10 + n1 - 1); + in2[1] = T(10 + n1 / 2); + invoke_on_all_policies<8>()(test_find_first_of_position(), in1.begin(), in1.end(), in2.begin(), in2.end()); + + // The same lengths with a predicate which is not symmetric: is_successor(*it1, *it2) holds for + // the last element of the first range, while the swapped is_successor(*it2, *it1) would hold for + // the third element from its end. + in2[0] = T(0); + in2[1] = T(10 + n1 - 2); + invoke_on_all_policies<9>()(test_find_first_of_position_predicate(), in1.begin(), in1.end(), in2.begin(), + in2.end(), is_successor); + } +} + +// The other order of the two lengths, the first range being the longer one, is served by the branch which +// walks the *second* range in its outer loop. Keeping the earliest match over all the elements of that +// range is a requirement of its own, separate from the argument order and from the per element binding of +// the branch above: an implementation which returns the first match it meets answers 6 for n1 = 7, where +// in1 = {10, ..., 16}, in2 = {16, 13, 0} and find_first_of answers 3. +template +void +test_match_away_from_the_front_long_first_range() +{ + const ::std::size_t sizes[] = {2, 3, 7, 16, 41, 130}; + for (const auto n1 : sizes) + { + Sequence in1(n1, [](::std::size_t i) { return T(10 + i); }); + + // The same two matches as above, planted in the same reversed order, with the second range being + // the shorter one this time. + Sequence in2(n1 / 2 + 1, [](::std::size_t) { return T(0); }); + in2[0] = T(10 + n1 - 1); + if (in2.size() > 1) + in2[1] = T(10 + n1 / 2); + invoke_on_all_policies<10>()(test_find_first_of_position(), in1.begin(), in1.end(), in2.begin(), in2.end()); + } +} + template struct test_non_const { @@ -121,6 +220,9 @@ main() #endif test([](const float64_t x, const float64_t y) { return x * x == y * y; }); + test_match_away_from_the_front(); + test_match_away_from_the_front_long_first_range(); + test_algo_basic_double(run_for_rnd_fw>()); return done(); 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..cb971f6549c --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_cross.pass.cpp @@ -0,0 +1,325 @@ +// -*- 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() +{ + try + { +#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, includes, contains_subrange, starts_with, + // ends_with and lexicographical_compare, first with const callables, then with callables taking + // their arguments by non-const reference, and finally without a callable at all, i.e. with the + // default std::ranges::equal_to and std::ranges::less. + + // 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"); + + 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"); + + // contains_subrange, starts_with and ends_with are constrained by std::indirectly_comparable just + // like search, so they see the user predicate only. The two ranges hold the very same sequence, so + // the second one is a subrange of the first one and is both its prefix and its suffix. + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::contains_subrange(std::forward(policy), view1, view2, cross_pred{}); + }, + [](auto&&, auto&&, bool res) { return res; }, "contains_subrange"); + + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::starts_with(std::forward(policy), view1, view2, cross_pred{}); + }, + [](auto&&, auto&&, bool res) { return res; }, "starts_with"); + + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::ends_with(std::forward(policy), view1, view2, cross_pred{}); + }, + [](auto&&, auto&&, bool res) { return res; }, "ends_with"); + + // lexicographical_compare orders the two ranges, so it needs a comparator accepting the two element + // types in all four combinations, see cross_comp. The two ranges are equal, so neither is less than + // the other. + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::lexicographical_compare(std::forward(policy), view1, view2, + cross_comp{}); + }, + [](auto&&, auto&&, bool res) { return !res; }, "lexicographical_compare"); + + //---------------------------------------------------------------------------------------------- + // 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"); + + 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"); + + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::contains_subrange(std::forward(policy), view1, view2, + cross_pred_mut{}); + }, + [](auto&&, auto&&, bool res) { return res; }, "contains_subrange, non-const callable"); + + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::starts_with(std::forward(policy), view1, view2, cross_pred_mut{}); + }, + [](auto&&, auto&&, bool res) { return res; }, "starts_with, non-const callable"); + + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::ends_with(std::forward(policy), view1, view2, cross_pred_mut{}); + }, + [](auto&&, auto&&, bool res) { return res; }, "ends_with, non-const callable"); + + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::lexicographical_compare(std::forward(policy), view1, view2, + cross_comp_mut{}); + }, + [](auto&&, auto&&, bool res) { return !res; }, "lexicographical_compare, non-const comparator"); + + //---------------------------------------------------------------------------------------------- + // The same algorithms called without a callable at all, i.e. with the default + // std::ranges::equal_to and std::ranges::less. There is no user callable left to relate the two + // element types, so both ranges hold the very same archetype and the comparison comes from it: + // equality_archetype has operator== only, ordered_archetype is std::totally_ordered. + //---------------------------------------------------------------------------------------------- + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::equal(std::forward(policy), view1, view2); + }, + [](auto&&, auto&&, bool res) { return res; }, "equal, default predicate"); + + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::mismatch(std::forward(policy), view1, view2); + }, + [](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, default predicate"); + + // 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); + }, + [](auto&& view1, auto&& view2, auto res) { + return std::ranges::begin(res) == std::ranges::begin(view1) && + std::ranges::size(res) == std::ranges::size(view2); + }, + "search, default predicate"); + + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::find_end(std::forward(policy), view1, view2); + }, + [](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, default predicate"); + + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::find_first_of(std::forward(policy), view1, view2); + }, + [](auto&& view1, auto&&, auto res) { return res == std::ranges::begin(view1); }, + "find_first_of, default predicate"); + + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::contains_subrange(std::forward(policy), view1, view2); + }, + [](auto&&, auto&&, bool res) { return res; }, "contains_subrange, default predicate"); + + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::starts_with(std::forward(policy), view1, view2); + }, + [](auto&&, auto&&, bool res) { return res; }, "starts_with, default predicate"); + + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::ends_with(std::forward(policy), view1, view2); + }, + [](auto&&, auto&&, bool res) { return res; }, "ends_with, default predicate"); + + // Both ranges hold the very same ascending sequence, so the second one is included in the first one + // and neither of them is lexicographically less than the other. + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::includes(std::forward(policy), view1, view2); + }, + [](auto&&, auto&&, bool res) { return res; }, "includes, default comparator"); + + run_algo2_all_policies( + [](auto&& policy, auto&& view1, auto&& view2) { + return dpl_ranges::lexicographical_compare(std::forward(policy), view1, view2); + }, + [](auto&&, auto&&, bool res) { return !res; }, "lexicographical_compare, default comparator"); + + // The two-range read pattern over plain_archetype_view, i.e. over ranges without the members + // std::ranges::view_interface provides; see the plain range section of the read test for what this + // proves. equal is the representative shape here: two ranges walked in lockstep, with the number of + // elements coming from the sized sentinel of each of them and not from a size() member. + run_algo2_plain_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, plain ranges"); + +#endif //_ENABLE_STD_RANGES_TESTING + } + catch (const std::exception& exc) + { + std::cerr << "Exception occurred in main() of " << __FILE__; + if (exc.what()) + std::cerr << ": " << exc.what(); + std::cerr << std::endl; + + return EXIT_FAILURE; + } + catch (...) + { + std::cerr << "Unknown exception occurred in main() of " << __FILE__ << std::endl; + + return EXIT_FAILURE; + } + + 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 new file mode 100644 index 00000000000..892f737cd1a --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_merge.pass.cpp @@ -0,0 +1,283 @@ +// -*- 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 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, then + // with comparators taking their arguments by non-const reference, and finally, for merge, without a + // comparator at all, i.e. with the default std::ranges::less. + + // Both inputs hold the very same sorted sequence 0, 1, 2, ... + 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::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,250,259 - the __uninitialized_copy_or_discard calls, which + // end up in memory_impl.h:96 (scalar) and memory_impl.h:111 (vectorized), both a placement new of + // the output value type from the input element; + // - 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. + { + auto call = [](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{}); + // 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 check = [](auto&&, auto&&, auto res) { return res; }; + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HOST + run_algo2_host_policies(call, check, "set_union"); +#endif +#if TEST_DPCPP_BACKEND_PRESENT && !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HETERO + run_algo2_hetero_policies(call, check, "set_union"); +#endif + } + + { + auto call = [](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{}); + // The two inputs are equal, so the difference is empty. + return res.out == std::ranges::begin(out_view); + }; + auto check = [](auto&&, auto&&, auto res) { return res; }; + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HOST + run_algo2_host_policies(call, check, "set_difference"); +#endif +#if TEST_DPCPP_BACKEND_PRESENT && !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HETERO + run_algo2_hetero_policies(call, check, "set_difference"); +#endif + } + + // set_intersection and set_symmetric_difference construct the output element the very same way, + // see the note above set_union. + { + auto call = [](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_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 check = [](auto&&, auto&&, auto res) { return res; }; + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_INTERSECTION_HOST + run_algo2_host_policies(call, check, "set_intersection"); +#endif +#if TEST_DPCPP_BACKEND_PRESENT && !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_INTERSECTION_HETERO + run_algo2_hetero_policies(call, check, "set_intersection"); +#endif + } + + { + auto call = [](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_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 check = [](auto&&, auto&&, auto res) { return res; }; + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_SYMMETRIC_DIFFERENCE_HOST + run_algo2_host_policies(call, check, "set_symmetric_difference"); +#endif +#if TEST_DPCPP_BACKEND_PRESENT && !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_SYMMETRIC_DIFFERENCE_HETERO + run_algo2_hetero_policies(call, check, + "set_symmetric_difference"); +#endif + } + + //---------------------------------------------------------------------------------------------- + // 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 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_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"); + + // 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. + { + auto call = [](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 check = [](auto&&, auto&&, auto res) { return res; }; + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HOST + run_algo2_host_policies(call, check, + "set_union, non-const comparator"); +#endif +#if TEST_DPCPP_BACKEND_PRESENT && !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION_HETERO + run_algo2_hetero_policies(call, check, + "set_union, non-const comparator"); +#endif + } + + { + auto call = [](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 check = [](auto&&, auto&&, auto res) { return res; }; + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HOST + run_algo2_host_policies(call, check, + "set_difference, non-const comparator"); +#endif +#if TEST_DPCPP_BACKEND_PRESENT && !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_DIFFERENCE_HETERO + run_algo2_hetero_policies( + call, check, "set_difference, non-const comparator"); +#endif + } + + { + auto call = [](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_intersection(std::forward(policy), view1, view2, out_view, + merge_comp_mut{}); + // 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 check = [](auto&&, auto&&, auto res) { return res; }; + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_INTERSECTION_HOST + run_algo2_host_policies(call, check, + "set_intersection, non-const comparator"); +#endif +#if TEST_DPCPP_BACKEND_PRESENT && !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_INTERSECTION_HETERO + run_algo2_hetero_policies( + call, check, "set_intersection, non-const comparator"); +#endif + } + + { + auto call = [](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_symmetric_difference(std::forward(policy), view1, view2, + out_view, merge_comp_mut{}); + // The two inputs are equal, so the symmetric difference is empty. + return res.out == std::ranges::begin(out_view); + }; + auto check = [](auto&&, auto&&, auto res) { return res; }; + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_SYMMETRIC_DIFFERENCE_HOST + run_algo2_host_policies( + call, check, "set_symmetric_difference, non-const comparator"); +#endif +#if TEST_DPCPP_BACKEND_PRESENT && !_TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_SYMMETRIC_DIFFERENCE_HETERO + run_algo2_hetero_policies( + call, check, "set_symmetric_difference, non-const comparator"); +#endif + } + + //---------------------------------------------------------------------------------------------- + // merge called without a comparator at all, i.e. with the default std::ranges::less, which makes + // std::mergeable ask the input element type itself for std::totally_ordered. The set operations + // share that requires-clause, but their calls are disabled for every policy above, so a default + // comparator would not add a single compiled branch for them. + //---------------------------------------------------------------------------------------------- + // Both inputs hold the very same sorted sequence 0, 1, 2, ... + 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::merge(std::forward(policy), view1, view2, out_view); + 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, default comparator"); + +#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 new file mode 100644 index 00000000000..760a195103e --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_permute.pass.cpp @@ -0,0 +1,498 @@ +// -*- 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() +{ + try + { +#if _ENABLE_STD_RANGES_TESTING + using namespace test_std_ranges; + 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, rotate, shift_left, shift_right, remove_if, unique, partition, + // stable_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, and the third one without a + // callable at all, i.e. with the default std::ranges::less and std::ranges::equal_to. + + //---------------------------------------------------------------------------------------------- + // Const callables, and the algorithms which take no user callable at all. + //---------------------------------------------------------------------------------------------- + // 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"); + + // rotate, shift_left and shift_right take no user callable at all: std::permutable, i.e. moving and + // swapping through the iterator, is everything they are allowed to ask for. The range is 0, 1, 2, + // ... so after rotating it by ten the tenth element is at the front and the old first one is ten + // positions from the end. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::rotate(std::forward(policy), view, std::ranges::begin(view) + 10); + }, + [](auto&& view, auto) { + return std::ranges::begin(view)[0].val == 10 && + std::ranges::begin(view)[std::ranges::size(view) - 10].val == 0; + }, + "rotate"); + + // Shifting left by ten moves the eleventh element to the front. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::shift_left(std::forward(policy), view, 10); + }, + [](auto&& view, auto) { return std::ranges::begin(view)[0].val == 10; }, "shift_left"); + + // Shifting right by ten moves the first element ten positions to the right. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::shift_right(std::forward(policy), view, 10); + }, + [](auto&& view, auto) { return std::ranges::begin(view)[10].val == 0; }, "shift_right"); + + // 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"); + + // 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"); + + // partition returns the tail of the elements which do not satisfy the predicate. + run_algo_all_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"); + + // stable_partition is constrained exactly like partition, i.e. by std::permutable, so it may only + // move and swap the elements through the iterator; the returned subrange is again the tail of the + // elements which do not satisfy the predicate. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::stable_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; + }, + "stable_partition"); + + // 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_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"); + + 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"); + + 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"); + + // The three algorithms whose non-const comparator counterparts below are broken: with a comparator + // accepting a const lvalue they are expected to compile everywhere, which is what pins the defect to + // the const-ness of the argument and not to the algorithm itself. + // 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::partial_sort(std::forward(policy), view, std::ranges::begin(view) + 10, + permutable_comp{}); + }, + [](auto&& view, auto) { + return std::ranges::begin(view)[0].val == 0 && std::ranges::begin(view)[9].val == 9; + }, + "partial_sort"); + + // partial_sort_copy writes the sorted prefix into a range of its own, so its input element only has + // to be assignable into the output one and comparable with it, see family 14. The output range is + // deliberately shorter than the input, so ten elements are copied and sorted, and the comparator + // orders descending, so those are the ten largest values of the ascending input. + // + // KSATODO: the parallel host pattern of partial_sort_copy does not compile, and unlike the three + // algorithms above the reason has nothing to do with the const-ness of the comparator argument: + // - algorithm_impl.h:2707 - the branch taken when the output range is shorter than the input one + // sorts in a temporary buffer of the output value type and fills it with + // ::new (__k) _T2(*__it), i.e. it constructs the output element from the input one, while + // std::indirectly_copyable only ever grants the assignment *__out = *__in. Assigning into the + // already initialized buffer element is not an option either, because std::sortable does not + // ask the output element for default construction; copying the input into the output range and + // sorting it in place, as the other branch at algorithm_impl.h:2686 does, needs no construction + // at all. + // The serial pattern (algorithm_ranges_impl.h:521) forwards to std::ranges::partial_sort_copy and + // is conforming by construction, so seq and unseq would compile; the gap macro covers the host side + // as a whole and switches them off as well. + // + // KSATODO: the device path assigns the output element from a const lvalue of the input one, which + // std::indirectly_copyable does not ask for, exactly like rotate_copy: + // - hetero/algorithm_impl_hetero.h:1512,1541,1556 - the three initial copies go through + // __pattern_hetero_walk2 with the input read through an access_mode::read accessor, so + // __brick_copy (hetero/algorithm_impl_hetero.h:397) assigns from a const _Tp&. + // Requesting read_write access for the input of those walks is enough to fix it. + { + auto call = [](auto&& policy, auto&& view) { + using elem_t = std::ranges::range_value_t>; + auto out_storage = make_out_storage(policy, 10); + auto out_view = out_storage.view(); + auto res = dpl_ranges::partial_sort_copy(std::forward(policy), view, out_view, + psort_copy_comp{}); + return std::ranges::begin(out_view)[0].val == (int)archetype_test_size - 1 && + std::ranges::begin(out_view)[9].val == (int)archetype_test_size - 10 && + res.out == std::ranges::end(out_view); + }; + auto check = [](auto&&, auto res) { return res; }; + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_PARTIAL_SORT_COPY_HOST + run_algo_host_policies(call, check, "partial_sort_copy"); +#endif +#if TEST_DPCPP_BACKEND_PRESENT && !_TEST_CPP20_RANGES_BROKEN_REQUIRES_PARTIAL_SORT_COPY_HETERO + run_algo_hetero_policies(call, check, "partial_sort_copy"); +#endif + } + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::nth_element(std::forward(policy), view, std::ranges::begin(view) + 10, + permutable_comp{}); + }, + [](auto&& view, auto) { return std::ranges::begin(view)[10].val == 10; }, "nth_element"); + + 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{}); + }, + [](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"); + + //---------------------------------------------------------------------------------------------- + // 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. + 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"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::stable_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; + }, + "stable_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"); + + 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; + }, + "sort, non-const comparator"); + + run_algo_all_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"); + + run_algo_all_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"); + + // partial_sort_copy projects its input range and its output range with two distinct callables, so + // both of them are passed here by non-const reference on top of the comparator, see family 14. The + // two gaps of the const case above are independent of the callables and break the very same + // policies here; the parallel host pattern additionally inherits the const comparator argument of + // the parallel merge sort, see the note above partial_sort. + // + // KSATODO: the parallel host and the device patterns drop _Proj1 altogether + // (algorithm_ranges_impl.h:595,608 and hetero/algorithm_ranges_impl_hetero.h:1664,1675 build + // __binary_op<_Comp, _Proj2, _Proj2>), so they project the input range with the projection of the + // output range and never call the comparator with the mixed argument pair the requires-clause asks + // for. That is a wrong result and not a compilation failure, so it stays invisible here: both + // projections below return the element itself. The serial pattern forwards the two projections to + // std::ranges::partial_sort_copy and is correct. std_ranges_partial_sort_copy.pass.cpp sees the + // wrong result itself, see _TEST_CPP20_RANGES_BROKEN_WRONG_RESULT_PARTIAL_SORT_COPY_PROJ1_HOST. + { + auto call = [](auto&& policy, auto&& view) { + using elem_t = std::ranges::range_value_t>; + auto out_storage = make_out_storage(policy, 10); + auto out_view = out_storage.view(); + auto res = dpl_ranges::partial_sort_copy(std::forward(policy), view, out_view, + psort_copy_comp_mut{}, psort_copy_in_proj_mut{}, + psort_copy_out_proj_mut{}); + return std::ranges::begin(out_view)[0].val == (int)archetype_test_size - 1 && + std::ranges::begin(out_view)[9].val == (int)archetype_test_size - 10 && + res.out == std::ranges::end(out_view); + }; + auto check = [](auto&&, auto res) { return res; }; + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_PARTIAL_SORT_COPY_HOST + run_algo_host_policies(call, check, "partial_sort_copy, non-const callables"); +#endif +#if TEST_DPCPP_BACKEND_PRESENT && !_TEST_CPP20_RANGES_BROKEN_REQUIRES_PARTIAL_SORT_COPY_HETERO + run_algo_hetero_policies(call, check, "partial_sort_copy, non-const callables"); +#endif + } + + run_algo_all_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"); + + // 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. + 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&& 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"); + + //---------------------------------------------------------------------------------------------- + // The same algorithms called without a comparator at all, i.e. with the default + // std::ranges::less: std::sortable then asks the element type itself for std::totally_ordered, + // which is what permutable_ordered_archetype provides and nothing else. These calls are the only + // ones which instantiate the default comparator path of the implementation, e.g. + // __is_comp_ascending on the device side. + // + // The parallel host merge sort hands a const lvalue to the comparator, which is what breaks the + // non-const comparator cases above; std::ranges::less accepts one, so par and par_unseq compile + // here and all four host policies run. + //---------------------------------------------------------------------------------------------- + run_algo_all_policies( + [](auto&& policy, auto&& view) { return dpl_ranges::sort(std::forward(policy), view); }, + [](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, default comparator"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::stable_sort(std::forward(policy), view); + }, + [](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, default comparator"); + + // 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::partial_sort(std::forward(policy), view, + std::ranges::begin(view) + 10); + }, + [](auto&& view, auto) { + return std::ranges::begin(view)[0].val == 0 && std::ranges::begin(view)[9].val == 9; + }, + "partial_sort, default comparator"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::nth_element(std::forward(policy), view, + std::ranges::begin(view) + 10); + }, + [](auto&& view, auto) { return std::ranges::begin(view)[10].val == 10; }, + "nth_element, default 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); + }, + [](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, default comparator"); + + // unique defaults its equivalence relation to std::ranges::equal_to, which needs the equality of + // the element type and no ordering at all. 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); }, + [](auto&& view, auto res) { return std::ranges::size(res) == 0; }, "unique, default predicate"); + + //---------------------------------------------------------------------------------------------- + // The sorting algorithms with a projection which maps the element to an integer key, see family + // 15. std::sortable then asks the element for nothing but moving and swapping, and these are the + // only calls of the whole suite which reach the radix sort of the device backend: it is selected + // by the projected key type and never by the element type, so an archetype element can only get + // there through such a projection. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::sort(std::forward(policy), view, std::ranges::less{}, + permutable_proj_key{}); + }, + [](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, projected key"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::stable_sort(std::forward(policy), view, std::ranges::less{}, + permutable_proj_key{}); + }, + [](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, projected key"); + + // The permuting pattern over plain_archetype_view, i.e. over a range without the members + // std::ranges::view_interface provides; see the plain range section of the read test for what this + // proves. sort is the representative shape here, because it is the pattern which splits the range + // into sub ranges of its own and therefore has the most reasons to ask the user range for its size. + run_algo_plain_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, plain range"); + +#endif //_ENABLE_STD_RANGES_TESTING + } + catch (const std::exception& exc) + { + std::cerr << "Exception occurred in main() of " << __FILE__; + if (exc.what()) + std::cerr << ": " << exc.what(); + std::cerr << std::endl; + + return EXIT_FAILURE; + } + catch (...) + { + std::cerr << "Unknown exception occurred in main() of " << __FILE__ << std::endl; + + return EXIT_FAILURE; + } + + 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 new file mode 100644 index 00000000000..699810f54cc --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_read.pass.cpp @@ -0,0 +1,446 @@ +// -*- 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 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, + // is_heap, is_heap_until and adjacent_find, first with const callables, then with callables taking + // non-const references, and finally without a callable at all, i.e. with the default + // std::ranges::less and std::ranges::equal_to. + + // 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::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"); + + 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"); + + // The last element whose value is divisible by three, and the last one whose value is not. +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_IF + run_algo_all_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 + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_IF_NOT + run_algo_all_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 + + 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::none_of(std::forward(policy), view, read_unary_pred{}); + }, + [](auto&&, bool res) { return !res; }, "none_of"); + + // The predicate holds for 0, fails for 1 and holds again for 3, so the range is not partitioned. + 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"); + + 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::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"); + + 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"); + + // 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_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"); + + 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"); + + // 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{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, + "is_sorted_until"); + + // is_heap and is_heap_until are constrained exactly like is_sorted, i.e. by + // std::indirect_strict_weak_order on the projected iterator, so the element type stays + // non-copyable here as well. The range is ascending, so it is not a max-heap and the heap property + // already breaks at the first child. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_heap(std::forward(policy), view, read_comp{}); + }, + [](auto&&, bool res) { return !res; }, "is_heap"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_heap_until(std::forward(policy), view, read_comp{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + 1; }, "is_heap_until"); + + 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 same algorithms with callables taking their arguments by non-const reference. + //---------------------------------------------------------------------------------------------- + run_algo_all_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"); + + run_algo_all_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"); + + run_algo_all_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"); + + // The last element whose value is divisible by three, and the last one whose value is not. +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_IF + run_algo_all_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 + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_IF_NOT + run_algo_all_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 + + run_algo_all_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"); + + // 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::all_of(std::forward(policy), view, read_unary_pred_mut{}); + }, + [](auto&&, bool res) { return !res; }, "all_of, non-const callable"); + + run_algo_all_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"); + + run_algo_all_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"); + + run_algo_all_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"); + + // 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{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "find_if, non-const projection"); + + run_algo_all_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"); + + run_algo_all_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"); + + run_algo_all_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"); + + // 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&& 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::is_heap(std::forward(policy), view, read_comp_mut{}); + }, + [](auto&&, bool res) { return !res; }, "is_heap, non-const comparator"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_heap_until(std::forward(policy), view, read_comp_mut{}); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + 1; }, + "is_heap_until, non-const comparator"); + + run_algo_all_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"); + + 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"); + + 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"); + + //---------------------------------------------------------------------------------------------- + // The same algorithms called without a callable at all, i.e. with the default std::ranges::less + // and std::ranges::equal_to. The ordering and the equality then have to come from the element + // type, so these calls use ordered_archetype and equality_archetype: they are the only ones which + // instantiate the default comparator path of the implementation, e.g. __is_comp_ascending on the + // device side. + //---------------------------------------------------------------------------------------------- + run_algo_all_policies( + [](auto&& policy, auto&& view) { return dpl_ranges::is_sorted(std::forward(policy), view); }, + [](auto&&, bool res) { return res; }, "is_sorted, default comparator"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_sorted_until(std::forward(policy), view); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, + "is_sorted_until, default comparator"); + + // The range is ascending, so it is not a max-heap and the heap property breaks at the first child. + run_algo_all_policies( + [](auto&& policy, auto&& view) { return dpl_ranges::is_heap(std::forward(policy), view); }, + [](auto&&, bool res) { return !res; }, "is_heap, default comparator"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::is_heap_until(std::forward(policy), view); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + 1; }, + "is_heap_until, default comparator"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::min_element(std::forward(policy), view); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view); }, "min_element, default comparator"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::max_element(std::forward(policy), view); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view) - 1; }, + "max_element, default comparator"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::minmax_element(std::forward(policy), view); + }, + [](auto&& view, auto res) { + return res.min == std::ranges::begin(view) && + res.max == std::ranges::begin(view) + std::ranges::size(view) - 1; + }, + "minmax_element, default comparator"); + + // adjacent_find defaults its predicate to std::ranges::equal_to, which needs the equality of the + // element type and nothing else; all the values differ, so the scan reaches the end of the range. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::adjacent_find(std::forward(policy), view); + }, + [](auto&& view, auto res) { return res == std::ranges::begin(view) + std::ranges::size(view); }, + "adjacent_find, default predicate"); + + //---------------------------------------------------------------------------------------------- + // The same algorithms over plain_archetype_view, i.e. over a range which does not derive from + // std::ranges::view_interface and therefore has neither size(), operator[], empty(), front() nor + // back(). No requires-clause of any algorithm asks a range for those members: random_access_range + // and sized_range are satisfied through begin(), end() and the sized sentinel alone, so an + // implementation which reaches for a member of the user range instead of going through + // std::ranges::begin / end / size does not compile here. + // + // Only one call per pattern shape is run this way, here and in the cross, the permute and the write + // test: how the user range is accessed is a property of the dispatch and of the pattern and not of + // the individual algorithm. The values are the ones of the calls above, so what is new is the range + // and nothing else. + //---------------------------------------------------------------------------------------------- + run_algo_plain_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, plain range"); + + run_algo_plain_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, plain range"); + +#endif //_ENABLE_STD_RANGES_TESTING + + return TestUtils::done(_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..16a8512446f --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_storable.pass.cpp @@ -0,0 +1,101 @@ +// -*- 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, then with comparators taking their arguments by non-const reference, and + // finally without a comparator at all, i.e. with the default std::ranges::less. + + 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"); + + //---------------------------------------------------------------------------------------------- + // The same algorithms called without a comparator at all, i.e. with the default + // std::ranges::less, which requires the element type itself to be std::totally_ordered. + //---------------------------------------------------------------------------------------------- + run_algo_all_policies( + [](auto&& policy, auto&& view) { return dpl_ranges::min(std::forward(policy), view); }, + [](auto&&, auto res) { return res.val == 0; }, "min, default comparator"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { return dpl_ranges::max(std::forward(policy), view); }, + [](auto&&, auto res) { return res.val == (int)archetype_test_size - 1; }, "max, default comparator"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { return dpl_ranges::minmax(std::forward(policy), view); }, + [](auto&&, auto&& res) { return res.min.val == 0 && res.max.val == (int)archetype_test_size - 1; }, + "minmax, default 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 new file mode 100644 index 00000000000..57e6ef2257f --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_test.h @@ -0,0 +1,320 @@ +// -*- 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; }; + +// The content of the second range of a two-range algorithm when the check has to tell the two ranges +// apart: 1000, 1001, 1002, ... With it the values of the two ranges are disjoint, so a check of a copy, +// a move or a swap cannot pass just because the position of the second range already held the value +// which is expected there. See run_algo2_offset. +inline constexpr auto archetype_offset_index_factory = [](std::size_t __i) { + return (int)(__i + archetype_test_size); +}; + +#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()); +} + +// The same as run_algo2, with the second range filled with the offset values instead of the ascending +// ones, see archetype_offset_index_factory. The algorithms which write the element of one range into +// the other one (copy, move, swap_ranges) are run this way, because with one and the same fill in both +// ranges the value the check reads is the one the position held from the start anyway. +template +void +run_algo2_offset(_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_offset_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()); +} + +// The same two runners over plain_archetype_view instead of archetype_view: the range then provides +// nothing but begin() and end(), because it does not derive from std::ranges::view_interface, so an +// implementation which calls size(), operator[] or any other member of it does not compile. Only a few +// representative algorithms are run this way - the range shape is a property of the implementation of +// the dispatch and not of the individual algorithm, so one call per pattern shape is enough. +template +void +run_algo_plain(_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.template view(); + + auto __res = __algo(std::forward<_Policy>(__policy), __view); + + EXPECT_TRUE(__checker(__view, __res), (std::string("wrong result from ") + __algo_name).c_str()); +} + +template +void +run_algo2_plain(_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.template view(); + auto __view2 = __storage2.template 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 every host policy. Each of them reaches its own implementation +// branch: the vectorized ones (unseq, par_unseq) go through the SIMD bricks and the parallel ones +// (par, par_unseq) through the parallel patterns, so all four are needed to have every branch +// compiled. A call site whose host side is broken wraps this call in an #if on the _HOST gap macro of +// the algorithm, which switches all four off together, whichever of the branches is the broken one. +// +// Calling this and not run_algo_all_policies also covers the archetypes which are meaningful for the +// host policies only: a value argument which is neither copyable nor movable cannot be passed into a +// device kernel, and the host implementation is required to refer to the value of the user. +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 every host policy, 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. The ids only have to be unique inside one translation +// unit, and every test file is its own executable, so each file numbers its calls from zero. +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. +// +// A call site which has to skip a policy because of a known implementation gap does not use this +// helper: it spells out the policies it does run, so that the #if on the gap macro covers exactly the +// broken part and every other branch of the implementation stays compiled. +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<_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) +{ + run_algo2_host_policies<_Elem1, _Elem2>(__algo, __checker, __algo_name); +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_hetero_policies<_Elem1Dc, _Elem2Dc, _CallId>(__algo, __checker, __algo_name); +#endif +} + +// The host policies with the offset fill of the second range, for the call sites which run the device +// side separately or not at all. See run_algo2_offset. +template +void +run_algo2_offset_host_policies(_Algo __algo, _Checker __checker, const char* __algo_name) +{ + run_algo2_offset<_Elem1, _Elem2>(oneapi::dpl::execution::seq, __algo, __checker, __algo_name); + run_algo2_offset<_Elem1, _Elem2>(oneapi::dpl::execution::unseq, __algo, __checker, __algo_name); + run_algo2_offset<_Elem1, _Elem2>(oneapi::dpl::execution::par, __algo, __checker, __algo_name); + run_algo2_offset<_Elem1, _Elem2>(oneapi::dpl::execution::par_unseq, __algo, __checker, __algo_name); +} + +#if TEST_DPCPP_BACKEND_PRESENT +template +void +run_algo2_offset_hetero_policies(_Algo __algo, _Checker __checker, const char* __algo_name) +{ + run_algo2_offset<_Elem1, _Elem2>(TestUtils::get_dpcpp_test_policy<_CallId>(), __algo, __checker, __algo_name); +} +#endif // TEST_DPCPP_BACKEND_PRESENT + +// The same as run_algo2_all_policies with the offset fill of the second range, see run_algo2_offset. +template +void +run_algo2_offset_all_policies(_Algo __algo, _Checker __checker, const char* __algo_name) +{ + run_algo2_offset_host_policies<_Elem1, _Elem2>(__algo, __checker, __algo_name); +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_offset<_Elem1Dc, _Elem2Dc>(TestUtils::get_dpcpp_test_policy<_CallId>(), __algo, __checker, __algo_name); +#endif +} + +// The same two helpers over the plain range without view_interface, see run_algo_plain. +template +void +run_algo_plain_all_policies(_Algo __algo, _Checker __checker, const char* __algo_name) +{ + run_algo_plain<_Elem>(oneapi::dpl::execution::seq, __algo, __checker, __algo_name); + run_algo_plain<_Elem>(oneapi::dpl::execution::unseq, __algo, __checker, __algo_name); + run_algo_plain<_Elem>(oneapi::dpl::execution::par, __algo, __checker, __algo_name); + run_algo_plain<_Elem>(oneapi::dpl::execution::par_unseq, __algo, __checker, __algo_name); +#if TEST_DPCPP_BACKEND_PRESENT + run_algo_plain<_ElemDc>(TestUtils::get_dpcpp_test_policy<_CallId>(), __algo, __checker, __algo_name); +#endif +} + +template +void +run_algo2_plain_all_policies(_Algo __algo, _Checker __checker, const char* __algo_name) +{ + run_algo2_plain<_Elem1, _Elem2>(oneapi::dpl::execution::seq, __algo, __checker, __algo_name); + run_algo2_plain<_Elem1, _Elem2>(oneapi::dpl::execution::unseq, __algo, __checker, __algo_name); + run_algo2_plain<_Elem1, _Elem2>(oneapi::dpl::execution::par, __algo, __checker, __algo_name); + run_algo2_plain<_Elem1, _Elem2>(oneapi::dpl::execution::par_unseq, __algo, __checker, __algo_name); +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_plain<_Elem1Dc, _Elem2Dc>(TestUtils::get_dpcpp_test_policy<_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 new file mode 100644 index 00000000000..7c7fdc77240 --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_value.pass.cpp @@ -0,0 +1,224 @@ +// -*- 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; + + // 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, search_n 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_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"); + + // search_n is constrained exactly like find: std::indirectly_comparable of the element against a + // pointer to the value type. The storage holds distinct values, so the only run which can be found + // is a run of length one. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::search_n(std::forward(policy), view, 1, search_value{searched}); + }, + [](auto&& view, auto res) { + return std::ranges::begin(res) == std::ranges::begin(view) + searched && std::ranges::size(res) == 1; + }, + "search_n"); + + // 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"); + + // 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. + // + // 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"); + +#if !_TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST + 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, + 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 + + // 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_all_policies( + [](auto&& policy, auto&& view) { + 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"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + 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"); + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + using elem_t = std::ranges::range_value_t>; + return dpl_ranges::search_n(std::forward(policy), view, 1, + typename elem_t::nocopy_value_type{searched}); + }, + [](auto&& view, auto res) { + return std::ranges::begin(res) == std::ranges::begin(view) + searched && std::ranges::size(res) == 1; + }, + "search_n, noncopyable value"); + + // Same for remove(): the predicate it builds internally must hold a reference to the value for + // the host policies. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + 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"); + + //---------------------------------------------------------------------------------------------- + // 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::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_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}, + search_proj_mut{}); + }, + [](auto&& view, auto res) { return std::ranges::begin(res) == std::ranges::begin(view) + searched; }, + "find_last, non-const projection"); +#endif + + 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"); + + // search_n takes its predicate ahead of its projection, so the default std::ranges::equal_to has to + // be spelled out to reach the projection at all. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + return dpl_ranges::search_n(std::forward(policy), view, 1, search_value{searched}, + std::ranges::equal_to{}, search_proj_mut{}); + }, + [](auto&& view, auto res) { + return std::ranges::begin(res) == std::ranges::begin(view) + searched && std::ranges::size(res) == 1; + }, + "search_n, 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 + + return TestUtils::done(_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 new file mode 100644 index 00000000000..4db7f35b481 --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_algo_archetypes_write.pass.cpp @@ -0,0 +1,580 @@ +// -*- 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, replace_if, replace), from the element of another range (copy, reverse_copy, rotate_copy, + // copy_if, remove_copy_if, remove_copy, unique_copy, partition_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 elements of replace and remove_copy are additionally equality comparable with the + // searched value of family 2, which is all their requires-clauses add. + + //---------------------------------------------------------------------------------------------- + // 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"); + + // The output range of copy, move and swap_ranges is filled with the offset values, so that the value + // the check reads is one which only the algorithm itself can have put there, see run_algo2_offset. + run_algo2_offset_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) { + const auto n = std::ranges::size(out_view); + return std::ranges::begin(out_view)[7].val == std::ranges::begin(in_view)[7].val && + std::ranges::begin(out_view)[n - 1].val == std::ranges::begin(in_view)[n - 1].val; + }, + "copy"); + + // reverse_copy and rotate_copy are constrained by std::indirectly_copyable just like copy, so they + // may only assign the element of the input range to the one of the output range. Both storages start + // as 0, 1, 2, ..., so a position which ends up holding a different value tells the assignment really + // happened: reverse_copy writes the last input element at the front of the output range, and + // rotate_copy writes the one the middle iterator points at. + run_algo2_all_policies( + [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::reverse_copy(std::forward(policy), in_view, out_view); + }, + [](auto&& in_view, auto&& out_view, auto) { + const auto n = std::ranges::size(in_view); + return std::ranges::begin(out_view)[0].val == std::ranges::begin(in_view)[n - 1].val && + std::ranges::begin(out_view)[n - 1].val == std::ranges::begin(in_view)[0].val; + }, + "reverse_copy"); + + run_algo2_all_policies( + [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::rotate_copy(std::forward(policy), in_view, + std::ranges::begin(in_view) + 10, out_view); + }, + [](auto&& in_view, auto&& out_view, auto) { + const auto n = std::ranges::size(in_view); + return std::ranges::begin(out_view)[0].val == std::ranges::begin(in_view)[10].val && + std::ranges::begin(out_view)[n - 10].val == std::ranges::begin(in_view)[0].val; + }, + "rotate_copy"); + + run_algo2_offset_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"); + + // Both ranges of swap_ranges hold the very same element type, so the offset fill is the only thing + // which tells them apart: after the swap the first range holds the offset values and the second one + // the ascending ones. + run_algo2_offset_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 == (int)archetype_test_size + 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 conditionally copying algorithms. Their callable is spelled over the projected input iterator + // alone, so std::indirectly_copyable remains the whole output requirement: the implementation may + // only assign an input element to an output one. Both storages start as 0, 1, 2, ..., so an output + // position which ends up holding a different value proves the assignment really happened. + // KSATODO: the device paths of copy_if, remove_copy_if and remove_copy (which are copy_if with a + // negated predicate, respectively with a negated equality against the searched value), unique_copy + // and partition_copy all assign a const copy of the input element to the output one, + // which std::indirectly_copyable never asks for: it only requires *__out = *__in, with *__in the + // non-const lvalue iter_reference_t of the input iterator. The places to fix are + // - utils.h:170-172 - __pstl_assign::operator() takes the source by const lvalue reference, so + // every writer below hands the output element a const source; + // - parallel_backend_sycl.h:298 - the single group copy_if functor materializes a copy of the + // input element with static_cast<__tuple_type>(__in_rng[__idx]) and assigns from that prvalue; + // - parallel_backend_sycl_reduce_then_scan.h:164,182 - __write_to_id_if::operator() assigns from + // the element it reads out of the const tuple __v gathered beforehand, i.e. from a const copy as + // well; it is reached from lines 1695 and 1709 for every one of the four algorithms; + // - parallel_backend_sycl_reduce_then_scan.h:2158 - the unique pattern additionally copies the + // 0th element with __write_op.__assign(__in_rng[0], __out_rng[0]); + // - parallel_backend_sycl_reduce_then_scan.h:238,262 - partition_copy does not use __pstl_assign, + // but __write_partitioned::operator() destructures the very same const tuple and assigns + // __tuple_type_cast(__value, ...), which is a prvalue copy, to either output range. + // Taking the source of __pstl_assign by forwarding reference and dropping the casts to a value + // fixes all of them; the host paths already assign from the reference itself. + { + auto call = [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::copy_if(std::forward(policy), in_view, out_view, copy_pred{}); + }; + auto check = [](auto&& in_view, auto&& out_view, auto res) { + // copy_pred keeps every third element, so the output holds 0, 3, 6, ... + const auto n = std::ranges::size(in_view); + return std::ranges::begin(out_view)[1].val == 3 && + (std::size_t)(res.out - std::ranges::begin(out_view)) == (n + 2) / 3; + }; + + run_algo2_host_policies(call, check, "copy_if"); +#if TEST_DPCPP_BACKEND_PRESENT && !_TEST_CPP20_RANGES_BROKEN_REQUIRES_COPY_IF_HETERO + run_algo2_hetero_policies(call, check, "copy_if"); +#endif + } + + { + auto call = [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::remove_copy_if(std::forward(policy), in_view, out_view, copy_pred{}); + }; + auto check = [](auto&& in_view, auto&& out_view, auto res) { + // The negation of copy_pred, so the output holds 1, 2, 4, 5, 7, ... + const auto n = std::ranges::size(in_view); + return std::ranges::begin(out_view)[0].val == 1 && + (std::size_t)(res.out - std::ranges::begin(out_view)) == n - (n + 2) / 3; + }; + + run_algo2_host_policies(call, check, "remove_copy_if"); +#if TEST_DPCPP_BACKEND_PRESENT && !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_COPY_IF_HETERO + run_algo2_hetero_policies(call, check, "remove_copy_if"); +#endif + } + + { + auto call = [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::unique_copy(std::forward(policy), in_view, out_view, copy_equiv{}); + }; + auto check = [](auto&& in_view, auto&& out_view, auto res) { + // copy_equiv groups the input into buckets of three, so the output holds 0, 3, 6, ... + const auto n = std::ranges::size(in_view); + return std::ranges::begin(out_view)[1].val == 3 && + (std::size_t)(res.out - std::ranges::begin(out_view)) == (n + 2) / 3; + }; + + run_algo2_host_policies(call, check, "unique_copy"); +#if TEST_DPCPP_BACKEND_PRESENT && !_TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE_COPY_HETERO + run_algo2_hetero_policies(call, check, "unique_copy"); +#endif + } + + // partition_copy needs a second output range, which is allocated inside the call, so the check is + // done there as well. + { + auto call = [](auto&& policy, auto&& in_view, auto&& out_true_view) { + using elem_t = std::ranges::range_value_t>; + auto out_false_storage = make_out_storage(policy, archetype_test_size); + auto out_false_view = out_false_storage.view(); + auto res = dpl_ranges::partition_copy(std::forward(policy), in_view, out_true_view, + out_false_view, copy_pred{}); + const auto n = std::ranges::size(in_view); + return std::ranges::begin(out_true_view)[1].val == 3 && std::ranges::begin(out_false_view)[0].val == 1 && + (std::size_t)(res.out1 - std::ranges::begin(out_true_view)) == (n + 2) / 3 && + (std::size_t)(res.out2 - std::ranges::begin(out_false_view)) == n - (n + 2) / 3; + }; + auto check = [](auto&&, auto&&, auto res) { return res; }; + + run_algo2_host_policies(call, check, "partition_copy"); +#if TEST_DPCPP_BACKEND_PRESENT && !_TEST_CPP20_RANGES_BROKEN_REQUIRES_PARTITION_COPY_HETERO + run_algo2_hetero_policies(call, check, "partition_copy"); +#endif + } + + // replace_if writes the unrelated value type into the range itself, exactly like fill, and takes a + // predicate over the element on top of it. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + using __elem = std::ranges::range_value_t>; + return dpl_ranges::replace_if(std::forward(policy), view, write_pred{}, + typename __elem::value_arg{42}); + }, + [](auto&& view, auto) { + return std::ranges::begin(view)[0].val == 42 && std::ranges::begin(view)[1].val == 1 && + std::ranges::begin(view)[3].val == 42; + }, + "replace_if"); + + // replace is the write family and the value family at once: the old value is compared with + // std::ranges::equal_to and the new value is written into the range as replace_if does. The two + // value types are unrelated to each other and to the element, so an implementation which confuses + // them, or which assigns an element instead of the new value, does not compile. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + using elem_t = std::ranges::range_value_t>; + return dpl_ranges::replace(std::forward(policy), view, search_value{3}, + typename elem_t::value_arg{42}); + }, + // The storage holds 0, 1, 2, ..., so only the element equal to the old value 3 is overwritten. + [](auto&& view, auto) { + return std::ranges::begin(view)[3].val == 42 && std::ranges::begin(view)[2].val == 2; + }, + "replace"); + + // remove_copy is the copying family and the value family at once: it drops the elements equal to + // the searched value and assigns the surviving ones to the output range. + run_algo2_all_policies( + [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::remove_copy(std::forward(policy), in_view, out_view, search_value{3}); + }, + [](auto&& in_view, auto&& out_view, auto res) { + // The value 3 occurs exactly once, so the output holds 0, 1, 2, 4, 5, ... + const auto n = std::ranges::size(in_view); + return std::ranges::begin(out_view)[3].val == 4 && + (std::size_t)(res.out - std::ranges::begin(out_view)) == n - 1; + }, + "remove_copy"); + + // replace_copy_if and replace_copy write either an input element or the new value into the output + // range, so the output element is assignable from both and from nothing else. Both cases are checked: + // a replaced position against the new value, and a kept one against the input element, which the + // offset fill of the output range makes a real check, see run_algo2_offset. + // KSATODO: every host path of the two but the serial scalar one stores the new value by value and + // therefore copy constructs it, which std::indirectly_writable, const _T&> never + // asks for: it only needs *__out = __value for a const lvalue value. The places to fix are + // - utils.h:451,455 - __replace_copy_functor holds the value as a const _Tp member and copy + // constructs it in its constructor; + // - algorithm_ranges_impl.h:1753 - __pattern_replace_copy_if instantiates that functor with _T + // deduced from its own const _T& parameter, which drops the __ref_or_copy reference the CPO hands + // it at glue_algorithm_ranges_impl.h:1182. + // Holding the __ref_or_copy type instead of a value fixes both. The serial scalar overload + // (algorithm_ranges_impl.h:1761) already forwards the reference to std::ranges::replace_copy_if, and + // the device path (hetero/algorithm_ranges_impl_hetero.h:952) legitimately copies the value into the + // kernel, so the serial scalar branch, i.e. seq, is the only conforming host one. The gap macro covers + // the host side as a whole, so all four host policies are guarded here. + { + auto call = [](auto&& policy, auto&& in_view, auto&& out_view) { + using out_t = std::ranges::range_value_t>; + return dpl_ranges::replace_copy_if(std::forward(policy), in_view, out_view, copy_pred{}, + typename out_t::value_arg{42}); + }; + auto check = [](auto&&, auto&& out_view, auto) { + // copy_pred holds for every third element, which is replaced with 42. + return std::ranges::begin(out_view)[0].val == 42 && std::ranges::begin(out_view)[3].val == 42 && + std::ranges::begin(out_view)[2].val == 2; + }; + + run_algo2_offset_host_policies(call, check, "replace_copy_if"); +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_offset_hetero_policies(call, check, + "replace_copy_if"); +#endif + } + + { + auto call = [](auto&& policy, auto&& in_view, auto&& out_view) { + using out_t = std::ranges::range_value_t>; + return dpl_ranges::replace_copy(std::forward(policy), in_view, out_view, search_value{3}, + typename out_t::value_arg{42}); + }; + auto check = [](auto&&, auto&& out_view, auto) { + // Only the element equal to the old value 3 is replaced with 42. + return std::ranges::begin(out_view)[3].val == 42 && std::ranges::begin(out_view)[2].val == 2; + }; + + run_algo2_offset_host_policies(call, check, "replace_copy"); +#if TEST_DPCPP_BACKEND_PRESENT + run_algo2_offset_hetero_policies(call, check, + "replace_copy"); +#endif + } + + //---------------------------------------------------------------------------------------------- + // 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"); + + // The conditionally copying algorithms with a predicate, respectively an equivalence relation, + // taking the input element by non-const reference: the projected reference of archetype_view is a + // non-const lvalue, so an implementation which copies the element, or hands a const one to the + // callable, does not compile. + // The three algorithms whose device path is guarded above are guarded here for the very same + // reason: the const copy of the input element breaks the call before the predicate is reached. + { + auto call = [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::copy_if(std::forward(policy), in_view, out_view, copy_pred_mut{}); + }; + auto check = [](auto&& in_view, auto&& out_view, auto res) { + const auto n = std::ranges::size(in_view); + return std::ranges::begin(out_view)[1].val == 3 && + (std::size_t)(res.out - std::ranges::begin(out_view)) == (n + 2) / 3; + }; + + run_algo2_host_policies(call, check, "copy_if, non-const predicate"); +#if TEST_DPCPP_BACKEND_PRESENT && !_TEST_CPP20_RANGES_BROKEN_REQUIRES_COPY_IF_HETERO + run_algo2_hetero_policies(call, check, + "copy_if, non-const predicate"); +#endif + } + + { + auto call = [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::remove_copy_if(std::forward(policy), in_view, out_view, + copy_pred_mut{}); + }; + auto check = [](auto&& in_view, auto&& out_view, auto res) { + const auto n = std::ranges::size(in_view); + return std::ranges::begin(out_view)[0].val == 1 && + (std::size_t)(res.out - std::ranges::begin(out_view)) == n - (n + 2) / 3; + }; + + run_algo2_host_policies(call, check, + "remove_copy_if, non-const predicate"); +#if TEST_DPCPP_BACKEND_PRESENT && !_TEST_CPP20_RANGES_BROKEN_REQUIRES_REMOVE_COPY_IF_HETERO + run_algo2_hetero_policies( + call, check, "remove_copy_if, non-const predicate"); +#endif + } + + { + auto call = [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::unique_copy(std::forward(policy), in_view, out_view, + copy_equiv_mut{}); + }; + auto check = [](auto&& in_view, auto&& out_view, auto res) { + const auto n = std::ranges::size(in_view); + return std::ranges::begin(out_view)[1].val == 3 && + (std::size_t)(res.out - std::ranges::begin(out_view)) == (n + 2) / 3; + }; + + run_algo2_host_policies(call, check, "unique_copy, non-const relation"); +#if TEST_DPCPP_BACKEND_PRESENT && !_TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE_COPY_HETERO + run_algo2_hetero_policies(call, check, + "unique_copy, non-const relation"); +#endif + } + + { + auto call = [](auto&& policy, auto&& in_view, auto&& out_true_view) { + using elem_t = std::ranges::range_value_t>; + auto out_false_storage = make_out_storage(policy, archetype_test_size); + auto out_false_view = out_false_storage.view(); + auto res = dpl_ranges::partition_copy(std::forward(policy), in_view, out_true_view, + out_false_view, copy_pred_mut{}); + const auto n = std::ranges::size(in_view); + return std::ranges::begin(out_true_view)[1].val == 3 && std::ranges::begin(out_false_view)[0].val == 1 && + (std::size_t)(res.out1 - std::ranges::begin(out_true_view)) == (n + 2) / 3 && + (std::size_t)(res.out2 - std::ranges::begin(out_false_view)) == n - (n + 2) / 3; + }; + auto check = [](auto&&, auto&&, auto res) { return res; }; + + run_algo2_host_policies(call, check, + "partition_copy, non-const predicate"); +#if TEST_DPCPP_BACKEND_PRESENT && !_TEST_CPP20_RANGES_BROKEN_REQUIRES_PARTITION_COPY_HETERO + run_algo2_hetero_policies( + call, check, "partition_copy, non-const predicate"); +#endif + } + + run_algo_all_policies( + [](auto&& policy, auto&& view) { + using __elem = std::ranges::range_value_t>; + return dpl_ranges::replace_if(std::forward(policy), view, write_pred_mut{}, + typename __elem::value_arg{42}); + }, + [](auto&& view, auto) { + return std::ranges::begin(view)[0].val == 42 && std::ranges::begin(view)[1].val == 1 && + std::ranges::begin(view)[3].val == 42; + }, + "replace_if, non-const predicate"); + + // replace and remove_copy with a projection taking the element by non-const reference: the value + // itself is compared with std::ranges::equal_to, so the projection is the only user callable here. + run_algo_all_policies( + [](auto&& policy, auto&& view) { + using elem_t = std::ranges::range_value_t>; + return dpl_ranges::replace(std::forward(policy), view, search_value{3}, + typename elem_t::value_arg{42}, replace_proj_mut{}); + }, + [](auto&& view, auto) { + return std::ranges::begin(view)[3].val == 42 && std::ranges::begin(view)[2].val == 2; + }, + "replace, non-const projection"); + + // remove_copy with a projection taking the element by non-const reference, as replace above. + run_algo2_all_policies( + [](auto&& policy, auto&& in_view, auto&& out_view) { + return dpl_ranges::remove_copy(std::forward(policy), in_view, out_view, search_value{3}, + replace_proj_mut{}); + }, + [](auto&& in_view, auto&& out_view, auto res) { + const auto n = std::ranges::size(in_view); + return std::ranges::begin(out_view)[3].val == 4 && + (std::size_t)(res.out - std::ranges::begin(out_view)) == n - 1; + }, + "remove_copy, non-const projection"); + + // The two replacing copies are guarded here for the very same reason as above: the copy of the new + // value breaks the call before the predicate, respectively the projection, is ever reached. + run_algo2_offset_all_policies( + [](auto&& policy, auto&& in_view, auto&& out_view) { + using out_t = std::ranges::range_value_t>; + return dpl_ranges::replace_copy_if(std::forward(policy), in_view, out_view, + copy_pred_mut{}, typename out_t::value_arg{42}); + }, + [](auto&&, auto&& out_view, auto) { + return std::ranges::begin(out_view)[0].val == 42 && std::ranges::begin(out_view)[3].val == 42 && + std::ranges::begin(out_view)[2].val == 2; + }, + "replace_copy_if, non-const predicate"); + + run_algo2_offset_all_policies( + [](auto&& policy, auto&& in_view, auto&& out_view) { + using out_t = std::ranges::range_value_t>; + return dpl_ranges::replace_copy(std::forward(policy), in_view, out_view, search_value{3}, + typename out_t::value_arg{42}, replace_proj_mut{}); + }, + [](auto&&, auto&& out_view, auto) { + return std::ranges::begin(out_view)[3].val == 42 && std::ranges::begin(out_view)[2].val == 2; + }, + "replace_copy, non-const projection"); + + // The writing pattern over plain_archetype_view, i.e. over ranges without the members + // std::ranges::view_interface provides; see the plain range section of the read test for what this + // proves. copy is the representative shape here: the number of elements to write is the smaller of + // the two range sizes, which the implementation has to obtain through std::ranges::size and not + // through a size() member of the user range. Both storages start as 0, 1, 2, ..., so only the two + // returned iterators say something here; that the assignment happens at all is what the call at id 1 + // above checks. + run_algo2_plain_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 res) { + return res.in == std::ranges::begin(in_view) + std::ranges::size(in_view) && + res.out == std::ranges::begin(out_view) + std::ranges::size(out_view); + }, + "copy, plain ranges"); +#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 new file mode 100644 index 00000000000..8642af5785a --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_archetypes.h @@ -0,0 +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 + +//------------------------------------------------------------------------------------------------ +// 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..16c9e79cf28 --- /dev/null +++ b/test/parallel_api/ranges/std_ranges_archetypes_base.h @@ -0,0 +1,312 @@ +// -*- 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 +#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 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 +// 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&; + // 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) {} + + T* base() const { return ptr; } + + reference 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); } +}; + +// 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 +{ + +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>); + +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>>, + 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; } + + // 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