Does C++ Have an Adjacent Compare Function Similar to R?

C++20 introduces std::span, a type-erased view of contiguous data. While initially designed with deep comparison operators, these were later removed. This article explores how to implement those comparisons leveraging C++20 features like concepts, ranges, and the three-way comparison operator (<=>). This approach offers a more elegant and efficient solution compared to C++17 techniques.

Understanding the Comparison Problem

std::span should ideally compare with contiguous ranges holding the same data type. This includes other std::span instances and containers like std::vector. Comparing a std::span<int> to a std::vector<int> is logical, while comparing it to a std::list<int> or std::vector<long> is less meaningful. For disparate comparisons, std::ranges::equal provides a suitable alternative.

C++17 Implementation Challenges

Implementing comparisons in C++17 required numerous overloaded operators to handle different combinations of types and constness. This led to verbose code, potential ambiguity, and increased maintenance complexity. Even with techniques like hidden friends, limitations persisted, particularly when comparing std::span with other container types.

The C++20 Solution

C++20 significantly simplifies comparison operator implementation. We can define a concept to encapsulate our requirements:

template <typename R, typename T>
concept const_contiguous_range_of = contiguous_range<R const> && std::same_as<std::remove_cvref_t<T>, std::ranges::range_value_t<R const>>;

This concept ensures that the compared range (R) is contiguous and its value type is identical to T, disregarding constness. With this concept, we can implement comparison operators concisely:

template <equality_comparable T, std::size_t E, const_contiguous_range_of<T> R>
bool operator==(span<T, E> lhs, R const& rhs) {
  return ranges::equal(lhs, rhs);
}

template <three_way_comparable T, std::size_t E, const_contiguous_range_of<T> R>
auto operator<=>(span<T, E> lhs, R const& rhs) {
    return std::lexicographical_compare_three_way(
        lhs.begin(), lhs.end(), ranges::begin(rhs), ranges::end(rhs));
}

These two operators, empowered by C++20’s synthesized and reversed comparison capabilities, cover all necessary comparison scenarios.

How C++20 Comparisons Work

C++20 automatically generates reversed and synthesized comparisons. Defining operator== implicitly provides !=, and defining operator<=> provides <, <=, >, and >=. The compiler intelligently selects the appropriate comparison based on the context. This significantly reduces boilerplate code.

Example Comparisons

  • span<int> vs. span<int>: Uses the defined operator==.
  • span<int> vs. span<const int>: Also uses operator==, as the concept handles constness.
  • vector<int> vs. span<int>: Uses the reversed operator==.
  • span<int> vs. list<int>: Compilation fails, as list is not contiguous, failing the concept requirement.
  • span<int> vs. vector<long>: Compilation fails, as int and long are not the same type, failing the concept requirement.
  • vector<int> vs. span<int> with <: Uses the reversed operator<=> and evaluates the result appropriately.

Conclusion

While std::span lacks built-in deep comparison operators, C++20 provides the tools to implement them efficiently and elegantly. By leveraging concepts, ranges, and the three-way comparison operator, we can achieve comprehensive comparison functionality with minimal code, enhancing the usability of std::span for various use cases. This approach dramatically improves upon the cumbersome methods required in C++17. While not directly mirroring R’s adjacent comparison functions, C++20 offers a powerful and flexible solution tailored to the specific needs of std::span.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *