I've been playing with the spaceship operator and I'm wondering what is the cause for the following behavior:
struct ArrayWrapper
{
int arr[3];
auto operator<=>(const ArrayWrapper&) const = default;
};
ArrayWrapper a1{1,2,3}, a2{1,2,4};
auto x = a1 <=> a2; // this compiles and works, x is std::strong_ordering::less
The array comparison works for arrays that are members. It also works for std::array
:
std::array<int, 3> arr1{1,2,4};
std::array<int, 3> arr2{1,2,3};
auto x = arr1 <=> arr2; // x is std::strong_ordering::greater
However it does not work for raw arrays that are not members:
int rawArr1[3]{1,2,3}, rawArr2[3]{1,2,3};
auto x = rawArr1 <=> rawArr2; // error: invalid operands of types ‘int [3]’ and ‘int [3]’ to binary ‘operator<=>’
I've tested it on GCC 11. What is the reason? Seems weird taking into account it works for array members.
A goal of the three way comparison is to make (newly introduced) implicitly generated comparisons consistent with implicitly generated copy-semantics (that pre-exist; disregarding deprecated cases). A consistent phenomenon can be observed with copying:
ArrayWrapper a3 = a2; // OK
int rawArr3[] = rawArr2; // ill-formed
The original proposal has this to say:
P0515R0 Consistent comparison
2.2.3 Language types and
operator<=>
- For copyable arrays
T[N]
(i.e., that are nonstatic data members),T[N] <=> T[N]
returns the same type asT
’s<=>
and performs lexicographical elementwise comparison. For other arrays, there is no<=>
because the arrays are not copyable.Notes ... For arrays, we don’t provide comparison if the array is not copyable in the language, to keep copying and comparison consistent. Note that for two arrays, arr1<=>arr2 is ill-formed because the array-to-pointer conversion is not applied.
And the wording of the rules (latest draft):
[expr.spaceship]
- If at least one of the operands is of object pointer type and the other operand is of object pointer or array type, array-to-pointer conversions ([conv.array]), pointer conversions ([conv.ptr]), and qualification conversions are performed on both operands to bring them to their composite pointer type ([expr.type]). After the conversions, the operands shall have the same type. [Note 1: If both of the operands are arrays, array-to-pointer conversions are not applied. — end note]
... no other case applying to arrays
- Otherwise, the program is ill-formed.
This is why rawArr1 <=> rawArr2
doesn't work.
[class.compare.default]
The direct base class subobjects of C, in the order of their declaration in the base-specifier-list of C, followed by the non-static data members of C, in the order of their declaration in the member-specification of C, form a list of subobjects. In that list, any subobject of array type is recursively expanded to the sequence of its elements, in the order of increasing subscript. Let xi be an lvalue denoting the ith element in the expanded list of subobjects for an object x (of length n), where xi is formed by a sequence of derived-to-base conversions ([over.best.ics]), class member access expressions ([expr.ref]), and array subscript expressions ([expr.sub]) applied to x.
This is why a1 <=> a2
works.
Furthermore, a guarantee for std::array
:
[container.requirements.general]
i <=> j
Constraints: X::iterator meets the random access iterator requirements.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With