[Development] How qAsConst and qExchange lead to qNN

Marc Mutz marc.mutz at qt.io
Wed Nov 9 11:52:15 CET 2022


Hi Ulf,

On 09.11.22 08:18, Ulf Hermann via Development wrote:
>> I don't want to take the Qt containers away from Qt users. I want to get
>> rid of their use in our APIs, so that both the Qt implementation as well
>> as our users are free to choose the best container for their needs
>> instead of having to pick from one of the public Qt containers.
> 
> I would like to know how that is supposed to work in practice. We have a 
> lot of public API dealing with Qt containers all over. What are you 
> going to do to, for example, to
> 
>      void addActions(const QList<QAction*> &actions);
> 
> in qwidget.h? What should it look like when we're done and our users are 
> free to choose the best container for their needs?

In this particular case, the API would be

    void addActions(QSpan<QAction*> actions);

This is the trivial case: setters for contiguous data. It's 
backwards-compatible, because QList implicitly converts to QSpan (and 
std::span), but now, as a user, I can also pass a C array or std::vector 
or QVLA of QAction*:

    QAction *actions[] = {
       new FooAction(this),
       new BarAction(this),
    };
    addActions(actions);

This is the same effect we have with Q*StringView, incl. avoidance of 
caller-side construction and destruction of owning containers.

This is NOIv1: https://www.youtube.com/watch?v=JUUID0_NvQI

We can also return spans:

    QSpan<QAction*> actions() const;

but this only works, though, if we're returning a) stored and b) 
contiguous data. If we return computed or non-contiguous data, instead, 
we can, however, use coroutines:

   std::g/QGenerator<QModelIndex> selectedIndexes() const;

This is NOIv2: https://www.youtube.com/watch?v=tvdwYwTyrig

The benefit here isn't only that the implementation data structure is 
encapsulated, but also that, for computed values, the computation stops 
when the consumer stops consuming.

The drawback is that the memory allocation of the coroutines' stack 
frame is not elidable (crosses ABI boundaries), but when compared to 
returning computed owning containers, you don't have more, and typically 
have much less, memory allocations.

> Mind that I'm specially interested in this because I'm currently facing 
> a similar problem, making different container types available in QML. 
> QML has its own poor man's "range" type in the form of QQmlListProperty 
> and it's terrible.

I don't know the QML landscape nearly as well as I should, but I would 
invite you to consider the role coroutines can play in implementing 
reactive programming environments such as QML and QProperty, even if we 
cannot rely on them atm.

>>   >> Q_FOREACH
>>   > [I can make 100% correct predictions about changes I intent to push,
>>   > too. What's the point?]
>>
>> I have no desire to touch the implementation of Q_FOREACH, ever. I did,
>> unwillingly, when its users suffered unnecessary pessimisations in the
>> past, but the port to C++20 ranged-for-with-init is not of that kind.
> 
> Waiting for someone to push a patch with code you've already outlined 
> and then approving it is pretty much the same as changing it yourself. 
> You just don't need any approval for that ...

I can honestly say that I was surprised about the effect of the C++20 
feature on the Q_FOREACH implementation. I was just predicting by
extrapolating from  the last change to Q_FOREACH (using C++17
if-with-init), and wasn't  aware of the consequences until after writing
down the implementation :)

Thanks,
Marc

-- 
Marc Mutz <marc.mutz at qt.io>
Principal Software Engineer

The Qt Company
Erich-Thilo-Str. 10 12489
Berlin, Germany
www.qt.io

Geschäftsführer: Mika Pälsi, Juha Varelius, Jouni Lintunen
Sitz der Gesellschaft: Berlin,
Registergericht: Amtsgericht Charlottenburg,
HRB 144331 B



More information about the Development mailing list