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 definedoperator==
.span<int>
vs.span<const int>
: Also usesoperator==
, as the concept handles constness.vector<int>
vs.span<int>
: Uses the reversedoperator==
.span<int>
vs.list<int>
: Compilation fails, aslist
is not contiguous, failing the concept requirement.span<int>
vs.vector<long>
: Compilation fails, asint
andlong
are not the same type, failing the concept requirement.vector<int>
vs.span<int>
with<
: Uses the reversedoperator<=>
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
.