[Development] C++20 comparisons @ Qt (was: Re: C++20 @ Qt)

Edward Welbourne edward.welbourne at qt.io
Mon Nov 7 11:26:47 CET 2022

On 04.11.22 12:13, Ulf Hermann via Development wrote:
>> One thing I haven't understood about the ordering problem is why we
>> cannot just define our "invalid" values to always be < any valid one and
>> equal to other invalid ones. This way we get at least weak ordering for
>> all our types and we're done.

Marc Mutz via Development (4 November 2022 16:15)
> That's what we're currently doing, yes. At least for QDateTime.

One factor in why I think partial order is probably a good choice is
that invalid QDateTimes are a bit more complicated than, say, null
QStrings; there is a null QDateTime, but you can also get an invalid one
by asking for a time that doesn't exist in the zone (overtly for spec =
TimeZone, implicitly for spec = LocalTime) in use, when folk moved their
clocks forward over that time in the given zone, typically in spring at
the start of DST.  These QDateTime instances, although invalid, do have
a toMSecsSinceEpoch() that's suitable to passing to
fromMSecsSinceEpoch() to reconstruct a valid date-time that's not what
you originally asked for but is close by.  See [0] for my attempt to do
better than that kludge.

[0] https://codereview.qt-project.org/c/qt/qtbase/+/308729/25

> For QString, we have that isNull() compares equal to empty.

Which makes a fair amount of sense, IMO.
Default-constructed string is empty.

> ... that prompted
> me to remember QOperatingSystemVersion, where we really, genuinely
> have several sets of values where elements from different sets are not
> ordered (Win10 <? OSX13), and Win op OSX is always false, for all op.
> There, partial_ordering is consistent with the current implementation.

Good example.

> We don't _need_ to make invalid values unordered, but it would be the
> mathematically correct thing to do.

... in some cases, at least.  For containers, like QString,
null-is-empty makes perfect mathematical sense, for example; the empty
set is the "null" set (and relation, and function), in so many senses.
Indeed, IIRC, the first teacher to introduce me to it used a
slashed-zero called "null" to denote it.

> We removed QVariant::op< because of these problems. partial_ordering
> would allow [us] to bring it back.

Albeit "whether we *should*" remains a separate story.

>> There may be types where existing operator< work differently (*cough*
>> QTypeRevision), but that just means we need to emulate that same
>> behavior with the new operators.

> Without having looked at QTypeRevision, I agree in general.

Certainly in the first instance we should match existing behaviour as
closely as possible with the new operators; but partial ordering does
present (once we're fully migrated to C++20) a new option that may be a
good choice for some types.  I don't advocate a blanket "do this
everywhere"; but there may well be types for which it makes sense.

>> Indeed the NaN behavior has always been a pain to deal with every time
>> I've encountered it. If we have a chance to avoid it, we should.
>> What is the downside of such an approach?

> The (potential) downside is that it's arbitrary and classes might behave
> inconsistently (one sorts invalid before valid ones, the other vice
> versa). It may also be a bit more work to document (unless we choose not
> to mention that little detail). Or we can't reap any future tool support
> that might be developed for partial_ordering (sanitizers, etc).

The NaN behaviour is a necessary escape from worse surprises; for
example, "if x < 0 and y < 0, then x * y > 0" - except that if you make
NaN < 0 that'll fail.  Of course, having the NaN drop into the else
branch of an if (x < 0) is apt to have it mistakenly presumed to be >=
0, when it also isn't that.  Still, with the language now actually
supporting partial order, we get the option of using it and at least
giving diligent programmers the chance to handle such cases gracefully'
and by overtly making some orderings partial, alert them to the need to.

The question remains: for which of our types will it actually make sense
to have a partial ordering ?  I trust those closest to the code to have
a clue, in each instance.


More information about the Development mailing list