Skip to content

inclusive_scan and exclusive_scan over reverse iterators with different source iterator types fails to compile #2296

Open
@mmichel11

Description

@mmichel11

Describe the Bug:
When performing an inclusive or exclusive scan with host or device backends, the scan fails to compile if both the source and destination sequences are provided as reverse iterators and the underlying source iterators are different, non equality comparable types. This issue likely affects other iterator adaptors as well.

To Reproduce:
The following reproducer may be used:

// icpx [-DONEDPL_USE_DPCPP_BACKEND=1] scan_reverse_iterator.cpp
#include <oneapi/dpl/execution>
#include <oneapi/dpl/algorithm>
#include <oneapi/dpl/numeric>
#include <vector>
#include <iostream>

#if ONEDPL_USE_DPCPP_BACKEND
#   define POLICY oneapi::dpl::execution::dpcpp_default
#else
#   define POLICY oneapi::dpl::execution::par_unseq
#endif

int main() {
    // Create a vector of bool values
    std::vector<bool> input = {true, false, true, true, false, true};

    std::cout << "Original vector of booleans:" << std::endl;
    for (const auto& val : input) {
        std::cout << (val ? "true" : "false") << " ";
    }
    std::cout << std::endl;

    // Create a vector to store the result (int type)
    std::vector<int> result(input.size());

    // Create reverse iterators
    auto input_rbegin = std::rbegin(input);
    auto input_rend = std::rend(input);
    auto result_rbegin = std::rbegin(result);

    // Use exclusive_scan with reverse iterators to convert bool to int
    // This will scan from right to left (due to reverse iterators)
    // The initial value (0) will appear at the rightmost position
    oneapi::dpl::exclusive_scan(
        POLICY,                  // Parallel execution policy
        input_rbegin,            // Start of reversed input range
        input_rend,              // End of reversed input range
        result_rbegin,           // Start of reversed output range
        int{0}                   // Initial value
    );

    std::cout << "\nOriginal vector (left to right):" << std::endl;
    for (size_t i = 0; i < input.size(); ++i) {
        std::cout << (input[i] ? 1 : 0) << " ";
    }
    std::cout << std::endl;

    std::cout << "\nResult of exclusive_scan with reverse iterators:" << std::endl;
    for (const auto& val : result) {
        std::cout << val << " ";
    }
    std::cout << std::endl;

    return 0;
}

and compiled with icpx [-DONEDPL_USE_DPCPP_BACKEND=1] scan_reverse_iterator.cpp. You will be met with a compile error.

Expected Behavior:
Expected behavior is successful compilation of this sample which can be verified by using a oneapi::dpl::execution::seq policy.

Additional Context:

The reason for this is caused by the fact that we attempt to perform in-place exclusive scan detection for both OpenMP and SYCL device backends to workaround issues. This cannot be done without leveraging UB in which we compare source and destination iterators. The following utility is used:

// TODO In C++20 we may try to use std::equality_comparable
template <typename _Iterator1, typename _Iterator2, typename = void>
struct __is_equality_comparable : std::false_type
{
};
// All with implemented operator ==
template <typename _Iterator1, typename _Iterator2>
struct __is_equality_comparable<
_Iterator1, _Iterator2,
std::void_t<decltype(::std::declval<::std::decay_t<_Iterator1>>() == ::std::declval<::std::decay_t<_Iterator2>>())>>
: std::true_type
{
};
template <typename _Iterator1, typename _Iterator2>
constexpr bool
__iterators_possibly_equal(_Iterator1 __it1, _Iterator2 __it2)
{
if constexpr (__is_equality_comparable<_Iterator1, _Iterator2>::value)
{
return __it1 == __it2;
}
else if constexpr (__is_equality_comparable<_Iterator2, _Iterator1>::value)
{
return __it2 == __it1;
}
else
{
return false;
}
}

__is_equality_comparable detects if the iterator comparison is valid by checking its return type and using SFINAE. In the case of two iterators such as std::reverse_iterator<std::vector<bool>::iterator> and std::reverse_iterator<std::vector<int>::iterator>, the check returns true as operator== is defined for any two reverse iterators. However, compilation fails in the body of this comparison as the two vector iterators are not equality comparable.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions