[Development] C++20 comparisons @ Qt (was: Re: C++20 @ Qt)
Thiago Macieira
thiago.macieira at intel.com
Wed Jun 14 18:16:32 CEST 2023
On Wednesday, 14 June 2023 01:52:49 PDT Marc Mutz via Development wrote:
> == Why E _and_ O? ==
>
> The reason we need E _and_ O for ordered types instead of just O is that
> O needs to order the lhs w.r.t. the rhs, which generally involves
> looking at all the state of the objects whereas E just needs to find
> _one_ difference to be able to quickly return false. E.g., a container
> can do
>
> bool E(~~~ lhs, ~~~ rhs) {
> if (lhs.size() != rhs.size()) return false; // O cannot do this!
> ~~~~
> }
>
> This is a very important optimization (and one reason why L1 string
> literals are going to stay and not be replaced with UTF-8 ones: L1 op
> UTF-16 _can_ use the size() short-cut while UTF-8 op UTF-16 cannot).
It's also why glibc added __memcmpeq and GCC and Clang can generate a call to
that. Same reason why I added QtPrivate::equalStrings() different from
QtPrivate::compareStrings() (internally they call ucstreq and ucstrcmp,
respectively).
> == Can E be spelled op==? ==
>
> It could. However, op== cannot have extra arguments and we have at least
> one use-case where E might have an additional argument: case-insensitive
> string comparisons. We currently have no (public) API to express op==,
> but case-insensitively. The closest we have is QString::compare(), but
> that is an O, so it cannot use the size() mismatch shortcut, assuming
> it's valid for case-insensitive string comparison (it is for L1, dunno
> about UTF-16).
Hmm... the facts are correct but I don't think that leads to the conclusion.
The whole objective here is to provide op== anyway, so the fact that some
classes have this implemented in another method that can do more should be
irrelevant. For those, this op== can be inline and call the out-of-line
function that does the relevant work.
> There's also the situation where (cheap!) implicit conversions allow one
> E to backfill several different op== (which, being hidden friends, don't
> participate in implicit conversions themselves). If E is spelled op==,
> what would the macros use to implement op==? It would be up to the class
> author to supply sufficiently-many op== in the correct form. We don't
> want that. We want all op== to be generated from the macros so we have
> central control over their generation (providing reversed operators in
> C++17, getting the signatures right, noexcept-ness, hidden-friend-ness,
> ...).
They can be exceptions and written manually.
However, I do not object to having the op== be an inline hidden friend,
calling a private static noexcept equality comparator function (whether that's
inline or not, taking extra arguments or not). In fact, I like that, purely on
stylistic reasons.
> == O as public API ==
>
> In C++20, O would be spelled op<=>, but this is not possible in C++17,
> so O needs to be an actual named function, not an operator. The
> question, and it's a rhetorical one, then becomes: should O be public
> API, and the (rhetorical) answer is "yes, because C++17 users will want
> to be able to access O in C++17 projects, too (in C++20, they could call
> op<=>)". This includes Qt's own implementation of O's.
Side note: I think we should start compiling Qt with C++20 right now, for all
compilers that support it, with no possibility for the user to turn that off.
There is C++20 functionality we could use in our implementations that don't
affect ABI and don't constrain C++17 users. And I also don't object to
providing C++20-only features, discussed on a case-by-case basis.
> Applied to our situation, this means:
>
> - each (orderable) class supplies O as a hidden friend
> - may supply it as a (non-static) member function
"At a minimum" as a hidden friend. It may be public API, like
QString::compare(), whether static or non-static.
> And we have the calling convention:
> lhs.O(rhs);
> Qt::O(lhs, rhs); // if you know the type
>
> using Qt::O;
> O(lhs, rhs);
> ..... // if you don't
I don't think this is necessary. I would prefer if that were only allowed
through op<=>.
C++17-constrained users would therefore need to know the types in question,
know what the "O" function is called and how to call it (static vs non-
static). And if it isn't public at all, then you're limited to the traditional
relational operators (op<, op<=, op> and op>=). Mind you, we're not breaking
those users in any way; we're simply not adding extra API for them.
Paraphrasing you about a decade ago, "C++17 costs more".
> This leads to the finding that O (and, depending on the outcome of the
> first two Open Questions) E cannot be named like static binary functions
> in existing classes, as, even if they are semantically compatible,
> they're syntactically incompatible.
>
> That objectively rules out "compare" for O and "equals" for E.
Conclusion invalidated due to premise rejected. I couldn't follow your logic
anyway, but since the premise was no longer applicable, I didn't try very
hard.
> == Naming O ==
>
> Given that compare() doesn't work, what are alternatives?
Moot section.
> == Naming E ==
>
> So far, we've been using equal(). equals() doesn't work for technical
> reasons, but while it'd work as a member function lhs.equals(rhs), it's
> also kinda wrong if the function is taking two arguments (equals(lhs,
> rhs), but there are _two_ objects). So equal() as the plural form or
> equals() makes sense. There were also proposals of qEqual() (which I'd
> again reserve as the CPO name of E) and areEqual() (which I find uglily
> long).
Why doesn't "equals" work?
We've been over this in the code review: our current naming conventions would
dictate "equals" even for two arguments, as the most common phrasing is "A
equals B" instead of "A and B equal". When you compare things, you always
compare at a minimum two of them, so there being two is implied by the act of
comparing anyway. And I don't see why we can't provide both static and non-
static members, like QString::compare does
I agree "areEqual" is ugly.
> order() and equal() have the nice property of being equally long. With
> equal() typically returning bool and order() often returning auto, this
> makes for a very pleasing alignment of the two functions. But after
> thinking about this long and hard, I think eq() and cmp() are best, esp.
> if you consider that they stand for == and <=> :)
Too short to be public API. Those could only be implementation details and
therefore need not be discussed.
--
Thiago Macieira - thiago.macieira (AT) intel.com
Cloud Software Architect - Intel DCAI Cloud Engineering
-------------- next part --------------
A non-text attachment was scrubbed...
Name: smime.p7s
Type: application/pkcs7-signature
Size: 5152 bytes
Desc: not available
URL: <http://lists.qt-project.org/pipermail/development/attachments/20230614/9c010433/attachment.bin>
More information about the Development
mailing list