Hi, C++20 changed the semantics of relational operators quite a bit, and it affects all Qt classes that are equality-comparable and/or ordered.
In the following, A and B are types and a and b are objects of type A and B, resp. First, the compiler will now synthesize missing operators from existing ones. So a C++20 program only needs to provide op==(A, B) and the compiler can use this to satisfy requests for all of these: - a == b (obviously) - a != b (by inverting the result) - b == a (by reversing the arguments) - b != a (by doing both) In turn, this can now cause ambiguities (ie. hard compile errors) when there's an asymmetry between user-provided operators and the compiler-synthesized ones. At the very least, we need to fix these for our C++20 users and one open question is whether we pick these into older branches and if so, how far back. I know projects that were very close to using C++20 together with Qt 5 a year or so ago. One can even now = default the equality operator. Then the compiler creates one based on for member-wise equality. If all such members have an = default'ed equality operator, and with a few other other requirements, the type in question has Strong Structural Equality, and thus can be used as arguments to non-type template parameters (NTTP). QFlags and QFixed are examples where this might come in handy. There are probably others, too. For ordered types, C++20 introduces a new relational operator, affectionately called the spaceship operator <=>. It basically returns -1, 0, 1 based on whether the lhs is <, ==, > the rhs (think strcmp). However, it doesn't return int, but one of three scoped enums: - partial_ordering (allows incomparable elements, think NaN op NaN, if we could turn back time and rewrite IEEE754) - weak_ordering (allows equivalent, but non-equal elements, think qstricmp) - strong_ordering (requires a trichotomy (exactly one of <, ==, > is true for every pair or lhs, rhs and there's no public API that, when ==, allows to tell a difference between the types (addressof is allowed to differ) From this operator,, the compiler can synthesize all of <, >, <=, >=. But users can also call it manually. So, not only must we provide op <=> for all of our ordered types, we must also decide in which of the three categories (partial, weak, strong) each comparison falls. And not just for homogeneous relations (a op a), but heterogeneous ones, too (a op b). I think this presents us with an excellent opportunity to re-design our currently-ad-hoc way of dealing with relational operators, which all too often are just documented as functions (documenting individual op== op!= for all combinations of {QRect,QRectF} × {QRect,QRectF} instead of as properties of the type (QRect: "QRect is equality-comparable with itself and QRectF", QRectF: "QRectF is equality-comparable with itself and QRect"). I think this would improve the user documentation immensely, esp. if we find a way to conveniently say why some relation is _not_ provided, too ("QRect is not ordered, because any ordering would be arbitrary and not natural") and = delete the corresponding operators: // QRect: friend void operator<=>(QRect, QRect) = delete; // QRectF: friend void operator<=>(QRectF, QRectF) = delete; friend void operator<=>(QRectF, QRect) = delete; I have drafted a proposal for how to go about the change here: https://bugreports.qt.io/browse/QTBUG-103757?focusedCommentId=671497#comment-671497 TL;DR: provide named functions for the minimal set of op== and op<=> we'd have to write in C++20, then use macros to turn these into op==, op!=, op<, op>, op<=, op>= for C++17 builds and op== and op<=> for C++20 builds. This would be much easier done with an ABI break, but we have the tools (QT_REMOVED_SINCE) now to deal with this already now. If we also make all these operators hidden friends (https://bugreports.qt.io/browse/QTBUG-87973), then we at the same time improve the user experience by removing overly-verbose error messages when something goes wrong. If we do this, though, some incidental comparisons that rely on implicit conversions will stop working. This is a good thing, IMO, because it forces us to provide (and therefore document) these operators explicitly. The process will also force us to (at least mentally) assign level numbers to heterogeneously-comparable classes. This might run into problems (such as QTBUG-108136), but is overall a good thing. Opinions? Comments? Thanks, Marc References: - https://bugreports.qt.io/browse/QTBUG-103757 - https://bugreports.qt.io/browse/QTBUG-104110 - https://bugreports.qt.io/browse/QTBUG-104114 - https://bugreports.qt.io/browse/QTBUG-104108 - https://en.cppreference.com/w/cpp/header/compare - https://bugreports.qt.io/browse/QTBUG-104180 _______________________________________________ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development