Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is it possible to compare array members using operator<=>, but not freestanding arrays?

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.

like image 387
rubix_addict Avatar asked Jul 18 '21 16:07

rubix_addict


Video Answer


1 Answers

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 as T’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.

like image 128
eerorika Avatar answered Oct 18 '22 23:10

eerorika