[Development] How qAsConst and qExchange lead to qNN
A. Pönitz
apoenitz at t-online.de
Mon Nov 14 23:04:51 CET 2022
On Mon, Nov 14, 2022 at 04:54:20PM +0000, Volker Hilsheimer wrote:
>
> > On 12 Nov 2022, at 14:41, A. Pönitz <apoenitz at t-online.de> wrote:
> >
> > On Fri, Nov 11, 2022 at 09:35:27AM +0100, Ulf Hermann via
> > Development wrote:
> >> There is an undeniable benefit of _offering_ QSpan, QStringView,
> >> and generator APIs in a few relevant cases:
> >
> > This is true, but my problem with this is that already _offering_
> > additional solutions does not come for free: It bloats the API, it
> > bloats docs, introducing the first overload is SIC etc.
>
>
> With QT_REMOVED_FROM we can remove the “first overload” problem, and
> replace QString with QAnyStringView.
>
> We could use QAnyStringView in e.g.. QFont::fromString or
> QPageRanges::fromString or QKeySequence::fromString (the obvious
> candidates in Qt Gui). And then I can write either of
>
>
> fromString(“foo”); fromString(u“foo”); fromString(u"foo”_s);
> fromString(stdString); fromString(qString);
>
> Is that not a good idea?
Judging from the frequency of this kind of operation in "my" code, my
answer is "No": The main issue is "Opportunity cost": The whole activity
is - at best - neutral. This kind of optimization has no end-user-visible
effect, even when scaling to a hundred uses per day. On the other side,
it eats resources, preventing to fix real, user-visible problems
(including /performance/ btw).
[Besides: Wasn't it that QAnyStringView drops the QString reference
count, i.e. even /pessimizes/ the so-far not-so-uncommon way of
operating on pre-existing QString objects?]
> Perhaps it isn’t because there is now a fraction of our API that I can
> call that way, why for the vast majority I have to always use
> Qt::StringLiterals and call with a u”…”_s.
No. You don't /have/ to, it is your choice. You can just use the
QString you have, or one you create ad-hoc, and no end-user will notice.
For Qt itself, it might be prudent to use the prefered string decoration
du jour for the implementation, but on an /application/ level outside
really heavy duty code, there's no need to even add a 'u' to a string
literal. Yes, there will by copies, yes, there will be allocation, and
no, people won't notice. And environment-wise there will be more cycles
burnt on creating, and reviewing, and integrating the Qt patch.
> But how can we make progress with our API otherwise? Should we accept
> that we will never be able to call a QString-taking setter with u”…”?
"Making changes, just because we can" is no progress. As written in
another mail recently, I'd pretty much prefer if Qt API gets only
changed if there's are real mistake to fix (nowadays, with most Qt 5
code not yet ported, actually a "after paddling back on some of the
Qt5->6 changes").
> [...]
> I don’t think we can be certain that the amount of client code that
> use non-Qt containers and only deals with Qt containers at the API
> boundary is negligible. I would rather assume that only a minority of
> Qt applications *only* use Qt. The slice of the world that truly is a
> “Qt World” is perhaps rather small, while the majority of applications
> is a messy, organically grown mix of different frameworks and
> libraries. In those, most thing are alien to most other things, and
> interoperability on various levels is important.
The main operation of any backend with Qt is "using Qt as a GUI", with
mostly high-level building blocks on the Qt side. Creating a copy of a
string to interface such a block, like rendering this string on a
display, or specifying a font, or a key sequence does not have to be
micro-optimized _when this otherwise impacts API_. And it does not even
have to be micro-optimized when it does _not_ impact API, see
"opportunity cost".
> So making it convenient for client code to use Qt APIs without having
> to deal with Qt containers has value.
Very limited value in my book.
> It is possible today - although
> perhaps under-documented, esp since we removed to/fromStdVector and
> to/fromStdList from Qt 6:
>
> setList({svector.cbegin(), svector.cend()});
[Btw: There is a self-fulfilling prophecy regarding the badness of Qt's
or generally owning containers here: By repeating the claim that e.g.
QList is bad or that full-container operations are bad, or anything that
is not the equivalent of std::containers is bad, the seed was planted to
remove some of Qt container's intrinsic benefits (ease of use...) or at
Qt5->6 even full classes.]
> This is not great, but as long as client code is anyway operating on
> owning containers, it’s perhaps as good as we can reasonably make it.
The "improvement" of having to use
setList({svector.cbegin(), svector.cend()});
already struck back at peoply converting
setList(functionReturningList())
to
setList({functionReturningContainer().begin(),
functionReturningContainer().end())}
Being able to pass around owning containers cheaply / without having to
worry about performace in each line is one of the benefits of /Qt style/
interface.
> > The problem of regularly having to convert between Qt containers has
> > been /introduced/ by people advocating QList uses by QVector, or
> > std::vector.
>
> Qt 3’s QValueList was implicitly constructible from std::list. We
> added it because people asked for easier integration of Qt with
> STL-using code.
While I have actually used Qt 3, even in earnest, I'd don't want to
extend every claim I make in 2022 to "all containers of Qt 1, 2, 3".
My personal baseline would be Qt 4(.2), i.e. ~2006.
> >> 2. Assume a container that isn't internally stored as QList or
> >> QString, but is returned from a Qt function. Users want to use that
> >> thing as something else than QString or QList. For example they
> >> might merely iterate it or store its contents in a "foreign"
> >> container.
> >>
> >> In those cases, using QList or QString as transfer mechanism
> >> induces an unnecessary deep copy.
> >
> > This is pretty much the same problem: Standardizing on QString and
> > QList as the "primary" containers avoids these problems on a large
> > scale, and this overall gain outweighs the effect local
> > micro-optimizations by far. The problem is that this (un-)balance is
> > hard to communicate, as it is very easy to demonstrate that small,
> > isolated uses of QString and QList are suboptimal, but the /overall/
> > benefit of a uniform approach only kicks in at "real world"-sized
> > applications.
>
>
> For applications that are not “pure Qt" applications - and I believe
> that is not an insignificant number, perhaps it’s even the majority -
> standardizing on Qt data types is simply not an option.
No, and these applications then have to consider Qt as "external",
and convert their data when accessing Qt functionality.
I have still to see an example where having to create an ad-hoc copy
of the string data in e.g. QPainter::drawText() will have any impact
on the user.
> And Ulf’s case here is anyway that “there is no QList or QString” that
> is internally stored and that we just need to return. There might not
> even be any list-or string-like datastructure stored.
So create it. But don't pessimize code and hurt applications that so far
used it and were happy with it, or even relied on it, or worse, leave
them behind.
I think one issue that is again overlooked in this discussion is that
even a tiny extra incompability might be the straw that breaks the
camel's neck when it comes to porting forward or not.
> QItemSelectionModel doesn’t store a QList of selected indices. And yet
> QItemSelectionModel::selectedIndexes returns a QModelIndexList that is
> rather costly to create. An application that just wants to iterate
> over all selected indexes already has to pay an unnecessary cost.
> An application that then doesn’t use Qt containers in client code pays an
> extra cost on top.
Right, and I readily agree that there are /rare/ cases where this is a
worth a consideration.
In this particular case, there could be thousands, or even millions
of items. So there could be a
QItemSelectionModel::forEachSelectedIndex(
const std::function<void(const QModelIndex &)> &)
avoiding /any/ temporary container (/and/ any kind of returned span for
that matter)
One can (to some degree...) even get away with
for (const QModelIndex &mi : model.someName())
with someName() providing only proxy with begin/end. No need to touch
existing API, though, that are API addition, leaving all previous uses
100% unharmed.
> I think it would be good if we could develop an API strategy that
> avoids that.
That's simple...
The first approximation to getting answers is running
yes no
in a shell and get inspiration from its output ;-)
Second approximation would take /provable/ user pain into account
and balance this against porting effort of existing users.
This will quite likely result in 'no change' outside Qt Core, and except
for QString/QByteArray themselves also not touch a lot even there.
> Marc’s proposal of a Non-Owning Interface is already
> become manifest in QRegion::begin/end
>
> https://doc.qt.io/qt-6/qregion.html#begin
>
> allowing us to write
>
>
> for (auto rect : region) doSomethingWith(rect);
Yes, and that's fine [but not quite matching the rest of the discussion
of using spans?]
> (while QRegion::rects will have to create a QList even if there is
> only a single rect in the inline storage, which is then not a QList).
>
> This is a *good* addition to Qt. I think we can make more such
> additions to Qt, in places where it makes a real difference for
> today’s client code using owning containers, and without changing the
> world.
Fine with me.
With emphasis on "addition" and "real", and notable absense of "change"
and "removal"...
Andre'
More information about the Development
mailing list