[Development] How qAsConst and qExchange lead to qNN

Marc Mutz marc.mutz at qt.io
Wed Nov 16 08:50:38 CET 2022

HI Thiago,

On 15.11.22 17:33, Thiago Macieira wrote:
> On Tuesday, 15 November 2022 01:42:55 PST Marc Mutz via Development wrote:
>>> Returning as an iteratable interface requires that we return a proxy
>>> object, like QRegularExpressionMatch, so that the solution is
>>> thread-safe. This is neither simple to understand, to code, or to port
>>> existing code over to. It also requires copying the data over (hopefully,
>>> implicitly) to the proxy object, so it doesn't solve anything.
>> I disagree on all points. QREM is complicated because we need to
>> shoehorn a coroutine into an iterator concept. Same with
>> QStringTokenizer. Coroutines with lazy sequences (generator<>) are very easy
>> to implement and use.
>> If we're to discuss further, please watch my Meeting C++ presentation,
>> which lays out all the pros and cons I'm aware of. No need to re-iterate
>> them in text here. https://youtu.be/tvdwYwTyrig
> It will take some time to watch it.

Thanks. 2× is about the right speed :)

> In the meantime, I'd appreciate a short answer on how you return the keys from
> a stored associative map,

   std::generator<Key> func() {
      for (auto &[key, value] : m_assoc_cont)
          co_yield key;

> in a thread-safe manner (such that if something in
> the same thread or another thread-safely modifies that map, the original user
> isn't affected).

The above isn't thread-safe, it isn't even re-entrant, in the same way 
that iteration using iterators isn't. This is a known issue whenever you 
hand out references, and it's nothing that violates our 
const-is-thread-safe promise, otherwise

     static const QMap map = ~~~;
     // T1

would also be affected. The promise doesn't end when you return from the 
const member function, it extends to references handed out in the 
process (op[], begin, end, any lazy sequence).

Of course, in special cases where you have the need for actual 
thread-safety, some form of owning container will be required. But then 
we're talking about maybe 0,1% of all APIs, if even that many.

> Because you pointed to QStringTokenizer and that implicitly-
> copies a QString.

That's imprecise. QStringTokenizer extends rvalue lifetimes ("rvalue 
pinning") so's to make this safe:

    for (auto part : qTokenize(label->text(), u';'))

even though the return value of label->text() would ordinarily be 
destroyed at the end of the full-expression, which, seeing the way 
ranged-for is specified:

        auto && __range = qTokenize(label->text(), u';');
        // would be here
        auto __begin = begin-expr(__range);
        auto __end = end-expr(__range);
        while (__begin != __end) {
            decl = *__begin;

iow: too early. It does this only for rvalue owning containers, not for 
lvalues and not for views (!borrowed_range in C++20 ranges parlor). This 
has nothing to do with multi-threading.


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

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

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