Search code examples
c++operator-overloadingc++20spaceship-operator

Clarifying std::weak_ordering with C++20 Spaceship Operator for a Struct


I am learning about std::strong_ordering and std::weak_ordering in C++20. According to the docs, std::strong_ordering requires that equivalent values be indistinguishable, as opposed to std::weak_ordering.

When researching std::weak_ordering (especially in the context of the spaceship operator, <=>), I generally encounter examples similar to the one below, which represents a multiplication expression:

// Represents a multiplication expression, the result of which is (multiplicand * multiplier)
struct Multiplication {
    int multiplicand;
    int multiplier;
};

This struct appears like it should be weakly ordered as different pairs of multiplicands and multipliers can evaluate to the same result, meaning equivalent values can be distinguishable (Multiplication{3, 2}, Multiplication{6, 1}, and Multiplication{1, 6} all technically represent 6).

However, the trivial approach for comparing different Multiplication structs actually returns an std::strong_ordering because multiplication results are ints and hence strongly ordered.

struct Multiplication {
    int multiplicand;
    int multiplier;

    // This returns std::strong_ordering by default
    auto operator<=>(Multiplication rhs) const 
    {
        return (multiplicand * multiplier) <=> (rhs.multiplicand * rhs.multiplier);
    }
};

Is it then my responsibility to explicitly annotate the return type as std::weak_ordering in this case to make it semantically accurate?


Solution

  • According to the docs, std::strong_ordering requires that equivalent values be indistinguishable, as opposed to std::weak_ordering.

    This is incorrect. It requires "substitutability", which in the standard is defined as:

    the property that f(a) == f(b) is true whenever a == b is true, where f denotes a function that reads only comparison-salient state that is accessible via the argument’s public const members.

    That's a very narrow definition, one that allows for a and b to have differences in data so long as they are not "comparison-salient".

    That being said:

    Is it then my responsibility to explicitly annotate the return type as std::weak_ordering in this case to make it semantically accurate?

    Yes. The compiler cannot know what is "substitutable" and what is not. This is a semantic property, and it is always up to the programmer to define it.

    And yes, your Multiplication struct is not substitutable.