Re: [Development] How qAsConst and qExchange lead to qNN
On Wed, Nov 16, 2022 at 09:50:35AM -0800, Thiago Macieira wrote: > On Tuesday, 15 November 2022 23:50:38 PST Marc Mutz via Development wrote: > > > 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, > > No, but it moves the responsibility for avoiding this problem to the user. > > Right now, you can do: > for (auto elem : object.keyList()) { > operate(); // may recurse back into object and modify it > } > > If you use a generator paradigm to return this key list, then the *user* must > know that they must create a local container with the items to be generated > and iterate over that. Performance-wise, this no different than if the Qt > code > created the container and returned it, but it has two drawbacks: > > 1) the responsibility for knowing this > > 2) if the Qt object already has a QList with this, then using a generator > paradigm enforces the need of a deep copy, when implicit would have been > cheaper Exactly. The only *safe* *and* *conveniently uniform* (a.k.a. "worth to have on a Qt API level") use of a generator as return value (e.g. something that's easily put into a delayed call) is to effectively store a full copy of the later generated elements /somewhere/ or otherwise make sure that the base object's lifetime or at least a subset of it that is capable to create the elements is extended until the generator object dies (e.g. by having some kind of ref count and COW on itself). Currently this "storing of a full copy" is bumping a reference count on an already existing Qt container, or creating one from scratch, including a deep copy. In the envisioned new world, this would always be an immediate full copy. > > > 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';')) > > BTW, http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2012r2.pdf is > accepted for C++23 and moves the end of the temporaries' lifetimes to the end > of the full for statement. > > Though we still need to work with C++17 and 20 for a while. > > Also, sometimes I wonder if all the work you and I do to optimise these > things > matter, in the end. We may save 0.5% of the CPU time, only for that to be > dwarfed by whatever QtGui, QtQml are doing. The Qt Project has easy access to a not completely trivially-sized test bed for API experiments which has stability promises one magnitude less and potential damage done to downstream dependencies several magnitudes less than similar activities in QtBase: Qt Creator. Interested parties could just check out the sources, and start running experiments without being bound to long-term API contract and with simple reverts at hand when experiments fail. I can even give a hint where to start: A rather heavily used string-ish class is Utils::FilePath, and there are at least half a dozen "obvious" candidates for an NOI: E.g. .suffix() currently returns a QString that is /always/ a copy of a part of the underlying storage, so according to my understanding this "obviously" would "have to be" a QStringView in a "new world" API. I'd be happy to see the results of the experiment from someone who thinks this kind of API is a good idea in the form of profiler results and consequences for uses of this API in "user" code. [I know mine.] Andre' ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
On Tue, Nov 15, 2022 at 08:07:50AM +, Marc Mutz via Development wrote: > On 14.11.22 23:04, A. Pönitz wrote: > >> 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"... > > QVector QRegion::rects() const _was_ removed for Qt 6.0. I was referring to the introduction of begin() and end(). The removal of QVector QRegion::rects() const was not needed, but did not particular hurt. > The trick to treat QRegion as a container of QRect, simplifying both > users and implementation of QRegion (no more vectorize()), only worked > because QRegion is home to only one collection. It's also a trick you > need to see to be able to use it, I don't think so. > so discoverability is poor. In the > general case, a given class may have more than one collection of items. > E.g. if circular windows became the new rage, QRegion could be a > collection not just of rects, but also of ellipses. Then what? > > Enter span-retuning getters: > > for (QRect rect : region.rects()) > ~~~ > for (QEllipse ell : recion.ellipses()) > ~~~ Or return per-type proxy-objects implementing begin() and end(). > [..] > There's a logical incompatibility between "QRegion as a QRect container > = good" and "QRection::rects() returning a span of QRect = bad". > > Either both are good ideas or neither are. No. The idea is that QRegion can be anything it want to /internally/, but there's a way to consider is as a container of QRects. > They both grant the same freedoms and put the same constraints on the > implementation of QRegion, but one is more flexible than the other. Returning a span requires a container somewhere /and/ adds the implicit requirement to the lifetime of this storage. It is putting /more/ constraints on the implementation of QRegion. The container-returning one is more flexible as this can be either a container that's part of the structure, or be created on-the-fly. Andre' ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
On Thursday, 17 November 2022 10:32:50 PST Elvis Stansvik wrote: > Fermat's Last QString Vectorization Update :p Everything is already set to Gerrit. What I haven't done is benchmark it to confirm the theoretical runs in LLVM-MCA. It starts at https://codereview.qt-project.org/c/qt/qtbase/+/386952 See the search at https://codereview.qt-project.org/q/ is:open+owner:thiago.macieira%2540intel.com+message:QString The changes are mostly organised as "reorganise the pre-AVX code", then "rewrite AVX2 code" then "add AVX512VL code" for each of the functions. -- Thiago Macieira - thiago.macieira (AT) intel.com Cloud Software Architect - Intel DCAI Cloud Engineering ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
On Thursday, 17 November 2022 10:24:35 PST Volker Hilsheimer via Development wrote: > > Though I am postponing the QString vectorisation update to 6.6 because I > > don't have time to collect the benchmarks to prove I'm right before > > feature freeze next Friday. > > Next Friday is the platform & module freeze. Feature freeze is not until > December 9th, i.e. another 3 weeks to go. Next Friday is also the day after Thanksgiving here in the US. I don't expect I can finish the benchmarking in 3 weeks, not considering I need to finish the IPC work and that includes starting a couple of changes that I haven't started yet (like the ability to clean up after itself). For the benchmarking, I've already collected the data by instrumenting each of the functions in question and running a Qt build, a Qt Creator start and a Qt build inside Qt Creator: qt-build-data.tar.xz: 1197.3 MB qtcreator-nosession.tar.xz: 2690.0 MB qtcreator-session.tar.xz: 35134.6 MB The data retains its intra-cacheline alignment. The way I'm seeing it, is that for each of the algorithm generations, I need to: 1) find the asymptotic limits, given L1, L2 and L3 cache sizes That is, the algorithms should be fast enough that the bottleneck is the transfer of data. There's no way that running qustrchr on 35 GB is going to be bound by anything other than RAM bandwidth or, in my laptop's case, the NVMe. So what are those limits? 2) benchmark at several data set sizes (half to 75% of L1, half to 75% of L2) on several generations Confirm that the algorithm is running close to or better than the ideal run that LLVM-MCA showed when I designed them. I know I can benchmark throughput to see if we're reaching the target bytes/cycle processing, but I don't know if I can benchmark the latency. I also don't know if it matters. 3) benchmark at several input sizes (i.e., strings of 4 characters, 8 characters, etc.) Same as #2, but instead of running over the sample that adds up to a certain data size, select the input such that the strings have always the same size. 4) compare to the previous generation's algorithm to confirm it's actually better Different instructions have different pros and cons; what might work for one at a given data size may not for another The algorithms available are: * baseline SSE2: no comparisons * SSE 4.1: compare to baseline SSE2, current SSE 4.1 * AVX2: compare to new SSE 4.1, current AVX2 * AVX512 with 256-bit vectors ("Avx256"): compare to new AVX2 I plan on collecting data on 3 laptop processors (Haswell, Skylake and Tiger Lake) and 2 desktop processors (Coffee Lake and Skylake Extreme). The Skylake should match the performance of almost all the Skylake and derivatives since 2016; the Coffee Lake NUC has the same processor as my Mac Mini; the Tiger Lake should be the performance of modern processors. The Skylake Extreme and the Tiger Lake can run the AVX512 code too. I don't know if the AVX512 code on Skylake will show a performance gain or a loss, because despite using only 256 bits, it may need to power on the OpMask registers. If it doesn't, I will adjust the feature detection to only apply to Ice Lakes and later. I have a new Alder Lake which would be nice to benchmark, to get the performance on both the Golden Cove P-core and the Gracemont E-core, but the thing runs Windows and the IT-mandated virus scans, so I will not bother. -- Thiago Macieira - thiago.macieira (AT) intel.com Cloud Software Architect - Intel DCAI Cloud Engineering ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
Den tors 17 nov. 2022 kl 18:46 skrev Thiago Macieira : > > On Thursday, 17 November 2022 02:04:54 PST Marc Mutz via Development wrote: > > > Also, sometimes I wonder if all the work you and I do to optimise these > > > things matter, in the end. We may save 0.5% of the CPU time, only for > > > that to be dwarfed by whatever QtGui, QtQml are doing. > > > > I hear you, but I'm not ready to give in just yet. > > Nor am I. > > Though I am postponing the QString vectorisation update to 6.6 because I don't > have time to collect the benchmarks to prove I'm right before feature freeze > next Friday. Fermat's Last QString Vectorization Update :p Elvis > > -- > Thiago Macieira - thiago.macieira (AT) intel.com > Cloud Software Architect - Intel DCAI Cloud Engineering > > > > ___ > Development mailing list > Development@qt-project.org > https://lists.qt-project.org/listinfo/development ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
> On 17 Nov 2022, at 18:45, Thiago Macieira wrote: > > On Thursday, 17 November 2022 02:04:54 PST Marc Mutz via Development wrote: >>> Also, sometimes I wonder if all the work you and I do to optimise these >>> things matter, in the end. We may save 0.5% of the CPU time, only for >>> that to be dwarfed by whatever QtGui, QtQml are doing. >> >> I hear you, but I'm not ready to give in just yet. > > Nor am I. > > Though I am postponing the QString vectorisation update to 6.6 because I > don't > have time to collect the benchmarks to prove I'm right before feature freeze > next Friday. Next Friday is the platform & module freeze. Feature freeze is not until December 9th, i.e. another 3 weeks to go. Volker ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
On Thursday, 17 November 2022 02:04:54 PST Marc Mutz via Development wrote: > > Also, sometimes I wonder if all the work you and I do to optimise these > > things matter, in the end. We may save 0.5% of the CPU time, only for > > that to be dwarfed by whatever QtGui, QtQml are doing. > > I hear you, but I'm not ready to give in just yet. Nor am I. Though I am postponing the QString vectorisation update to 6.6 because I don't have time to collect the benchmarks to prove I'm right before feature freeze next Friday. -- Thiago Macieira - thiago.macieira (AT) intel.com Cloud Software Architect - Intel DCAI Cloud Engineering ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
Hi Thiago, On 16.11.22 18:50, Thiago Macieira wrote: > On Tuesday, 15 November 2022 23:50:38 PST Marc Mutz via Development wrote: >>> 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, > > No, but it moves the responsibility for avoiding this problem to the user. > > Right now, you can do: >for (auto elem : object.keyList()) { >operate(); // may recurse back into object and modify it >} > > If you use a generator paradigm to return this key list, then the *user* must > know that they must create a local container with the items to be generated > and iterate over that. Performance-wise, this no different than if the Qt code > created the container and returned it, but it has two drawbacks: > > 1) the responsibility for knowing this Not necessarily. E.g. when the co-routine implementation uses the equivalent of an indexed loop, it immunizes itself from changes to the container while it's suspended. It can also post a re-entrancy guard in the class' data, like we sometimes already do in event handlers and often do in slots, to at least detect and mitigate the issue. This isn't different from emitting signals or calling virtual functions while iterating, and the solutions are the same, and, largely, if not completely, under the control of the co-routine implementation. That said, it's not entirely clear to me how widespread such issues are. After all, the user or a generator sees the potentially-re-entering code, it's in the function he's presently writing/analyzing, and not hidden in the way signal/slot connections or even virtual functions hide the issue by having far-removed code cause the problem. So I don't know whether the benefits of lazy evaluation outweight or are dwarfed by this issue. > 2) if the Qt object already has a QList with this, then using a generator > paradigm enforces the need of a deep copy, when implicit would have been > cheaper I hasten to interject here that the code you wrote above actually does deep-copy in that case (hidden detach in the for loop). Apart from that, we're circling back to the assumption that a class would hold or return a QList for the sake of QList. For holding, and also for returning, if one must return an owning container, a QVLA or otherwise SBO'd container would be more appropriate in many cases. The lack of such containers in Qt begets the use of QList in the first place. To get out of this tread-mill, one needs to look outside the Qt echo chamber, to std C++ (std::u16string, std::pmr), Folly (F14 (hash table), fbstring (SSO, CoW only for large strings)), Python (strings are QAnyString with SSO there), LLVM (llvm::SmallVector, StringRef, ArrayRef), Mozilla's JS strings (L1/UTF-16 QAnyString, SSO). Then work backwards from these kinds of containers to how we can enable them in Qt. >>> 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';')) > > BTW, http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2012r2.pdf is > accepted for C++23 and moves the end of the temporaries' lifetimes to the end > of the full for statement. Hallelujah! Thanks, Nico! > Though we still need to work with C++17 and 20 for a while. > > Also, sometimes I wonder if all the work you and I do to optimise these things > matter, in the end. We may save 0.5% of the CPU time, only for that to be > dwarfed by whatever QtGui, QtQml are doing. I hear you, but I'm not ready to give in just yet. Thanks, Marc -- Marc Mutz 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 ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
Hi Volker, On 14.11.22 19:00, Volker Hilsheimer wrote: [...] > Today, the vast majority of client code already has an owning container that > gets passed around by copy or as const references. There are important subsets that don't, e.g. the aforementioned serialisation APIs and where 10%+ executable code savings can be had: - https://codereview.qt-project.org/c/qt/qtbase/+/353688 - https://codereview.qt-project.org/c/qt/qt3d/+/263444 - https://codereview.qt-project.org/c/qt/qt3d/+/157737 > In Qt, we usually store data in the implementation in QList and QString. A > test case that artificially creates a ton of QString instances perhaps rather > falls into the small and isolated use case that doesn’t translate very well > into real applications. I disagree that tst_qsettings isn't a real-world application. Most Qt applications will have a part that does serialisation into one of JSON, QSettings, or XML, and checking the impact of QString -> QAnyStringView on a test that does makes perfect sense to me to gauge the impact on that part of the API. Granted, it's a maximum-attainable savings gauge. But it's one of the better indications we have, apart from looking at the assembly generated, on how QAnyStringView affects codegen. That said, there are also Qt apps that don't use tr() and just pass string literals for human-readable text. Those would get a similar reduction in executable code size if _all_ QString setters were replaced by QAnyStringView ones. The file APIs also suffer from the forced use of UTF-16 for paths (cf. https://bugreports.qt.io/browse/QTBUG-104095). Hands up: who has in the past been lazy and said "meh, QFile::encodeName(), I'll just skip that" > There are most certainly classes where it’d be good if we could replace that > implementation with e.g. a std::pmr::vector with an optimised allocator. And > then being stuck with a QList API forces both us and client code to construct > suboptimal data structures. And there are APIs where replacing the QString > version with QAnyStringView makes perfect sense (such as all remaining > fromString factory functions). > > But that we either replace all, or none of our APIs with something taking a > view or a span are perhaps not the only outcomes of this conversation. https://bugreports.qt.io/browse/QTBUG-101388 explicitly proposes to start where there's the most bang for the buck, and I've been following it to the letter. > Can we focus on the cases with the biggest wins, like you already did with > QRegion, QSettings, and QObject::setObjectName? What APIs in Qt that take a > QString are usually called with a string literal in real applications, rather > than with an already created QString object (that is in turn the result of > user input or reading from some storage)? Exactly what https://bugreports.qt.io/browse/QTBUG-101388 proposes. > What was the outcome of the QObject::setObjectName change for e.g. Qt Creator? I don't have numbers for QtC, but I have numbers for uic-generated code: https://codereview.qt-project.org/c/qt/qtbase/+/303859/comments/a3b3dfa2_c7b1ee67 That's not separated from the rest of the code, so I take it fits the real-world benchmark better: In particular those 1.7% are _just_ setObjectName(). Just in uic-generated code. 1.7% of executable code previously wasted on temporary QString creation in a real-life library. Gone. And the simplest code is also the most efficient: setObjectName("m_foobar"); The sad fact is that this is just rolling back the inefficiencies caused by making objectName a QString in Qt 4. > What would a QSpan-returning implementation of e.g. QObject::findChildren or > QItemSelectionModel::selectedIndexes look like? Is that even feasible without > using coroutines? It's not possible. As per my previous emails, a span can only be used to return stored contiguous data. Returning computed or non-contiguous data requires a co-routine (or callbacks) (NOIv1 vs NOIv2). Thanks, Marc -- Marc Mutz 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 ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
On Tuesday, 15 November 2022 23:28:46 PST Marc Mutz via Development wrote: > In the problematic case where a temporary container is created at the > call site for the sole purpose of providing function arguments, it's > specifically the dec, though, which is problematic (deref-to-zero has an > acquire fence). The compiler cannot prove that the atomic hasn't been > manipulated, so it can't optimize the deref out and go directly to > deallocation. This includes the case of a defaulted extra argument > (https://bugreports.qt.io/browse/QTBUG-98117), maybe not with QString, > anymore, but, most recently, with QKeySequence (which prompted the > addActions() revamp). Indeed. I'd really, REALLY love to be able to inform the compiler about the contract of reference counters and of "really const" parameter passing. I don't know if C++ contracts would help here. > Seeing as destruction of temps is sequenced before the end of the > full-expression and seeing as atomics are synchronization points, the > C++ memory model says that these atomic decs _will_ hold up execution of > the next statements. Partially. The CPU is slightly smarter than that and can continue to speculatively run past the atomic operation, so long as the behaviour is as-is to not having done so. -- Thiago Macieira - thiago.macieira (AT) intel.com Cloud Software Architect - Intel DCAI Cloud Engineering ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
On Tuesday, 15 November 2022 23:50:38 PST Marc Mutz via Development wrote: > > 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, No, but it moves the responsibility for avoiding this problem to the user. Right now, you can do: for (auto elem : object.keyList()) { operate(); // may recurse back into object and modify it } If you use a generator paradigm to return this key list, then the *user* must know that they must create a local container with the items to be generated and iterate over that. Performance-wise, this no different than if the Qt code created the container and returned it, but it has two drawbacks: 1) the responsibility for knowing this 2) if the Qt object already has a QList with this, then using a generator paradigm enforces the need of a deep copy, when implicit would have been cheaper > > 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';')) BTW, http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2012r2.pdf is accepted for C++23 and moves the end of the temporaries' lifetimes to the end of the full for statement. Though we still need to work with C++17 and 20 for a while. Also, sometimes I wonder if all the work you and I do to optimise these things matter, in the end. We may save 0.5% of the CPU time, only for that to be dwarfed by whatever QtGui, QtQml are doing. -- Thiago Macieira - thiago.macieira (AT) intel.com Cloud Software Architect - Intel DCAI Cloud Engineering ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
> On 16 Nov 2022, at 09:54, Marc Mutz via Development > wrote: […] >>> But UTF-16 is sacrosanct in Qt. It's a cult. Irregardless of how many >>> deep copies it takes to convert to and from UTF-16 from native >>> encodings, people still worship it as god-given. It's not. […] > If CoW is so superior, why did we kick out the industry-standard > unsharable state? Because otherwise the performance sucked. Well, CoW > performance sucks. Esp. for small strings, which are the majority of > strings. Folly uses CoW only for strings > 256 chars, IIRC. But CoW in > Qt is another cult, so we rather break correctness of our APIs than back > off the cult. > > It saddens me to see the project so stuck in the 90s. Hi Marc, I respect and admire your passion for this topic, and your desire to make Qt better on this front. From watching several of your Meeting C++ talks, I know that you strongly believe that this - owning containers, CoW - is a make-or-break topic for the future of Qt. And you put energy into this discussion because you want Qt to succeed also in a C++20 world, where people have perhaps adapted, and expect to be able to use, a programming style where perhaps everything is a stream or asynchronous and awaitable. But your recent use of divisive language does nothing to further your cause. Many of us are evidently not convinced that this is critical for the success of Qt, because what we experience every day when using Qt, or when looking at user feedback, is that other things are more important. And with that perspective, it’s hard to see how the cost of moving to non-owning interfaces is justified by the benefits. The cost comes in many forms, and is very tangible: making the change, dealing with the code churn, adapting to the new style, living with inconsistency across a large 3rd party eco system of libraries; and last but not least cost of opportunity and delay for other things we could spend our time on instead. On top of all that, some of our historical attempts in the project have made people weary of changes in the Core. And as a whole, we have not bought into the benefits of NOI for a real software system, or for the Qt API as a whole. Again, historical changes that were made with the promise of improvements have not always delivered, and often resulted in having to deal with a lot of code changes just to end up with something that performs worse. And if a costly change to Qt addresses none of the problems that users bring to us every day, then we should probably not spend time on it. We try to get more people over to Qt 6, in a very compelling competitive landscape with new languages, game engines, and cross-platform UI technology. The last thing we need right now is more deprecations and changes that people have to constantly chase after. So, if people don't want to change things, then it’s not because they are addicted to a cult or idiom, and I expect that you try to respect their perspective as well. Yes, this comes with the risk that we are focusing too much on the short term. But even when looking further ahead, large scale changes to a complex systems are almost never a good idea. Qt is a complex system, and we can’t predict the impact of large scale changes. But do we have to? I don’t see anyone claiming that Qt is flawless, that our current tools (including CoW) are perfect for all situations, and that we can’t or shouldn't make improvements in those places where the status quo is bad enough. Most people participating in this conversation agree that we should make respective additions to Qt, where appropriate. Can we start with that? You proposed that we add a QSpan that is only used internally until at least Qt 6.6; I assume that you had some internal use cases in mind where we can see how this would be used, discuss a real implementation, evaluate the (change of) complexity for users and for implementors, and see what we learn from that. I asked earlier: what has been the impact of the QObject::setObjectName change for a real application (one that uses .ui files will have a lot of those calls)? It’s one of the (few) APIs in Qt that is always called with a string literal. Did anyone notice anything breaking, or did it cause any porting effort? It seems like a very sensible change to me (which doesn’t mean that I believe that e.g. QLineEdit::setText needs be changed as a logical next step). But perhaps it’s also evidence that it’s a good idea to make that kind of change in selected places (like fromString factory functions). Volker ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
Hi Philippe, On 15.11.22 12:50, Philippe wrote: > On Tue, 15 Nov 2022 08:52:24 + > Marc Mutz via Development wrote: >> QAnyStringView, QUtf8StringView and, later, QAnyString, QUtf8String, >> can be used to to make UTF-8 a first-class citizen in Qt. > > Maybe, but I see a deviation from simplicity (not "Qt-ish") Do you? Then answer me this: what is QByteArray? It's not an array of bytes, because it has toLower() and toUpper(), which don't make sense for binary data. It also isn't a string, because, while it's contents are to be treated as UTF-8, QByteArray(u8"ä").toUower() != u8"Ä". Now, tell, me: What's the Qt was to convert a UTF-8 string to lower-case? Answer: fromUtf8().toLower().toUtf8() How is any of this simple? An API isn't automatically simple just because it minimizes the number of classes. A simple API is one where each class has one responsibility, and one responsibility only. Don't confuse familiarity with simplicity. A new user will have no problem with an API where QByteArray is _only_ an octet-stream and QUtf8String is _only_ a UTF-8 string. And don't even get me started on QByteArray::fromRawData(). The simplest possible string API is one where owning and non-owning containers are separate types, and we have different types for each major encoding, incl. binary: Encoding | value_type | Owning| Non-owning | Rem. | -++---++---| Latin-1 | char? | QLatin1String | QLatin11StringView | Qt 7 | UTF-8| char8_t| QUtf8String | QUtf8StringView| C++20 | UTF-16 | char16_t | QString | QStringView| | -++---++---+ any of ^ | ---| QAnyString| QAnyStringView | | -++---++---+ binary | std::byte | QByteArray| QByteArrayView | | -++---++---+ L1 is really US-ASCII, but it makes sense to not throw away the 8th bit, and go directly to L1. And no, UTF-8 is not a full replacement for L1/US-ASCII, because it is a variable-length encoding (size() check for op==, also think how to implement QString::insert(UTF-8) vs. QString::insert(L1) efficiently to see why). We have space for one more entry in QAnyStringView::Tag, so we could support UTF-32 or Local8Bit, too, going forward. This is expressive, and simple API. No more head-scratching over whether a QByteArray-taking function expects UTF-8 or binary data. No more bugs because owning containers are sometimes not NUL-terminated, even though they promise to always be. No more segfaults because QString backing data was statically allocated and the library was already unloaded (can not only happen on plugin unload, but also during shutdown, or so I'm told). You may belittle these problems as being irrelevant in practice, but it's a kind of problem that, if it strikes, leaves you dumbfounded. As opposed to statically-detectable lifetime issues with non-owning containers. I don't know about you, but I prefer *facepalm* problems readily diagnosed in the IDE over multi-night debugging sessions. >> But UTF-16 is sacrosanct in Qt. It's a cult. Irregardless of how many >> deep copies it takes to convert to and from UTF-16 from native >> encodings, people still worship it as god-given. It's not. > > If this is "sacrosanct", this is simply because many people appreciate > QString for its rich API and ease of use. This has to be respected. Note how I'm not saying to remove QString in favour of u16string. But we have a way to abstract said rich API from the underlying storage now (via QStringView). The idea is to provide rich API and ease of use for UTF-8 strings, too. > >> There's nothing inherently Qt-ish about owning containers. > > Yes and no, because owning containers are part of the very "Qt-ish" > Implicit Sharing idiom, which one is _great_ for the ease of use, safety > and optimization it provides. > > ~115 Qt classes: https://doc.qt.io/qt-6/implicit-sharing.html Regardless of how you think about implicit sharing, you must agree that Qt has perverted that: We kicked out the unsharable state, normally entered when handing out mutable references: QString c = "mouse"; auto it = c.begin(); auto copy = c; *it = 'h'; assert(copy == "mouse"); // nope: it's house, now If CoW is so superior, why did we kick out the industry-standard unsharable state? Because otherwise the performance sucked. Well, CoW performance sucks. Esp. for small strings, which are the majority of strings. Folly uses CoW only for strings > 256 chars, IIRC. But CoW in Qt is another cult, so we rather break correctness of our APIs than back off the cult. It saddens me to see the project so stuck in the 90s. Thanks, Marc -- Marc Mutz Principal Software Engineer The Qt Company Erich-Thilo-Str. 10 12489
Re: [Development] How qAsConst and qExchange lead to qNN
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 ... which is why we prefer to hand out (implicitly shared) copies rather than references. static const QMap map = ~~~; // T1 map["x"].size(); Our container classes do hand out references, because containers have to. But in other places we avoid it. best regards, Ulf ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
That's a deep copy. A deep copy of a string is obviously more expensive than the overhead of calling the QString ctor and dtor. That remains to be proven. A rule of thumb for atomics is that they're two orders of magnitude slower than a normal int. They also still act as optimizer firewalls. With that rule of thumb, copying 50 char16_t's is faster than one ref-count update. What really is the deciding point is whether or not there's a memory allocation involved. I mentioned that for many use-cases, therefore, a non-CoW SBO container is preferable over a CoW non-SBO one. As stated elsewhere, the real problem about the deep copy is the allocation involved in it, not necessarily the copying of the elements. I can't say what case is more common. What I can say is that the risk of creating deep copies sounds worse to me than the risk of calling the QString ctor and dtor too often. This is what I mean with "fuzzy". We don't really have the data to support a move to QAnyStringView for all of our API. I can say with firm belief that, _atm_, passing QString is more common. But this is a self-fulfilling fact. The tst_qsettings experiment shows what can happen if you port an API that doesn't naturally receive pre-made QStrings. Are we in a position to change how people use our APIs? I can say with firm belief that most existing applications using Qt will not be rewritten from the ground up to avoid passing QString and QList to Qt APIs. So they will pay the cost of deep copies if we force QAnyStringView and QSpan on them by changing our API. There are lots of existing Qt applications. Your CO2 calculation doesn't look that great then. New APIs that are not used in the "wrong" way all over the world, yet, are a different story. If we can provide a clean QAnyStringView or QSpan override without ambiguities, it's also a different story. (But adding complexity to the API is still a thing). Maybe we can approach this in a module-by-module way. QtWidgets is probably pretty "bad" regarding QList and QString arguments, and no one will change their old widgets applications to suit our newfangled containers. QtQml and QtQuick, on the other hand, hardly have any C++ API, and most QML and QtQuick applications are not that old. We might get away with changing API there. QtCore and QtGui is where the most difficult decisions are to be taken, I guess. best regards, Ulf ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
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 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 map["x"].size(); 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; ++__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. Thanks, Marc -- Marc Mutz 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 ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
Hi Thiago, On 15.11.22 17:25, Thiago Macieira wrote: > On Tuesday, 15 November 2022 00:52:24 PST Marc Mutz via Development wrote: >> That remains to be proven. A rule of thumb for atomics is that they're >> two orders of magnitude slower than a normal int. They also still act as >> optimizer firewalls. With that rule of thumb, copying 50 char16_t's is >> faster than one ref-count update. What really is the deciding point is >> whether or not there's a memory allocation involved. I mentioned that >> for many use-cases, therefore, a non-CoW SBO container is preferable over a >> CoW non-SBO one. > > That's irrelevant so long as we don't have SBO containers. We have not-quite-SBO-but-close QVLA for arbitrary types, and we have std::string as a stand-in for QByteArray and u16string for QString. There are also tons of such container classes in 3rd-party libraries, which suddenly become viable because we've decoupled the API from the implementation. > So what we need to really compare are memory allocations versus the atomics. A > locked operation on a cacheline on x86 will take in the order of 20 cycles of > latency on top of any memory delays[1], but do note the CPU keeps running > meanwhile (read: an atomic inc has a much smaller impact than an atomic dec > that uses the result). A memory allocation for a single byte will have an > impact bigger than this, hundreds of cycles. > > Therefore, in the case of CoW versus deep copy, CoW always wins. > > [1] https://uops.info/html-instr/INC_LOCK_M32.html says 23 cycles on an 11- > year-old Sandy Bridge, 19 on Haswell, 18 on everything since Skylake. In the problematic case where a temporary container is created at the call site for the sole purpose of providing function arguments, it's specifically the dec, though, which is problematic (deref-to-zero has an acquire fence). The compiler cannot prove that the atomic hasn't been manipulated, so it can't optimize the deref out and go directly to deallocation. This includes the case of a defaulted extra argument (https://bugreports.qt.io/browse/QTBUG-98117), maybe not with QString, anymore, but, most recently, with QKeySequence (which prompted the addActions() revamp). Seeing as destruction of temps is sequenced before the end of the full-expression and seeing as atomics are synchronization points, the C++ memory model says that these atomic decs _will_ hold up execution of the next statements. Thanks, Marc -- Marc Mutz 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 ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
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. In the meantime, I'd appreciate a short answer on how you return the keys from a stored associative map, 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). Because you pointed to QStringTokenizer and that implicitly- copies a QString. -- Thiago Macieira - thiago.macieira (AT) intel.com Cloud Software Architect - Intel DCAI Cloud Engineering ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
On Tuesday, 15 November 2022 00:52:24 PST Marc Mutz via Development wrote: > That remains to be proven. A rule of thumb for atomics is that they're > two orders of magnitude slower than a normal int. They also still act as > optimizer firewalls. With that rule of thumb, copying 50 char16_t's is > faster than one ref-count update. What really is the deciding point is > whether or not there's a memory allocation involved. I mentioned that > for many use-cases, therefore, a non-CoW SBO container is preferable over a > CoW non-SBO one. That's irrelevant so long as we don't have SBO containers. So what we need to really compare are memory allocations versus the atomics. A locked operation on a cacheline on x86 will take in the order of 20 cycles of latency on top of any memory delays[1], but do note the CPU keeps running meanwhile (read: an atomic inc has a much smaller impact than an atomic dec that uses the result). A memory allocation for a single byte will have an impact bigger than this, hundreds of cycles. Therefore, in the case of CoW versus deep copy, CoW always wins. [1] https://uops.info/html-instr/INC_LOCK_M32.html says 23 cycles on an 11- year-old Sandy Bridge, 19 on Haswell, 18 on everything since Skylake. -- Thiago Macieira - thiago.macieira (AT) intel.com Cloud Software Architect - Intel DCAI Cloud Engineering ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
On Monday, 14 November 2022 23:52:23 PST Marc Mutz via Development wrote: > Unless and until we replace all QString/QStringView overload sets with a > single QAnyStringView function (possibly accompanied by a QString > Q_WEAK_OVERLOAD), we can't make QString implicitly convertible from > char16_t literals. While we can QT_REMOVED_SINCE our own API, we can't > do the same for user API that relies on the explicit guarantee of > QString/QStringView overloadability. And then there's user library code. Our changing this would affect them, unless they make the same type of change that we do. -- Thiago Macieira - thiago.macieira (AT) intel.com Cloud Software Architect - Intel DCAI Cloud Engineering ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
On Tue, 15 Nov 2022 08:52:24 + Marc Mutz via Development wrote: > There's nothing inherently Qt-ish about owning containers. Yes and no, because owning containers are part of the very "Qt-ish" Implicit Sharing idiom, which one is _great_ for the ease of use, safety and optimization it provides. ~115 Qt classes: https://doc.qt.io/qt-6/implicit-sharing.html >QAnyStringView, QUtf8StringView and, later, QAnyString, QUtf8String, > can be used to to make UTF-8 a first-class citizen in Qt. Maybe, but I see a deviation from simplicity (not "Qt-ish") >But UTF-16 is sacrosanct in Qt. It's a cult. Irregardless of how many >deep copies it takes to convert to and from UTF-16 from native >encodings, people still worship it as god-given. It's not. If this is "sacrosanct", this is simply because many people appreciate QString for its rich API and ease of use. This has to be respected. Philippe > On 15.11.22 08:14, Ulf Hermann via Development wrote: > >>> So, if the method immediately converts whatever it gets to QList or > >>> QString, then there is no point in passing it a span or view. > >> > >> My point is that there _is_. Citing my blog post: > >> > >> callConsumeQStringHelloWorld(): > > > [...] > > > > That's the worst case scenario of passing an 8bit string literal to a > > function that takes a QString. We have QStringLiteral to avoid the 8bit > > to 16bit conversion, but I know there are more problems with that. > > > > Now lets look at the case of passing a pre-existing QString (i.e. one we > > don't have to create in place) to a function taking QAnyStringView and > > storing the result as QString. > > > > // somewhere: > > QString a; > > void setter(QAnyStringView view) { a = view.toString(); } > > > > // elsewhere: > > QString foo; > > [ ... modify foo ... ] > > setter(QAnyStringView(foo)); > > > > That's a deep copy. A deep copy of a string is obviously more expensive > > than the overhead of calling the QString ctor and dtor. > > That remains to be proven. A rule of thumb for atomics is that they're > two orders of magnitude slower than a normal int. They also still act as > optimizer firewalls. With that rule of thumb, copying 50 char16_t's is > faster than one ref-count update. What really is the deciding point is > whether or not there's a memory allocation involved. I mentioned that > for many use-cases, therefore, a non-CoW SBO container is preferable over a > CoW > non-SBO one. > > If QString and QByteArray had both efficient substringing and SBO, my > arguments would carry much less weight. But we have neither, and adding > it (esp. SBO) to QString for Qt 7 will break users, silently. Therefore > it's prudent _not_ to change QString, but to add QSmallString or > something instead. Until then, u16string and QVLA can be used, > by users and implementations. > > Any way you turn it, non-owning containers in the API make for long-term > stable APIs even as we improve our container classes in incompatible ways. > > > Which case is > > more common? And by what factor? > > > > I can't say what case is more common. What I can say is that the risk of > > creating deep copies sounds worse to me than the risk of calling the > > QString ctor and dtor too often. > > > > This is what I mean with "fuzzy". We don't really have the data to > > support a move to QAnyStringView for all of our API. > > I can say with firm belief that, _atm_, passing QString is more common. > But this is a self-fulfilling fact. The tst_qsettings experiment shows > what can happen if you port an API that doesn't naturally receive > pre-made QStrings. > > If you want to take a peek at a world without owning containers, use the > Qt 6 QXmlStreamParser API or llvm. > > With the caveat that the problem with QXmlStreamParser is that it needs > to convert to UTF-16. The vast majority of XML data is _not_ in UTF-16. > If QXmlStreamReader's API was formulated in QAnyStringView, it wouldn't > have to. We could just mmap() files and hand out QAnyStringViews to > tokens. Not going into whattaboutism here, but what about all those > unnecessary encoding conversions? Each one is a deep copy, too. UTF-16 > is a total alien in the Unix world, and, connecting to your world, I > think it's safe to assume that very few QML files are encoded in UTF-16, > either. And PySide string are also not encoded in UTF-16, I think. > > But UTF-16 is sacrosanct in Qt. It's a cult. Irregardless of how many > deep copies it takes to convert to and from UTF-16 from native > encodings, people still worship it as god-given. It's not. > > No-one would use Clang if it internally converted everything to UTF-16 > first. It would be too slow. > > QAnyStringView, QUtf8StringView and, later, QAnyString, QUtf8String, can > be used to to make UTF-8 a first-class citizen in Qt. > > Likewise, owning containers are also sacrosanct in Qt. They're a cult. > Irregardless of how
Re: [Development] How qAsConst and qExchange lead to qNN
On 14.11.22 22:17, Thiago Macieira wrote: > On Monday, 14 November 2022 12:53:19 PST Marc Mutz via Development wrote: >>> I don't think we will ever change return types. >> >> Your short interjections would be more valuable if you didn't just state >> an opinion, but also give rationale ;-) > > That's why I said "I think". > > We can't return a non-owning view because that requires that we store > internally *as* a contiguous area. The "stored" here is much more of a constraint than the "contiguous". Even associative containers can be contiguous (QFlatMap, or a sorted vector or struct { key, value }). But I wrote that for non-stored or non-contiguous coroutines returning generators can be used. No-one suggests to use spans for non-stored or non-contiguous data, so this a straw man. > If we are already doing that, we can store > as QList and return *as* QList with implicit sharing. In comparison, returning a QList requires us to store a QList. This is objectively a stronger constraint than merely string contiguous data. It's also a substantial constraint, as it excludes SBO, like in the QRegion case. > 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 See esp. https://youtu.be/tvdwYwTyrig?t=1219 for how trivial a QREMatch becomes when coroutines are available. Thanks, Marc -- Marc Mutz 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 ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
On 15.11.22 08:14, Ulf Hermann via Development wrote: >>> So, if the method immediately converts whatever it gets to QList or >>> QString, then there is no point in passing it a span or view. >> >> My point is that there _is_. Citing my blog post: >> >> callConsumeQStringHelloWorld(): > > [...] > > That's the worst case scenario of passing an 8bit string literal to a > function that takes a QString. We have QStringLiteral to avoid the 8bit > to 16bit conversion, but I know there are more problems with that. > > Now lets look at the case of passing a pre-existing QString (i.e. one we > don't have to create in place) to a function taking QAnyStringView and > storing the result as QString. > > // somewhere: > QString a; > void setter(QAnyStringView view) { a = view.toString(); } > > // elsewhere: > QString foo; > [ ... modify foo ... ] > setter(QAnyStringView(foo)); > > That's a deep copy. A deep copy of a string is obviously more expensive > than the overhead of calling the QString ctor and dtor. That remains to be proven. A rule of thumb for atomics is that they're two orders of magnitude slower than a normal int. They also still act as optimizer firewalls. With that rule of thumb, copying 50 char16_t's is faster than one ref-count update. What really is the deciding point is whether or not there's a memory allocation involved. I mentioned that for many use-cases, therefore, a non-CoW SBO container is preferable over a CoW non-SBO one. If QString and QByteArray had both efficient substringing and SBO, my arguments would carry much less weight. But we have neither, and adding it (esp. SBO) to QString for Qt 7 will break users, silently. Therefore it's prudent _not_ to change QString, but to add QSmallString or something instead. Until then, u16string and QVLA can be used, by users and implementations. Any way you turn it, non-owning containers in the API make for long-term stable APIs even as we improve our container classes in incompatible ways. > Which case is > more common? And by what factor? > > I can't say what case is more common. What I can say is that the risk of > creating deep copies sounds worse to me than the risk of calling the > QString ctor and dtor too often. > > This is what I mean with "fuzzy". We don't really have the data to > support a move to QAnyStringView for all of our API. I can say with firm belief that, _atm_, passing QString is more common. But this is a self-fulfilling fact. The tst_qsettings experiment shows what can happen if you port an API that doesn't naturally receive pre-made QStrings. If you want to take a peek at a world without owning containers, use the Qt 6 QXmlStreamParser API or llvm. With the caveat that the problem with QXmlStreamParser is that it needs to convert to UTF-16. The vast majority of XML data is _not_ in UTF-16. If QXmlStreamReader's API was formulated in QAnyStringView, it wouldn't have to. We could just mmap() files and hand out QAnyStringViews to tokens. Not going into whattaboutism here, but what about all those unnecessary encoding conversions? Each one is a deep copy, too. UTF-16 is a total alien in the Unix world, and, connecting to your world, I think it's safe to assume that very few QML files are encoded in UTF-16, either. And PySide string are also not encoded in UTF-16, I think. But UTF-16 is sacrosanct in Qt. It's a cult. Irregardless of how many deep copies it takes to convert to and from UTF-16 from native encodings, people still worship it as god-given. It's not. No-one would use Clang if it internally converted everything to UTF-16 first. It would be too slow. QAnyStringView, QUtf8StringView and, later, QAnyString, QUtf8String, can be used to to make UTF-8 a first-class citizen in Qt. Likewise, owning containers are also sacrosanct in Qt. They're a cult. Irregardless of how many deep copies it takes just to call a Qt function from Python, Java/Script or QML, people still worship them as god-given. They're not. There's nothing inherently Qt-ish about owning containers. Qt is all about allowing developers to write great applications across platforms. Ideally, we'd focus on picking up developers where they come from. That means instead of inflicting a Java-ish C++ API on Python programmers, we make PySide rock by minimizing copies between Python and Qt/C++ data structures. iow: NOI. Guys, take the blinkers off :) Thanks, Marc -- Marc Mutz 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 ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
On 14.11.22 23:04, A. Pönitz wrote: >> 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"... QVector QRegion::rects() const _was_ removed for Qt 6.0. The trick to treat QRegion as a container of QRect, simplifying both users and implementation of QRegion (no more vectorize()), only worked because QRegion is home to only one collection. It's also a trick you need to see to be able to use it, so discoverability is poor. In the general case, a given class may have more than one collection of items. E.g. if circular windows became the new rage, QRegion could be a collection not just of rects, but also of ellipses. Then what? Enter span-retuning getters: for (QRect rect : region.rects()) ~~~ for (QEllipse ell : recion.ellipses()) ~~~ Even while we wait for the circles-based GUIs to become en vogue, the .rects() is already more discoverable than pure QRegion-as-container. There's a logical incompatibility between "QRegion as a QRect container = good" and "QRection::rects() returning a span of QRect = bad". Either both are good ideas or neither are. They both grant the same freedoms and put the same constraints on the implementation of QRegion, but one is more flexible than the other. Thanks, Marc -- Marc Mutz 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 ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
On 15.11.22 03:32, Giuseppe D'Angelo via Development wrote: > > We've found a robust solution to this problem via the removed_api > mechanism, which is going to keep the QString overload, but hide it from > clients. (At least, I *think*.) This will make calls passing u"" > literals to keep working just fine. So what other concern is there for > not allowing this QString construction? The problem is that replacing a QString function with a QStringView one is highly SiC. This has nothing to do with char16_t literals, but more with char8_t (and char) literals from which QString implicitly converts, but QStringView does not. This is why we never replaced, but always overloaded, QString functions with QStringView ones in Qt 5. The Qt 6 solution is to replace QString functions with QAnyStringView, which is designed to replace an overload set consisting of - QString - QChar - QStringView - QUtfStringView (incl. char and char8_t literals) - QLatin1StringView - QByteArrayView (only for compat, to be subsumed by QUtf8StringView) with a single function in an SC way. Unless and until we replace all QString/QStringView overload sets with a single QAnyStringView function (possibly accompanied by a QString Q_WEAK_OVERLOAD), we can't make QString implicitly convertible from char16_t literals. While we can QT_REMOVED_SINCE our own API, we can't do the same for user API that relies on the explicit guarantee of QString/QStringView overloadability. Thanks, Marc -- Marc Mutz 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 ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
So, if the method immediately converts whatever it gets to QList or QString, then there is no point in passing it a span or view. My point is that there _is_. Citing my blog post: callConsumeQStringHelloWorld(): > [...] That's the worst case scenario of passing an 8bit string literal to a function that takes a QString. We have QStringLiteral to avoid the 8bit to 16bit conversion, but I know there are more problems with that. Now lets look at the case of passing a pre-existing QString (i.e. one we don't have to create in place) to a function taking QAnyStringView and storing the result as QString. // somewhere: QString a; void setter(QAnyStringView view) { a = view.toString(); } // elsewhere: QString foo; [ ... modify foo ... ] setter(QAnyStringView(foo)); That's a deep copy. A deep copy of a string is obviously more expensive than the overhead of calling the QString ctor and dtor. Which case is more common? And by what factor? I can't say what case is more common. What I can say is that the risk of creating deep copies sounds worse to me than the risk of calling the QString ctor and dtor too often. This is what I mean with "fuzzy". We don't really have the data to support a move to QAnyStringView for all of our API. best regards, Ulf ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
On Monday, 14 November 2022 18:32:36 PST Giuseppe D'Angelo via Development wrote: > I'm not sure what is meant here. I was just pointing out that > overloading a function with QString and QStringView (or QAnyStringView > for that matter) is just a historical accident: > > * we have an "old" API that takes QString > * we realize it doesn't _have to_ (e.g. string never stored, etc.) Mind you we need to determine not "it doesn't need to store now" but "it will never need to store". We have to know ahead of time that we won't need this. And if we can't overload, then we need to know this even beyond the next major version. > * we want to replace it with a "newer" API that takes QStringView. We > can't literally *replace* it (binary, and also, minor, source > compatibility). So we have to add a QStringView overload and keep both > for the entire major version. We can hide the existing one with QT_REMOVED_SINCE. So we can, effectively, replace. > * Now, if we have a QString(const char16_t *) constructor, this > overloading break SC when someone calls the API with a u"" literal, and > that's super-annoying. Right. > But the point is that if we developed the API from scratch today, we'd > likely not even had the QString overload to begin with -- we would have > averted the whole issue. I don't think that's necessarily true. But that's an orthogonal problem. Your question remains: > We've found a robust solution to this problem via the removed_api > mechanism, which is going to keep the QString overload, but hide it from > clients. (At least, I *think*.) This will make calls passing u"" > literals to keep working just fine. So what other concern is there for > not allowing this QString construction? -- Thiago Macieira - thiago.macieira (AT) intel.com Cloud Software Architect - Intel DCAI Cloud Engineering ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
Il 10/11/22 20:27, Thiago Macieira ha scritto: (We end up with these overload sets because right now we have tons of f(QString), and we're slowly moving them to f(QStringView) if it does make sense for them. Due to the BC promise we can't just_remove_ f(QString).) And we shouldn't. So we shouldn't be moving to QStringView. Where it's useful, we can use a Q_WEAK_OVERLOAD. But I insist that we only do that where there's a clear performance benefit. Otherwise, keep using QString. I'm not sure what is meant here. I was just pointing out that overloading a function with QString and QStringView (or QAnyStringView for that matter) is just a historical accident: * we have an "old" API that takes QString * we realize it doesn't _have to_ (e.g. string never stored, etc.) * we want to replace it with a "newer" API that takes QStringView. We can't literally *replace* it (binary, and also, minor, source compatibility). So we have to add a QStringView overload and keep both for the entire major version. * Now, if we have a QString(const char16_t *) constructor, this overloading break SC when someone calls the API with a u"" literal, and that's super-annoying. But the point is that if we developed the API from scratch today, we'd likely not even had the QString overload to begin with -- we would have averted the whole issue. We've found a robust solution to this problem via the removed_api mechanism, which is going to keep the QString overload, but hide it from clients. (At least, I *think*.) This will make calls passing u"" literals to keep working just fine. So what other concern is there for not allowing this QString construction? Thanks, -- Giuseppe D'Angelo | giuseppe.dang...@kdab.com | Senior Software Engineer KDAB (France) S.A.S., a KDAB Group company Tel. France +33 (0)4 90 84 08 53, http://www.kdab.com KDAB - The Qt, C++ and OpenGL Experts smime.p7s Description: Firma crittografica S/MIME ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
On Mon, Nov 14, 2022 at 04:54:20PM +, Volker Hilsheimer wrote: > > > On 12 Nov 2022, at 14:41, A. Pönitz 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
Re: [Development] How qAsConst and qExchange lead to qNN
On Monday, 14 November 2022 12:53:19 PST Marc Mutz via Development wrote: > > I don't think we will ever change return types. > > Your short interjections would be more valuable if you didn't just state > an opinion, but also give rationale ;-) That's why I said "I think". We can't return a non-owning view because that requires that we store internally *as* a contiguous area. If we are already doing that, we can store as QList and return *as* QList with implicit sharing. 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. -- Thiago Macieira - thiago.macieira (AT) intel.com Cloud Software Architect - Intel DCAI Cloud Engineering ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
On 14.11.22 18:11, Thiago Macieira wrote: > On Monday, 14 November 2022 08:22:44 PST Marc Mutz via Development wrote: >> Can we agree that NOI for setters is a no-brainer? Then 90% of the >> usefulness of NOI can already be reaped, in a BC and SC manner. There's >> pretty little we can do with return values before Qt 7, except use the >> stuff in private APIs to try it out. But we can and should convert >> setters already. > > Not without conditions. > > I can agree with that so long as it doesn't remove the ability to add the > owning equivalent as overload, even if it is a Q_WEAK_OVERLOAD, where it > benefits because the target code is going to store anyway. _If_ and for as long as the class stores the data in _that_ owning container, fine by me. That'll probably be the case for Qt 6 going forward, because of those pesky owning container return types. That said, I repeat that the goal here needs to be to let each class choose its own optimal data structure, and for a large class of cases that would be QVLA/llvm::SmallVector, iow: _something_ with a small buffer optimisation. As long as we have an implicitly-shared everywhere policy, it's CoW on top of CoW on top of CoW. If the QRegionData/Private is ref-counted, then it makes little sense to contain the QRects in a container that's _also_ CoW. Nothing is using the implict sharedness of the QRect container (rects() is gone in 6.0, and can be re-used for a QSpan getter). > I don't think we will ever change return types. Your short interjections would be more valuable if you didn't just state an opinion, but also give rationale ;-) Thanks, Marc -- Marc Mutz 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 ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
> On 14 Nov 2022, at 18:30, Marc Mutz via Development > wrote: > > Hi Ulf, > > On 14.11.22 17:37, Ulf Hermann via Development wrote: >> Hi Marc, >> >>> On 11.11.22 09:35, Ulf Hermann via Development wrote: There is an undeniable benefit of _offering_ QSpan, QStringView, and generator APIs in a few relevant cases: 1. Users want to pass a "foreign" container to a Qt function that doesn't only store it as QList or QString. It might merely iterate it or store it as something else. >>> >>> The assumption that there's a problem only for "foreign containers" is >>> incorrect: Take the native Qt container QString as an example. See >>> assembly in my QAnyStringView blog post: >>> https://www.qt.io/blog/qstringview-diaries-qanystringview You have this >>> problem as soon as you pass constant data, which is a common enough >>> use-case to warrant optimizing for. >> >> My point is the "doesn't only store it as QList or QString" and >> "foreign" is in quotes quite on purpose. Maybe that wasn't clear enough, >> though. >> >> So, if the method immediately converts whatever it gets to QList or >> QString, then there is no point in passing it a span or view. > > My point is that there _is_. Citing my blog post: […] > It matters whether the QString/QList creation is in front of or behind > the ABI boundary. The size of the tst_qsettings executable for Clang > builds went down by >10% when the API was switched over to > QAnyStringView, without any changes to the test code itself (and > QAnyStringView hasn't even got the size/tag swap that Thiago pointed out > and that would make it require less gargantuan immediate values): > https://codereview.qt-project.org/c/qt/qtbase/+/353688 > > Sure, the sequence of assembler instructions is the same, but improved > code locality means it'll also execute a bit faster. Also, if we can > condense user code by 10% without changes to their code, then some users > may have an easier time right-sizing their embedded hardware, e.g. > > > Finally, to give you the very big picture: we have a duty to avoid > inflicting such egregiously inefficient code on the world. This bloat > needs to be stored: on disk, in RAM, in cache. At each of these levels, > the extra storage causes some pJ extra energy use, billions of times > over, causing corresponding CO2 emissions. > > The German government has started dealing out the well-known "blue > angel" certificates for software now. Customers will eventually demand > our help in getting the energy use of their software down so they can be > certified. > > We can, of course, ostrich on. But Qt is in a unique position, as a C++ > native UI framework, to assist users in meeting climate goals. Think > about it: Would projects that prefer programmer productivity over > resource use inflict C++ on their programmers? Won't such projects > rather (continue to) use Java and HTML, shipping applications with a > bundled chromium to run a few HTML/JS apps in GiBs of memory instead of > MiBs (hello, MS Teams!)? To me, it sounds more logical that projects > that inflict C++ on their developers would be those projects that need > to meet resource usage goals (because of right-sizing of embedded > hardware or because they want to get certified as energy-efficient). And > all this talk about convenience trumping efficiency is going to raise > eyebrows in such projects. > > Let's make C++ easy to use, for sure. But, sorry, using spans isn't > difficult. Don't confuse familiarity with simplicity. > Hey Marc, Today, the vast majority of client code already has an owning container that gets passed around by copy or as const references. In Qt, we usually store data in the implementation in QList and QString. A test case that artificially creates a ton of QString instances perhaps rather falls into the small and isolated use case that doesn’t translate very well into real applications. There are most certainly classes where it’d be good if we could replace that implementation with e.g. a std::pmr::vector with an optimised allocator. And then being stuck with a QList API forces both us and client code to construct suboptimal data structures. And there are APIs where replacing the QString version with QAnyStringView makes perfect sense (such as all remaining fromString factory functions). But that we either replace all, or none of our APIs with something taking a view or a span are perhaps not the only outcomes of this conversation. Can we focus on the cases with the biggest wins, like you already did with QRegion, QSettings, and QObject::setObjectName? What APIs in Qt that take a QString are usually called with a string literal in real applications, rather than with an already created QString object (that is in turn the result of user input or reading from some storage)? What was the outcome of the QObject::setObjectName change for e.g. Qt Creator? What would a
Re: [Development] How qAsConst and qExchange lead to qNN
Hi Ulf, On 14.11.22 17:37, Ulf Hermann via Development wrote: > Hi Marc, > >> On 11.11.22 09:35, Ulf Hermann via Development wrote: >>> There is an undeniable benefit of _offering_ QSpan, QStringView, and >>> generator APIs in a few relevant cases: >>> >>> 1. Users want to pass a "foreign" container to a Qt function that >>> doesn't only store it as QList or QString. It might merely iterate it or >>> store it as something else. >> >> The assumption that there's a problem only for "foreign containers" is >> incorrect: Take the native Qt container QString as an example. See >> assembly in my QAnyStringView blog post: >> https://www.qt.io/blog/qstringview-diaries-qanystringview You have this >> problem as soon as you pass constant data, which is a common enough >> use-case to warrant optimizing for. > > My point is the "doesn't only store it as QList or QString" and > "foreign" is in quotes quite on purpose. Maybe that wasn't clear enough, > though. > > So, if the method immediately converts whatever it gets to QList or > QString, then there is no point in passing it a span or view. My point is that there _is_. Citing my blog post: callConsumeQStringHelloWorld(): pushq %rbp movl$12, %esi leaq.LC76(%rip), %rdx subq$32, %rsp movq%rsp, %rbp movq%rbp, %rdi callQString::fromUtf8(QByteArrayView)@PLT movq%rbp, %rdi callconsumeQString(QString const&)@PLT movq(%rsp), %rax testq %rax, %rax je .L834 lock subl $1, (%rax) je .L839 .L834: addq$32, %rsp popq%rbp ret .L839: movq(%rsp), %rdi movl$8, %edx movl$2, %esi callQArrayData::deallocate(QArrayData*, long long, long long)@PLT addq$32, %rsp popq%rbp ret callConsumeQStringViewHelloWorld(): leaq.L.str.3401(%rip), %rsi movl$12, %edi jmp consumeQStringView(QStringView)@PLT # TAILCALL It matters whether the QString/QList creation is in front of or behind the ABI boundary. The size of the tst_qsettings executable for Clang builds went down by >10% when the API was switched over to QAnyStringView, without any changes to the test code itself (and QAnyStringView hasn't even got the size/tag swap that Thiago pointed out and that would make it require less gargantuan immediate values): https://codereview.qt-project.org/c/qt/qtbase/+/353688 Sure, the sequence of assembler instructions is the same, but improved code locality means it'll also execute a bit faster. Also, if we can condense user code by 10% without changes to their code, then some users may have an easier time right-sizing their embedded hardware, e.g. Finally, to give you the very big picture: we have a duty to avoid inflicting such egregiously inefficient code on the world. This bloat needs to be stored: on disk, in RAM, in cache. At each of these levels, the extra storage causes some pJ extra energy use, billions of times over, causing corresponding CO2 emissions. The German government has started dealing out the well-known "blue angel" certificates for software now. Customers will eventually demand our help in getting the energy use of their software down so they can be certified. We can, of course, ostrich on. But Qt is in a unique position, as a C++ native UI framework, to assist users in meeting climate goals. Think about it: Would projects that prefer programmer productivity over resource use inflict C++ on their programmers? Won't such projects rather (continue to) use Java and HTML, shipping applications with a bundled chromium to run a few HTML/JS apps in GiBs of memory instead of MiBs (hello, MS Teams!)? To me, it sounds more logical that projects that inflict C++ on their developers would be those projects that need to meet resource usage goals (because of right-sizing of embedded hardware or because they want to get certified as energy-efficient). And all this talk about convenience trumping efficiency is going to raise eyebrows in such projects. Let's make C++ easy to use, for sure. But, sorry, using spans isn't difficult. Don't confuse familiarity with simplicity. Thanks, Marc -- Marc Mutz 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 ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
On Monday, 14 November 2022 08:22:44 PST Marc Mutz via Development wrote: > Can we agree that NOI for setters is a no-brainer? Then 90% of the > usefulness of NOI can already be reaped, in a BC and SC manner. There's > pretty little we can do with return values before Qt 7, except use the > stuff in private APIs to try it out. But we can and should convert > setters already. Not without conditions. I can agree with that so long as it doesn't remove the ability to add the owning equivalent as overload, even if it is a Q_WEAK_OVERLOAD, where it benefits because the target code is going to store anyway. I don't think we will ever change return types. -- Thiago Macieira - thiago.macieira (AT) intel.com Cloud Software Architect - Intel DCAI Cloud Engineering ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
> On 12 Nov 2022, at 14:41, A. Pönitz 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? 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. 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”…”? >> 1. Users want to pass a "foreign" container to a Qt function that doesn't >> only store it as QList or QString. It might merely iterate it or store it as >> something else. > > Tradionally, we had QList as the (designed to be, and in practice also > being) the "usually good enough for most uses" container, so in a /Qt/ > World, this "alien container" case /practically/ had no relevance, 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. So making it convenient for client code to use Qt APIs without having to deal with Qt containers has value. 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()}); 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 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. >> 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. 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. 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. I think it would be good if we could develop an API strategy that avoids that. 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); (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
Re: [Development] How qAsConst and qExchange lead to qNN
Hi Marc, On 11.11.22 09:35, Ulf Hermann via Development wrote: There is an undeniable benefit of _offering_ QSpan, QStringView, and generator APIs in a few relevant cases: 1. Users want to pass a "foreign" container to a Qt function that doesn't only store it as QList or QString. It might merely iterate it or store it as something else. The assumption that there's a problem only for "foreign containers" is incorrect: Take the native Qt container QString as an example. See assembly in my QAnyStringView blog post: https://www.qt.io/blog/qstringview-diaries-qanystringview You have this problem as soon as you pass constant data, which is a common enough use-case to warrant optimizing for. My point is the "doesn't only store it as QList or QString" and "foreign" is in quotes quite on purpose. Maybe that wasn't clear enough, though. So, if the method immediately converts whatever it gets to QList or QString, then there is no point in passing it a span or view. Otherwise, since we don't know what people are going to do with the method (pass foreign containers, subsets, etc), a span or view may be adequate. br, Ulf ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
Hi Ulf, On 11.11.22 09:35, Ulf Hermann via Development wrote: > There is an undeniable benefit of _offering_ QSpan, QStringView, and > generator APIs in a few relevant cases: > > 1. Users want to pass a "foreign" container to a Qt function that > doesn't only store it as QList or QString. It might merely iterate it or > store it as something else. The assumption that there's a problem only for "foreign containers" is incorrect: Take the native Qt container QString as an example. See assembly in my QAnyStringView blog post: https://www.qt.io/blog/qstringview-diaries-qanystringview You have this problem as soon as you pass constant data, which is a common enough use-case to warrant optimizing for. Aside: I find it amusing when people complain that span<>s aren't as optimal as (ptr, n) on the broken Windows ABI, but then say that all APIs should take owning containers instead. Either we care about effciency of argument passing, or not. IMHO, spans strike the optimal balance between efficiency and convenience. The next major problem is subsetting. The Qt containers (QString, QByteArray, QList) don't have efficient subsetting. Any form of parsing thus directly benefits from views, and the end of parsing will then naturally be tokens stored in views. If relevant consumer APIs don't take views, but owning containers, you're likewise injecting owning container constructions in user code, let alone memory allocations. A good API should not have such impedance mismatches between its parsers and its data consumers. And please, don't shoot the messenger. QStringRef was trying to solve the same problem, just badly (being tied to QString without being able to produce a QString from a substring w/o deep copy). Yes, there's fromRawData(), but it doesn't remove the ctor and dtor calls of the owning container. And apart from QStringLiteral, no-one uses it. So even if the class stores data in native containers, the construction of these native containers is often better done centrally instead of being duplicated in each caller. To summarize: the deep copy often happens already, in user code. By using spans, the deep copy still happens, but the code to do so isn't duplicated. > All other cases look much fuzzier to me. QSpan or QStringView (or a > generator) may be beneficial or detrimental there, depending on exact > usage pattern. The cost we're avoiding is mostly the reference count > a far cry from a deep copy. This is not correct. The ref count of owning containers is certainly not the main reason to use views. Otherwise non-implicitly-shared containers would be a solutions, which they are not. As detailed above, we're mainly avoiding the code bloat of owning container construction and destruction, as well as the deep-copy on subsetting. We also avoid accidental detaches for (auto [pos, col] : gradient.stops()) doSomthingWith(pos, col); and allow the implementation of a class to choose an optimal data structure without causing an impedance mismatch with its own API. I find it amusing when some people say that the use of NOI constrains implementations when the opposite is demonstrably true (QRegion). Both owning and non-owning interfaces have their advantages and disadvantages. Huge code bases like llvm show that you can program just as well with NOI (ArrayRef/StringRef) as with owning containers. And I think at least for setters, the benefits of NOI far outweigh the drawbacks. The only drawback I have heard is deep copy. From the same people that suggest to favour convenience over efficiency. Well, for setters, nothing is more convenient than NOI. Can we agree that NOI for setters is a no-brainer? Then 90% of the usefulness of NOI can already be reaped, in a BC and SC manner. There's pretty little we can do with return values before Qt 7, except use the stuff in private APIs to try it out. But we can and should convert setters already. > On the flip side we're introducing complex > life time problems that will lead to hard to find memory management > defects. Spans add no lifetime problems on top of .data() and/or .begin()/.end(). In particular, these are statically detectable (cf. https://github.com/isocpp/CppCoreGuidelines/blob/master/docs/Lifetime.pdf, implemented in VS 2019 and (experimentally) in Clang), so they're not "hard to find". > I suggest we look at this from the perspective of a _user_ of > Qt. I'm pretty sure you can all imagine which problem a user would > prefer here. > > So, I suggest we add those "view" APIs to the cases where they provide a > clear benefit. For methods that return a span/view/generator, we should > always offer a safe alternative that returns an owning container. The > naming convention should be: > > 1. Use overloads for methods that take views or spans. In new API we can > omit the methods that take owning containers. If the overload set grows > out of hand, don't add the view/span alternative
Re: [Development] How qAsConst and qExchange lead to qNN
> 1. Use overloads for methods that take views or spans. In new API we can > omit the methods that take owning containers. If the overload set grows > out of hand, don't add the view/span alternative until we can remove > something. By Thiago's argument, that means not to convert existing > methods to QStringView for now. > > 2. Use the postfix "View", "Span" or "Generator" for methods that return > views, spans or generators rather than owning containers. This way it's > harder for users to mess up the life time. Ad 1. Having overloads sometimes causes issues, like e.g. can't be a slot due to ambiguity when making a signal-slot connection. For this reason I believe that most (all?) of signals overloads were removed in Qt 6. The similar issues may be encountered when using QtConcurrent API. So, if you suggest suffix for Ad 2, maybe make it consistent and use the same suffix for Ad 1, too? Disclaimer: I'm not saying that adding "view" and "span" functionality into Qt is a good idea. It's a bit like giving a raw pointer of some part of your internal data into the public API (however, wrapped nicely with a fashionable envelope - so it shouldn't look so bad, right?). Yeah, in some cases it may save 1 ns for containers having thousands of items. But will it make e.g. rendering QGradient faster (when gradient stops are wrapped with span, as Mark suggested)? Especially, as it was mentioned, typical case is 2, max 3 items? Qt was always cute, because its API was designed so that it's really hard to mess things up. It looks like we are slowly dropping this beautiful keynote, unfortunately. Jarek ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
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. > 1. Users want to pass a "foreign" container to a Qt function that doesn't > only store it as QList or QString. It might merely iterate it or store it as > something else. Tradionally, we had QList as the (designed to be, and in practice also being) the "usually good enough for most uses" container, so in a /Qt/ World, this "alien container" case /practically/ had no relevance, The problem of regularly having to convert between Qt containers has been /introduced/ by people advocating QList uses by QVector, or std::vector. > 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. [Within a function, or a private class, or to access external functions it's of course fine to use anything that fits and this can be micro-optimized at will] > All other cases look much fuzzier to me. QSpan or QStringView (or a > generator) may be beneficial or detrimental there, depending on exact usage > pattern. Right. > The cost we're avoiding is mostly the reference count, a far cry > from a deep copy. On the flip side we're introducing complex life time > problems that will lead to hard to find memory management defects. I suggest > we look at this from the perspective of a _user_ of Qt. I'm pretty sure you > can all imagine which problem a user would prefer here. > So, I suggest we add those "view" APIs to the cases where they provide a > clear benefit. Right. But we should to spell out what "clear benefits" means. I /my/ world it would not be sufficient to give examples of cases where it helps, but also need weighing with the expected frequency and against the (potentially, not necessarily exitsting) costs for other/"normal" users. > For methods that return a span/view/generator, we should > always offer a safe alternative that returns an owning container. The naming > convention should be: > > 1. Use overloads for methods that take views or spans.uIn new API we can > omit the methods that take owning containers. I am not sure that in the typical case a span-only is a benefit. As said before, this leaks implementation details into the API and pessimizes the case where the user /has/ a container. > If the overload set grows out > of hand, don't add the view/span alternative until we can remove something. I oppose any scheme that makes removal of API a common case or part of the overall scheme. > By Thiago's argument, that means not to convert existing methods to > QStringView for now. That at least, right. > 2. Use the postfix "View", "Span" or "Generator" for methods that return > views, spans or generators rather than owning containers. This way it's > harder for users to mess up the life time. That's fine. Andre' ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
There is an undeniable benefit of _offering_ QSpan, QStringView, and generator APIs in a few relevant cases: 1. Users want to pass a "foreign" container to a Qt function that doesn't only store it as QList or QString. It might merely iterate it or store it as something else. 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. All other cases look much fuzzier to me. QSpan or QStringView (or a generator) may be beneficial or detrimental there, depending on exact usage pattern. The cost we're avoiding is mostly the reference count, a far cry from a deep copy. On the flip side we're introducing complex life time problems that will lead to hard to find memory management defects. I suggest we look at this from the perspective of a _user_ of Qt. I'm pretty sure you can all imagine which problem a user would prefer here. So, I suggest we add those "view" APIs to the cases where they provide a clear benefit. For methods that return a span/view/generator, we should always offer a safe alternative that returns an owning container. The naming convention should be: 1. Use overloads for methods that take views or spans. In new API we can omit the methods that take owning containers. If the overload set grows out of hand, don't add the view/span alternative until we can remove something. By Thiago's argument, that means not to convert existing methods to QStringView for now. 2. Use the postfix "View", "Span" or "Generator" for methods that return views, spans or generators rather than owning containers. This way it's harder for users to mess up the life time. best regards, Ulf ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
On Thu, Nov 10, 2022 at 03:55:39PM +, Marc Mutz via Development wrote: > Hi Ivan, > > On 10.11.22 11:03, Ivan Solovev via Development wrote: > > I wonder how your NOI-everywhere suggestion will work with the > > signal/slot connections? Specially for the case when > > Qt::QueuedConnection is used (explicitly or implicitly). > > That's a very good question, thanks. > > As a first reaction, three things come to mind: > > 1. It doesn't, obviously. If you emit signals, any signal argument must > be owning, or QueuedConnection cannot be used. Given that C++20 > requires us to mark up views and non-owning ranges (enable_view, > enable_borrowed_range), I'm confident that we could detect attempts > to create such connections at compile- or, latest, at run-time, and > refuse them, or, since we need to serialize the arguments into a > QMetaCallEvent, anyway, also lifetime-pin, either by holding a weak > reference to the source object or by storing the data in the event in > an owning container. > > 2. QML and QProperty do not appear to require passing the new value in a > NOTIFY signals's parameter, so just not passing the argument is an > option, too (but doesn't work with QueuedConnection, either). > > 3. Finally, the kinds of types I'm primarily thinking of in the context > of NOI are not Q_OBJECTS. It remains to be seen in which form or > shape co-routine-based reactive programming will complement or even > substitute signal/slots in the future. > > So, to summarize, I'd say that NOI works well with signal/slot, too, > with the exception of QueuedConnection, where we have several options > for work-arounds. In the context of Qt, a library, that is used by code in unrelated projects, which may or may not use QueuedConnection on each signal, in fact for each individual connect, I cannot even remotely think of a non-owning interface unless this is somehow "owning in disguise", i.e. offer spans as parameters but effectively stashs away a deep copy in a safe place. Could you give a hint on what these several options for work-arounds may be? Andre' ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
[ TL;DR : Qt API should use QString foo() const; void setFoo(const QString &); Neither QStringView nor C++20 changes this. ] On Wed, Nov 09, 2022 at 10:52:15AM +, Marc Mutz via Development wrote: > 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 ); > > > > 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 actions); > > This is the trivial case: setters for contiguous data. This might be trivial to implement, but the result is not obviously better. In case the actions would directly be stored in a QList this suddenly causes a full deep copy of the list. Even if we would apply this pattern only to cases where the contents not stored as such, this introduces several problems: (a) we get inconsistent API, function signatures differ without obvious user-visible reason, (b) the signatures depend on the implemenations, i.e. details of the implementation now leak into the API, (c) the implementations cannot change freely anymore as "necessary" changes to the signatures cannot be done due to the API (d) is gets spooky when a function takes several string args and needs a copy of some, but not of all > 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); Qt applications are using, well, Qt. People are of course free to store QActions in a std::vector when they dislike QList, but of the end of the day I very much prefer if people with > This is the same effect we have with Q*StringView, incl. avoidance of > caller-side construction and destruction of owning containers. It's the same effect, but the frequency of the need of a caller side construction of the owning container depend on the caller. Most notably, the caller code can even influence that by using the "right" container to start with. The "problem" you expect here only appears for code that decides to deviate from Qt style, as soon as you are consistent, it "just works". And that's actually the reason why I am opposing this kind of change. As you may know, we are migrating the Qt Creator code base towards a state where file names are not kept as QStrings but as a separate type, kind of lightweight URL containing scheme, host and path only. The structure internally has seen a few iterations now, first three, than four QStrings, now one QString as common store and three integral offsets to access the scheme/host/path (sub)strings when needed, as this means only 8 byte overhead over a plain QString, instead of the 48 bytes for the first shot using trhee full strings. I am not bound by the same kind of API promises like Qt there, and since this a quite heavily used structure I am in principle fine with even "weird" API if it helps e.g. with performance. As a consequence I actually tried a few ideas to see what helps and what not /in practice/. The result is effectively the following: 1. QStringView comes in handy when accessing these substrings. 2. ... but that's effectivly only due to the special nature of the storage here. 3. Cutting down such pieces further, comparing etc in the caller feels good, QStringView does the right thing. No copies etc. 4. ... and it doesn't look as ugly as .midRef() etc before. 5. Passing such chunks further on to local helper functions that are "implementation details" feels good. 6. That's were it stops. Most notably, going further, like using QStringView when crossing module boundaries, feels wrong, even when it technically would work in some cases. We have quite a few cases where operation are prepared but executed delayed, e.g. to not block the gui thread, or not at all, e.g. actions for context menus that are never triggered by the user. In these cases the data needs to be copied as the original can vanish. In other cases the data passes through several layers of truly expensive but unavoidable operations, so optimizing away one copy is not noticable. Think for instance what is actually needed when you want to find out, whether there's an executable "/usr/bin/qmake" on some remote device. A similar situation
Re: [Development] How qAsConst and qExchange lead to qNN
On Thursday, 10 November 2022 10:40:35 PST Giuseppe D'Angelo via Development wrote: > On 09/11/2022 20:25, Thiago Macieira wrote: > > Our API should default to passing QStrings for simplicity and not to > > confuse> > > the user. The fact that I cannot wrote: > >foo(u"bar'); > > > > if foo takes a QString is MESSED UP. > > Is this because we deliberately didn't add a QString(const char16_t *) > constructor, to prevent ambiguities in case we have overloads like these: > >void f(QString) >void f(QStringView) > > ? I think so. I understand why we did it. I just think it's wrong. > (We end up with these overload sets because right now we have tons of > f(QString), and we're slowly moving them to f(QStringView) if it does > make sense for them. Due to the BC promise we can't just _remove_ > f(QString).) And we shouldn't. So we shouldn't be moving to QStringView. Where it's useful, we can use a Q_WEAK_OVERLOAD. But I insist that we only do that where there's a clear performance benefit. Otherwise, keep using QString. > However: doesn't the new "removed API" system change the status quo? The > point of the new system is to allow us to preserve BC, while always > presenting only one of the two functions (the most recent) to client > code. f(u"foo") would therefore still compile and don't incur in the > ambiguity. > > (Surely, it's not 100% API compatible, but it would be a SIC A). It might, indeed. -- Thiago Macieira - thiago.macieira (AT) intel.com Cloud Software Architect - Intel DCAI Cloud Engineering ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
On 09/11/2022 20:25, Thiago Macieira wrote: Our API should default to passing QStrings for simplicity and not to confuse the user. The fact that I cannot wrote: foo(u"bar'); if foo takes a QString is MESSED UP. Is this because we deliberately didn't add a QString(const char16_t *) constructor, to prevent ambiguities in case we have overloads like these: void f(QString) void f(QStringView) ? (We end up with these overload sets because right now we have tons of f(QString), and we're slowly moving them to f(QStringView) if it does make sense for them. Due to the BC promise we can't just _remove_ f(QString).) However: doesn't the new "removed API" system change the status quo? The point of the new system is to allow us to preserve BC, while always presenting only one of the two functions (the most recent) to client code. f(u"foo") would therefore still compile and don't incur in the ambiguity. (Surely, it's not 100% API compatible, but it would be a SIC A). Thanks, -- Giuseppe D'Angelo | giuseppe.dang...@kdab.com | Senior Software Engineer KDAB (France) S.A.S., a KDAB Group company Tel. France +33 (0)4 90 84 08 53, http://www.kdab.com KDAB - The Qt, C++ and OpenGL Experts smime.p7s Description: S/MIME Cryptographic Signature ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
> 1. It doesn't, obviously. If you emit signals, any signal argument must >be owning, or QueuedConnection cannot be used. Given that C++20 >requires us to mark up views and non-owning ranges (enable_view, >enable_borrowed_range), I'm confident that we could detect attempts >to create such connections at compile- or, latest, at run-time, and >refuse them, or, since we need to serialize the arguments into a >QMetaCallEvent, anyway, also lifetime-pin, either by holding a weak >reference to the source object or by storing the data in the event in >an owning container. I guess compile-time checks will be quite tricky, because we will need to check the threads of both sender and receiver objects for the Qt::AutoConnection case. Is it even possible at compile time? As for the run-time check - I'm not a big fan of this approach either. It will be similar to the metatype check that we have now (and which, I believe, is finally not needed with the latest metatype changes). But I think that the code that compiles and runs, but then just refuses to emit signals, is a potential source of errors and complaints. > 3. Finally, the kinds of types I'm primarily thinking of in the context >of NOI are not Q_OBJECTS. It remains to be seen in which form or >shape co-routine-based reactive programming will complement or even >substitute signal/slots in the future. But then we will still have all the Widgets/ItemModels/Netwroking and much more around. And those will still use Qt containers. So, I would personally agree with what Volker has proposed in his answer: > We need to come up with a naming convention for getters that returns > std::span so that we can add those APIs as alternatives. > And perhaps we want symmetry between setters and getters working on spans, > rather than making std::span setters overloads. Best regards, Ivan ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
Hi Ivan, On 10.11.22 11:03, Ivan Solovev via Development wrote: > I wonder how your NOI-everywhere suggestion will work with the > signal/slot connections? Specially for the case when > Qt::QueuedConnection is used (explicitly or implicitly). That's a very good question, thanks. As a first reaction, three things come to mind: 1. It doesn't, obviously. If you emit signals, any signal argument must be owning, or QueuedConnection cannot be used. Given that C++20 requires us to mark up views and non-owning ranges (enable_view, enable_borrowed_range), I'm confident that we could detect attempts to create such connections at compile- or, latest, at run-time, and refuse them, or, since we need to serialize the arguments into a QMetaCallEvent, anyway, also lifetime-pin, either by holding a weak reference to the source object or by storing the data in the event in an owning container. 2. QML and QProperty do not appear to require passing the new value in a NOTIFY signals's parameter, so just not passing the argument is an option, too (but doesn't work with QueuedConnection, either). 3. Finally, the kinds of types I'm primarily thinking of in the context of NOI are not Q_OBJECTS. It remains to be seen in which form or shape co-routine-based reactive programming will complement or even substitute signal/slots in the future. So, to summarize, I'd say that NOI works well with signal/slot, too, with the exception of QueuedConnection, where we have several options for work-arounds. But this is definitely something we need to keep in mind, going forward, indeed. Thanks, Marc -- Marc Mutz 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 ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
Hi Marc, >> 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 ); >> >> 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 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* I wonder how your NOI-everywhere suggestion will work with the signal/slot connections? Specially for the case when Qt::QueuedConnection is used (explicitly or implicitly). Best regards, Ivan ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
A. Pönitz wrote: > [And since we are at it: It would ease my life (but possibly hamper the > life of my cardiologist) if you wouldn't call it "Qt NIH API". This is > an open insult to people who actively designed these APIs _with > different goals_ than the STL. I personally think it's completely fine > if you don't agree with these different goals, but then please accept > that these goals exist, too] Not to mention that some of the Qt APIs actually predate their STL equivalent, making the STL version the NIH one. Kevin Kofler ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
On Wednesday, 9 November 2022 11:25:32 PST Thiago Macieira wrote: > On Wednesday, 9 November 2022 02:52:15 PST Marc Mutz via Development wrote: > > We can also return spans: > > QSpan actions() const; > > we CANNOT and MUST NOT. Unless you can find a way to return the collection of QActions from: QMap actions; -- Thiago Macieira - thiago.macieira (AT) intel.com Cloud Software Architect - Intel DCAI Cloud Engineering ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
On Wednesday, 9 November 2022 02:52:15 PST Marc Mutz via Development wrote: > This is the same effect we have with Q*StringView, incl. avoidance of > caller-side construction and destruction of owning containers. Indeed, which is WRONG. Our API should default to passing QStrings for simplicity and not to confuse the user. The fact that I cannot wrote: foo(u"bar'); if foo takes a QString is MESSED UP. -- Thiago Macieira - thiago.macieira (AT) intel.com Cloud Software Architect - Intel DCAI Cloud Engineering ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
On Wednesday, 9 November 2022 02:52:15 PST Marc Mutz via Development wrote: > We can also return spans: > > QSpan actions() const; we CANNOT and MUST NOT. -- Thiago Macieira - thiago.macieira (AT) intel.com Cloud Software Architect - Intel DCAI Cloud Engineering ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
Hi, let me mention that I've been pondering all that hairy stuff just recently when trying to come up with a solution for how to map QList*> and QQmlListProperty to each other without exposing people to dangling references and without copying and allocating all the time. One thing we could do is add a special escape hatch to QSpan (or QQmlListProperty) that makes it easy to retrieve the underlying QList in the cases where it's just a thin wrapper around a complete QList. That thing could then be used to implicitly construct a (shared) QList from a QSpan. With this, you'd get the old behavior if you just pass QList around. Your QLists are just wrapped and unwrapped in QSpans when passing Qt's API boundary. However, you could also pass other things that can be expressed as QSpan, and new Qt APIs could return QSpans that are not backed by QLists. I realize that this proposal has very little to do with std::span. It also doesn't solve all of my problems with QQmlListProperty, but it might be part of a solution. best regards, Ulf ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
> On 9 Nov 2022, at 12:49, Marc Mutz via Development > wrote: > > Hi Volker, > > On 09.11.22 10:15, Volker Hilsheimer wrote: >> But I do believe that we can add APIs that are iterator and ranges friendly >> to Qt without tossing out the baby with the bathwater and without breaking >> tons of code and patterns. For APIs where we simply return a stored QList we >> want to be able to return that QList as a shared copy. If we would only >> return a std::span as a view on the stored list, then client code will need >> to construct their copy from that std::span. That is equally silly. > > There are two assumptions you are making here, and it's important to > understand them when discussing NOI: > > First, that when we return a copy of a stored QList, most users would > keep the data around for longer. > > I don't have the stats to prove it, but I'd say that that's a corner > case, not the general case. Even when talking about storing the data > temporarily in a function, no owning container is required. And if > there's no Qt API that requires you to pass owning containers, passing > from and to Qt API also drops out of the use-cases that require > returning a shared copy of a QList. The assumption that I don’t have to make is: there is a tone of working code that calls existing Qt APIs to get or set QList values on objects. That code might or might not assume that this operation is cheap as far as making a shared copy of that list is concerned. The API called might or might not take that list and store it, rather than process it and store it in some other form. I honestly don’t care. I know that the code exists and does what the people who wrote it expect it to do. We can probably in most cases add overloads to setters that take a std::span/QSpan, and construct whatever the implementation needs to store from that. But we cannot remove existing QList-taking setters, and we cannot change existing getters that return a QList to return a std::span. Even if it’s API/ABI compatible with the help from QT_REMOVED_SINCE. > Second, that the data is stored in a QList in the first place. Yes, that’s the case that I explicit mentioning in my email. It would be great to have alternatives that can return a std::span, and that doesn’t need to construct a QList or whatever in the first place. An alternative, not a replacement. I am making assumptions, you are wagering. We are not going to break if (widgets->actions().contains(myAction)) ... based on wagers and assumptions. We need to come up with a naming convention for getters that returns std::span so that we can add those APIs as alternatives. And perhaps we want symmetry between setters and getters working on spans, rather than making std::span setters overloads. Cheers, Volker SBO: short buffer optimization NOI…? Non-owning … interface? Your arguments would be easier to follow with fewer acronyms ;) > Even most Qt types don't have a need for CoW semantics of QList in their > implementation. The perceived need is coming solely from having to hand > out owning containers in the API, an operation we don't want to > deep-copy. So, catch-22. > > But imagine you /don't/ need to hand out owning containers from your > API, what container would you choose, then? Well, QRegion chose > variant>, and I've repeatedly shown how that > caused users to have to use rectCount() and boundingRect() to avoid the > conversion of the first into the latter option. > > QVersionNumber uses SBO to store short version numbers inline, only > falling back to QList if the number isn't representable as a qint8[3]. > The implementation is truly horribly complex, and the API is more > limited (the user can't iterate over the segments) than it would be if > it had not used SBO. > > I'd wager that a lot more Qt types would rather benefit from a non-CoW > SBO container than benefit from the non-SBO CoW ones we currently have. > And the use of QList in the API is holding those types back from using the > optimal data structure, because QList is under compatibility constraints, so > even where we _have_ an SBO container (e.g. std::string as a stand-in for > QByteArray or std::u16string for QString), we can't use it, because the API > uses > owning Qt containers and we can't change them. Or we do, and then we > continue the vicious cycle of Qt container churn on each major release. > > NOI can break that cycle, so the Qt containers don't need to change, > keeping existing users happy, and both the implementations and users of > our API are free to choose whatever data structure best fits their > needs, making Qt developers and new users happy. > > Win-win. > > Thanks, > Marc > > -- > Marc Mutz > 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
Re: [Development] How qAsConst and qExchange lead to qNN
Hi Volker, On 09.11.22 10:15, Volker Hilsheimer wrote: > But I do believe that we can add APIs that are iterator and ranges friendly > to Qt without tossing out the baby with the bathwater and without breaking > tons of code and patterns. For APIs where we simply return a stored QList we > want to be able to return that QList as a shared copy. If we would only > return a std::span as a view on the stored list, then client code will need > to construct their copy from that std::span. That is equally silly. There are two assumptions you are making here, and it's important to understand them when discussing NOI: First, that when we return a copy of a stored QList, most users would keep the data around for longer. I don't have the stats to prove it, but I'd say that that's a corner case, not the general case. Even when talking about storing the data temporarily in a function, no owning container is required. And if there's no Qt API that requires you to pass owning containers, passing from and to Qt API also drops out of the use-cases that require returning a shared copy of a QList. Second, that the data is stored in a QList in the first place. Even most Qt types don't have a need for CoW semantics of QList in their implementation. The perceived need is coming solely from having to hand out owning containers in the API, an operation we don't want to deep-copy. So, catch-22. But imagine you /don't/ need to hand out owning containers from your API, what container would you choose, then? Well, QRegion chose variant>, and I've repeatedly shown how that caused users to have to use rectCount() and boundingRect() to avoid the conversion of the first into the latter option. QVersionNumber uses SBO to store short version numbers inline, only falling back to QList if the number isn't representable as a qint8[3]. The implementation is truly horribly complex, and the API is more limited (the user can't iterate over the segments) than it would be if it had not used SBO. I'd wager that a lot more Qt types would rather benefit from a non-CoW SBO container than benefit from the non-SBO CoW ones we currently have. And the use of QList in the API is holding those types back from using the optimal data structure, because QList is under compatibility constraints, so even where we _have_ an SBO container (e.g. std::string as a stand-in for QByteArray or std::u16string for QString), we can't use it, because the API uses owning Qt containers and we can't change them. Or we do, and then we continue the vicious cycle of Qt container churn on each major release. NOI can break that cycle, so the Qt containers don't need to change, keeping existing users happy, and both the implementations and users of our API are free to choose whatever data structure best fits their needs, making Qt developers and new users happy. Win-win. Thanks, Marc -- Marc Mutz 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 ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
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 ); > > 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 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 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 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 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 ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
> Volker Hilsheimer via Development wrote: nice pragmatic overview :) Philippe On Wed, 9 Nov 2022 09:15:16 + Volker Hilsheimer via Development wrote: > Hi, > > > On 8 Nov 2022, at 22:20, Marc Mutz via Development > > wrote: > > To summarize: > > - I will not accept responsibility for any container rewrites in any of > > the Qt major version changes. I was not involved in any of these > > decisions, and where I was involved in the discussion, my suggestions > > were not followed. > > > Indeed. And those who were involved, and made those decisions, did so with > best intentions. > > Lets please keep a respectful tone in this conversation. Finger-pointing is > not going to get us anywhere, and I hope we can learn and come up with good > steps forward without making this personal. > > > > - I do not want to take Qt containers away from Qt users. Instead, I > > want our APIs to stop forcing our users (and us) to use (owning) Qt > > containers. > > > It would be great if we can add mechanisms in Qt that allows allows people to > iterate e.g. over the selected rows in a QItemSelectionModel, without Qt > having to create a temporary list with a ton of memory allocation, just so > that the calling code can throw the list away after iterating over it once. > There are many APIs in Qt where the returned value is going to be temporary. > QObject::findChildren is another example - we iterate over objects to create > the list, then we return the list, then client code iterates over the list to > do something with the objects (perhaps only the first). > > Thats clearly inefficient, and it impacts Qt in many places and modules. > Making a e.g. std::span available with appropriately implemented iterators > would help with that, with the possible foot-gun that people that keep that > std::span around will end up with invalidated iterators. > > But I do believe that we can add APIs that are iterator and ranges friendly > to Qt without tossing out the baby with the bathwater and without breaking > tons of code and patterns. For APIs where we simply return a stored QList we > want to be able to return that QList as a shared copy. If we would only > return a std::span as a view on the stored list, then client code will need > to construct their copy from that std::span. That is equally silly. > > I really think that we can add many of those new capabilities without causing > unnecessary churn, and without introducing incompatibilities or deprecations. > And in the few cases where we cant without seriously compromising the > end-goal or quality of the overall API, a public discussion here on the > mailing list is necessary. > > > Volker > > ___ > Development mailing list > Development@qt-project.org > https://lists.qt-project.org/listinfo/development ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
Hi, > On 8 Nov 2022, at 22:20, Marc Mutz via Development > wrote: > To summarize: > - I will not accept responsibility for any container rewrites in any of > the Qt major version changes. I was not involved in any of these > decisions, and where I was involved in the discussion, my suggestions > were not followed. Indeed. And those who were involved, and made those decisions, did so with best intentions. Let’s please keep a respectful tone in this conversation. Finger-pointing is not going to get us anywhere, and I hope we can learn and come up with good steps forward without making this personal. > - I do not want to take Qt containers away from Qt users. Instead, I > want our APIs to stop forcing our users (and us) to use (owning) Qt > containers. It would be great if we can add mechanisms in Qt that allows allows people to iterate e.g. over the selected rows in a QItemSelectionModel, without Qt having to create a temporary list with a ton of memory allocation, just so that the calling code can throw the list away after iterating over it once. There are many APIs in Qt where the returned value is going to be temporary. QObject::findChildren is another example - we iterate over objects to create the list, then we return the list, then client code iterates over the list to do something with the objects (perhaps only the first). That’s clearly inefficient, and it impacts Qt in many places and modules. Making a e.g. std::span available with appropriately implemented iterators would help with that, with the possible foot-gun that people that keep that std::span around will end up with invalidated iterators. But I do believe that we can add APIs that are iterator and ranges friendly to Qt without tossing out the baby with the bathwater and without breaking tons of code and patterns. For APIs where we simply return a stored QList we want to be able to return that QList as a shared copy. If we would only return a std::span as a view on the stored list, then client code will need to construct their copy from that std::span. That is equally silly. I really think that we can add many of those new capabilities without causing unnecessary churn, and without introducing incompatibilities or deprecations. And in the few cases where we can’t without seriously compromising the end-goal or quality of the overall API, a public discussion here on the mailing list is necessary. Volker ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
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 ); 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? 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. >> 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 ... best regards, Ulf ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
Hi Andre, When you say > You, /personally/ /you/, have been the driving force I think you and Kevin are giving me credit where none is due. The Qt containers changed multiple times before the Qt project was even formed and yours truly could have possibly had any influence on their design. Interestingly, when you say > Staying with unmodified Qt 4 containers would very likely just have > worked in Qt 6 and not have caused any problems for users upgrading Two things spring to mind: First, that it's probably not a coincidence that stability came right after Qt adopted basically the STL model. QVector was std::vector + COW, QMap was std::map + COW, and the old QGList experiment was finally put to rest as Q3PtrList. Second, that I completely agree with you. I think the decision to make Q6List be Q5Vector and add a prepend optimisation was a major mistake. We should have told people to prepare for QList -> QVector by using auto when receiving QLists from Qt, added implicit conversions between them for when users were passing in QLists, and then we should have cleaned _our_ basement by porting away from QList to QVector (we anyway did that), but leave Q5List as-is in qt5compat. I think you will find me and Peppe on record on this mailing list in the run-up to Qt 6 asking exactly that. Alas, TPTB decided, instead, to - reuse the QList name for QVector - _not_ provide Q5List in qt5compat thus breaking not only our own uses (which we had to do, anyway, in the move from QList to QVector), but also our users' code. Silently, I hasten to add. Finally, when you say > I haven't checked > explicitly, but my best guess is that Qt 4.0 (2005(!)) would > container-wise be "good enough" for my current needs. We're in violent agreement, too. Why take them from someone for whom the Qt4 containers are good enough™? From my pov, the Qt containers can be moved into a libQtQTL or something like that and enjoy their life as a slightly different approach to the STL containers. Then users that don't need Qt per-se could opt-in to using the Qt containers _only_ (and a similar argument can be made for the Qt string types; libQt7Strings; CopperSpice says to say hello), potentially drawing in more users, if they, indeed, serve a different audience than the STL ones. What I _do_ care about is that Qt atm doesn't let the user /choose/. Because of their ubiquitous use in Qt APIs, the user is forced to use the Qt containers, even though he may have a need for the STL containers (interop with 3rd-party libraries, allocators, > 2Gi elements, etc). 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. Let each user choose among the Qt, STL, Boost, Abseil, and Folly container classes on a level playing field. Now, if you like an intellectual challenge, the please explain to me how Qt's insert-or-assign is either more convenient, easier, more user-friendly, more idiomatic, or whatever, than STL's insert-or-no-op If, as you say, the Qt containers are carefully designed to a different set of goals, then I'd really like to know what the goal looks like that made Qt choose this design. To summarize: - I will not accept responsibility for any container rewrites in any of the Qt major version changes. I was not involved in any of these decisions, and where I was involved in the discussion, my suggestions were not followed. - I do not want to take Qt containers away from Qt users. Instead, I want our APIs to stop forcing our users (and us) to use (owning) Qt containers. Finally, >> 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. > string UDLs From my POV, we were fixing up a prior experiment-gone-wrong. I laid out the problems of the _qs UDL design in https://lists.qt-project.org/pipermail/development/2022-March/042335.html, so I won't repeat them here. Thanks, Marc On 08.11.22 20:32, A. Pönitz wrote: > On Mon, Nov 07, 2022 at 08:15:58PM +, Marc Mutz via Development wrote: >> Hi all, >> >> SCNR taking the proffered bait: >> >> On 07.11.22 18:51, Edward Welbourne wrote: >>> For Qt to remain relevant in the rapidly-evolving C++ landscape, it >>> needs to change; but one of its strong selling-points has long been its >>> conservatism in API and ABI, that lets working code keep working. >> >> I've not been around for the Qt 1 → Qt 2 port, but I did participate in >> all of the Qt 2 → Qt 3, Qt 3 → Qt 4, Qt 4 → Qt 5 and Qt 5 → Qt 6 >> transitions. Speaking with my Qt user hat on, I'm
Re: [Development] How qAsConst and qExchange lead to qNN
On Mon, Nov 07, 2022 at 08:15:58PM +, Marc Mutz via Development wrote: > Hi all, > > SCNR taking the proffered bait: > > On 07.11.22 18:51, Edward Welbourne wrote: > > For Qt to remain relevant in the rapidly-evolving C++ landscape, it > > needs to change; but one of its strong selling-points has long been its > > conservatism in API and ABI, that lets working code keep working. > > I've not been around for the Qt 1 → Qt 2 port, but I did participate in > all of the Qt 2 → Qt 3, Qt 3 → Qt 4, Qt 4 → Qt 5 and Qt 5 → Qt 6 > transitions. Speaking with my Qt user hat on, I'm terribly sorry to > inform y'all that Qt has largely _failed_ to keep working code working. > Sure, a trivial QWidgets program from the mid-90s may still compile and > work in Qt 6, but as soon as said program touches Qt container classes, > it's game over. Staying wiht unmodified Qt 4 containers would very likely just have worked in Qt 6 and not have caused any problems for users upgrading their applications during the last 15 years. > Both Boost and C++ itself have a much better track record of keeping > working code working, let alone any random C library. And this is > actually a fair comparison, because we're comparing apples (STL > containers) to apples (Qt containers). No. Goals of STL and Qt containers are /quite/ different. Qt offers convenience and ease of use while still being "good enough" at performance for normal use. STL can give better performance in some cases, but needs more handholding, more alertness from the user, and it's easier to get things completely wrong, also performance-wise. This is absolutely not "apples vs apples", rather (tr("Nicht alles, was hinkt, ist ein Vergleich, aber:")) "cars vs motorcycles": Sure, they have a lot in common, but none is uniformly "better" than the other. > Let's not kid ourselves thinking Qt is any special here. It isn't. We > mustn't just look at one major release cycle. Qt loses projects at every > new major release, because changes to non-core-competency parts of the > API make porting unnecessarily complicated, so that unmaintained or > understaffed projects[1] cannot muster the strength to be ported. I consider this an absurd line of reasoning coming from /you/. You, /personally/ /you/, have been the driving force behind a significant amount of /from my perspective/ unnecessary and absolutely unwelcomed changes like the removal of Q(5)List that I predicted to be and now apparently _is_ hampering "understaffed projects" (which in my book covers pretty much /every/ open source projects and quite a few commercial ones) to move from Qt 5 to Qt 6. This "loss of projects" is not the consequence of any natural law or divine intervention. Even "understaffed projects" can typically "muster the strength" to do _necessary_ adaptations to _critical_ problems. However, it is completely no surprise _to me_ that they do not want to jump through any hoop that is effectively only busy-work, not solving any real problem. > My goal with qNN is to make porting away _simple_. All that's required > is to s/qNN::/std::/ and be done. No deprecation, no sanity bot, not > even the need for local code knowledge; just simple global textual > replacement. And no regressions, if you first switch to C++NN, and only > then do the replacement. We can even retain the qNN symbols until Qt > requires C++(NN+3), because the qNN headers will be nothing but a long > list of using std::foo; at that time. > > It's not really acceptable that such trivial ports should be subjected > to all the same (or, apparently since it's done in bulk, more > restrictive) requirements than for deprecation of core-competency APIs. > The more so as I must have missed the outcry of developers when we inflicted _To me_ it is not really acceptable that (parts of) qtbase are treated as playground for experiments. [And _I_ _do_ think that e.g. calling "string literal qualifications du jour" that barely last for a year "an experiment" is fair]. In my world, deprecations ("trivial" or not) _in a library_ should only happen as a consequence of an unexpected _and_ fundamental _and_ otherwise unfixable flaws in the API, causing _real_ (not "imagined") harm for "a lot" of users. This should never be used wantonly, and definitely not pre-planned. > To hold users, a project must maintain _long-term_ API stability, not > rewrite the container classes for every major release. ... g ... > So, sorry, but as a user of Qt I'd really like to use those stable STL > classes, if only your volatile APIs let me. I'd rather I had done that > _one_ port between Qt 1 and 2 than all those ports in Qt 1-6. Again, /you/, /personally/ /you/, have been /actively/ involved and promoted and/or done part of these /breaking/ changes. I haven't checked explicitly, but my best guess is that Qt 4.0 (2005(!)) would container-wise be "good enough" for my current needs. And it's not that STL has't changed since then...
Re: [Development] How qAsConst and qExchange lead to qNN
-- Alex > -Original Message- > From: Development On Behalf Of > Volker Hilsheimer via Development > Sent: Monday, 7 November 2022 16:51 > To: Marc Mutz ; development@qt-project.org > Subject: Re: [Development] How qAsConst and qExchange lead to qNN > > > On 4 Nov 2022, at 16:00, Marc Mutz via Development project.org> wrote: > > > > Hi, > > > > After getting my head washed by Volker, lemme provide background on > > these two functions. > > Thanks for the context, Marc! > > > TL;DR: we created real maintenance and porting problems by not > > removing stop-gap functionality in a timely fashion, qNN presented as > > a way to ensure this won't happen again. > > > > Both qAsConst and qExchange are 1:1 reimplementations of std > > functionality we couldn't rely on back then (as_const is C++17 and > > exchange is C++14), so they're exactly equivalent to the std versions. > > Or were, when they were added. > > > > Neither std::as_const nor qAsConst have changed, so the replacement is > > easy: s/qAsConst/std::as_const/. It cannot not compile because > > qAsConst comes from qglobal.h and that transitively includes > > . This is what was intended from the get-go: > > > The open question is whether and when we should deprecate such a stop-gap > 1:1 reimplementations of std functionality. How to deprecate is now well > documented, but the wiki starts with the process of doing so once we concluded > that we shall. It doesn’t give us any guidance yet on how to come to that > conclusion. I had to ask Volker which wiki he refers to and maybe it is the same for somebody else too. The URL is https://wiki.qt.io/Deprecation > When it’s time to phase out one of our own qNN implementations, then > > 1) propose the change here first to raise awareness, and to give people time > to > ask questions and/or raise objections > > Even if the people doing the work all agree, a lot of maintainers and > contributors will still be impacted (at least by the tool being removed). The > proposal should come with some data about how prevalent the usage of the > relevant construct is in Qt. It makes a difference whether we’d have to touch > a > few dozen lines, or several hundred to remove all usage. > > 2) If possible, add a warning to the sanity bot so that no new usage is added > > This is trivial in some cases, not so trivial in others. Rationale: For > changes that > impact a larger amount of code, there’ll be plenty of time between those > changes getting merged, and the old Qt-implementation ultimately getting > removed or fully deprecated (which we can’t/shouldn’t do while we still have > usage in Qt itself). For example, we now have some qAsConst back in the qtbase > code. I support this. In particular, the open communication before the fact is the key here. Let's face it, Qt is large enough that it cannot be expected that everybody knows what's going on in all the modules and such changes may never hit a developer's radar until after the merge and its enforcement. I propose we add the gist of Volker's proposal to the deprecation wiki mentioned above. > Whether we then do a bulk replacement in Qt, or whether we just stop using old > stuff in new code and phase it out over time as we touch code (until here’s > perhaps little enough left to make a bulk change), depends on the discussion. > If > we do make a bulk change, then making that change in stable branches to avoid > cherry-picking conflicts would probably be ok as well (unless those branch > can’t > use the new C++ version yet). Though Volker kind of implies that his two rules might lead to cherry-picking back into older releases, I would like to see this as explicitly mentioned option wherever we document the rules. > From: Development On Behalf Of > Marc Mutz via Development >#define Q_FOREACH(decl, expr) \ > for (const auto _q_foreach_c = expr; decl : _q_foreach_c) > > And I'd probably approve it, because then that thing can actually just be > replaced > with the expansion everywhere, and then, finally, be deleted. Considering the above, this kind of change would have to be brought up to the mailing list and be discussed before any approval is given. -- Alex ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
Marc Mutz via Development wrote: > Sure, a trivial QWidgets program from the mid-90s may still compile and > work in Qt 6, but as soon as said program touches Qt container classes, > it's game over. A lot of the changes to Qt container classes were either driven by you or at least praised by you, so it is really unfair for you to now blame Qt for those changes. Those changes were mainly driven by: * raw performance over backwards compatibility, and sometimes even over API convenience, and * interoperability with new C++ and/or STL features such as std::move, both of which are causes that you have been heavily championing for all this time. Qt could have easily kept old programs just working unmodified. It is you and other "modern C++" promoters that have forced it to break compatibility at everyone else's expense. > Both Boost and C++ itself have a much better track record of keeping > working code working, let alone any random C library. And this is > actually a fair comparison, because we're comparing apples (STL > containers) to apples (Qt containers). The answer to that is that Qt just needs to stop doing incompatible changes. It is time to consider the Qt containers done and retain API and ABI compatibility forever. IMHO, that could and should have happened even with the Qt 4 version. > Let's not kid ourselves thinking Qt is any special here. It isn't. We > mustn't just look at one major release cycle. Qt loses projects at every > new major release, because changes to non-core-competency parts of the > API make porting unnecessarily complicated, so that unmaintained or > understaffed projects[1] cannot muster the strength to be ported. See above. Qt should not be doing major (i.e., API/ABI-incompatible) releases at all anymore. (Heck, you can bump the first digit if you add some great new feature, without necessarily breaking compatibility.) > My goal with qNN is to make porting away _simple_. All that's required > is to s/qNN::/std::/ and be done. No deprecation, no sanity bot, not > even the need for local code knowledge; just simple global textual > replacement. And no regressions, if you first switch to C++NN, and only > then do the replacement. We can even retain the qNN symbols until Qt > requires C++(NN+3), because the qNN headers will be nothing but a long > list of using std::foo; at that time. How about #define q17 std? Or even #define qAsConst std::as_const? If it is really a drop-in replacement as intended, that should work. (And in the unlikely event of a name conflict with user code, #undef can be used as a quick workaround.) > It's not really acceptable that such trivial ports should be subjected > to all the same (or, apparently since it's done in bulk, more > restrictive) requirements than for deprecation of core-competency APIs. > The more so as I must have missed the outcry of developers when we > inflicted > >// Universe A >Qt 1 Qt 2 Qt 3 Qt 4+5 Qt 6 >QGList -> QList -> QPtrList / QValueList -> QList / QVector -> QList >QLinkedList Oh, I did complain about the QList changes in Qt 6. You, on the other hand, have proposed (on 2017-03-18) to just "Kill QList in Qt 6" which would have broken existing application code even more than the change that was implemented. And I was not yet on this list when the Qt 4 changes happened, or I would have complained about those, too. > on an audience that could, instead, have had > >// Universe B >Qt 1 Qt 2-4 Qt 4-5 Qt 6 Qt 6-7 >QGList -> std::vector -> std::vector -> std::vector -> std::vector >CfrontC++98 C++11/14 C++17 C++20/23 But std::vector is much worse than any of the Qt containers above: no implicit sharing, uglier and less complete API, no amortized O(1) prepending optimization. The Qt 5 QList also had efficient random insertions/removals for large structures, a feature that was actually LOST in the Qt 6 version. (Yes, you can explicitly store pointers in the list to simulate the old behavior, but that means less convenient API.) But std::vector never had that feature to begin with. > To hold users, a project must maintain _long-term_ API stability, not > rewrite the container classes for every major release. Indeed. So why have you not supported keeping the Qt 5 QList in Qt 6 then? > So, sorry, but as a user of Qt I'd really like to use those stable STL > classes, if only your volatile APIs let me. I'd rather I had done that > _one_ port between Qt 1 and 2 than all those ports in Qt 1-6. I would rather have kept the Qt containers without constant porting and without losing features (which the STL never had to begin with). Did you know that Qt 3 containers actually had defined behavior on overflows? (An out-of-bounds read would always return 0, which was especially useful for strings.) That was dropped in Qt 4 in the
Re: [Development] How qAsConst and qExchange lead to qNN
Hi all, SCNR taking the proffered bait: On 07.11.22 18:51, Edward Welbourne wrote: > For Qt to remain relevant in the rapidly-evolving C++ landscape, it > needs to change; but one of its strong selling-points has long been its > conservatism in API and ABI, that lets working code keep working. I've not been around for the Qt 1 → Qt 2 port, but I did participate in all of the Qt 2 → Qt 3, Qt 3 → Qt 4, Qt 4 → Qt 5 and Qt 5 → Qt 6 transitions. Speaking with my Qt user hat on, I'm terribly sorry to inform y'all that Qt has largely _failed_ to keep working code working. Sure, a trivial QWidgets program from the mid-90s may still compile and work in Qt 6, but as soon as said program touches Qt container classes, it's game over. Both Boost and C++ itself have a much better track record of keeping working code working, let alone any random C library. And this is actually a fair comparison, because we're comparing apples (STL containers) to apples (Qt containers). Let's not kid ourselves thinking Qt is any special here. It isn't. We mustn't just look at one major release cycle. Qt loses projects at every new major release, because changes to non-core-competency parts of the API make porting unnecessarily complicated, so that unmaintained or understaffed projects[1] cannot muster the strength to be ported. My goal with qNN is to make porting away _simple_. All that's required is to s/qNN::/std::/ and be done. No deprecation, no sanity bot, not even the need for local code knowledge; just simple global textual replacement. And no regressions, if you first switch to C++NN, and only then do the replacement. We can even retain the qNN symbols until Qt requires C++(NN+3), because the qNN headers will be nothing but a long list of using std::foo; at that time. It's not really acceptable that such trivial ports should be subjected to all the same (or, apparently since it's done in bulk, more restrictive) requirements than for deprecation of core-competency APIs. The more so as I must have missed the outcry of developers when we inflicted // Universe A Qt 1 Qt 2 Qt 3 Qt 4+5 Qt 6 QGList -> QList -> QPtrList / QValueList -> QList / QVector -> QList QLinkedList on an audience that could, instead, have had // Universe B Qt 1 Qt 2-4 Qt 4-5 Qt 6 Qt 6-7 QGList -> std::vector -> std::vector -> std::vector -> std::vector CfrontC++98 C++11/14 C++17 C++20/23 To hold users, a project must maintain _long-term_ API stability, not rewrite the container classes for every major release. So, sorry, but as a user of Qt I'd really like to use those stable STL classes, if only your volatile APIs let me. I'd rather I had done that _one_ port between Qt 1 and 2 than all those ports in Qt 1-6. Well, as they say, if 20 years ago was the best time to do the switch, then the next-best time is today[2]. Anyway; to all those who disagree when I say Qt should concentrate on its core competencies and stop meddling with container classes, shared pointers, etc, I say this: which of the two universes above would you rather have lived in these past 30 years? A or B? Be truthful! Because, you see, whenever we phase out some Qt NIH API, we beam a small part of your (and our!) code from Universe A into the Universe B. I _really_ can't understand why anyone would complain. Must be a case of recency bias or something like that... Speaking of the devil, from my pov, qAsConst isn't "small enough" to survive. Fighting off patches that try to make the deleted rvalue overload "do something" is _also_ maintenance work, even if it doesn't result in a code change. Finally, I'll bet my behind (pardon my French) that at some point in the not-too-distant future someone will appear on Gerrit with a patch to change Q_FOREACH to use C++20 ranged-for-with-initializer: #define Q_FOREACH(decl, expr) \ for (const auto _q_foreach_c = expr; decl : _q_foreach_c) And I'd probably approve it, because then that thing can actually just be replaced with the expansion everywhere, and then, finally, be deleted. B Universe, here we come! Thanks, Marc [1] I need qpdfedit once every year to do my taxes, but every year it's getting harder to get the old laptop that still has it installed to run again (yes, I know of VMs) [2] Shamelessly stolen from Isodope (https://youtu.be/ESAaz9v4mSU?t=514) -- Marc Mutz 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 ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
Volker Hilsheimer (7 November 2022 16:51) wrote: > The open question is whether and when we should deprecate such a > stop-gap 1:1 reimplementations of std functionality. How to deprecate > is now well documented, but the wiki starts with the process of doing > so once we concluded that we shall. It doesn’t give us any guidance > yet on how to come to that conclusion. And, just to be clear, that omission was entirely deliberate. When I wrote [[Deprecation]], I was quite sure we did not have a consensus on the answer to that question, so what I wrote takes no position on the controversy. On the one hand, the benefits of getting rid of old cruft argue for deprecating anything for which there is now a better way to do whatever it was there for; on the other hand, disruption to users of Qt is unwelcome, arguing for only deprecating anything when its use is actually apt to cause harm (e.g. the old API incorporates a design bug). For Qt to remain relevant in the rapidly-evolving C++ landscape, it needs to change; but one of its strong selling-points has long been its conservatism in API and ABI, that lets working code keep working. There is thus an inevitable tension between "move forward, leaving the past behind" and "if it ain't broke, don't 'fix' it", Eddy. ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] How qAsConst and qExchange lead to qNN
> On 4 Nov 2022, at 16:00, Marc Mutz via Development > wrote: > > Hi, > > After getting my head washed by Volker, lemme provide background on > these two functions. Thanks for the context, Marc! > TL;DR: we created real maintenance and porting problems by not removing > stop-gap functionality in a timely fashion, qNN presented as a way to > ensure this won't happen again. > > Both qAsConst and qExchange are 1:1 reimplementations of std > functionality we couldn't rely on back then (as_const is C++17 and > exchange is C++14), so they're exactly equivalent to the std versions. > Or were, when they were added. > > Neither std::as_const nor qAsConst have changed, so the replacement is > easy: s/qAsConst/std::as_const/. It cannot not compile because qAsConst > comes from qglobal.h and that transitively includes . This is > what was intended from the get-go: The open question is whether and when we should deprecate such a stop-gap 1:1 reimplementations of std functionality. How to deprecate is now well documented, but the wiki starts with the process of doing so once we concluded that we shall. It doesn’t give us any guidance yet on how to come to that conclusion. As qAsConst has shown, it is of course useful and pragmatic for us to introduce certain functionality before the respective C++ standard is generally available to us. Let’s then assume - for the sake of argument - that we might be able to require a new standard 2 years after all generally available compilers fully implement it (C++20 has perhaps reached that point [1] if we ignore Apple clang’s limping behind on library features, so let’s say 2024). Then there’ll likely be a lot of code in Qt that is using our temporary implementation. Again, qAsConst is a good example. [1] https://en.cppreference.com/w/cpp/compiler_support The questions is now what we do. Replacing all qAsConst with std::as_const is mechanically straight forward, also thanks to the tooling that Marc and others have been building for that purpose. So as long as we follow the proposed rules, then making the change as such not the problem. And neither is reviewing - with well-tested tooling, reviewing every line of diff produced adds little value. However, such a changes will touch a lot of code, across all repositories. And that introduces conflicts when cherry-picking changes back to stable branches (and it messes up the git blame history, which for one-liners is perhaps rarely an issue). If we consider the s/count/size, s/qAsConst/std::as_const, s/QStringLiteral/u””_s, … replacement activities, then each of them might not be a big issue, but the sum of lines changed by all of them together quickly makes this become a source of bother and discontent. To a certain degree, the argument is the same as for making coding-style fixes: we don’t, unless we touch the respective code anyway. That’s not an entirely fair comparison, because we do have to maintain our Qt replacements for as long as they get used in Qt. With qAsConst, maintaining 4 lines of code would perhaps have been acceptable if it saved us that noise. With the 9 lines of qExchange, Marc has already pointed out that we have diverged from std. If we consider std::span or std::expected - we clearly don’t want to drag those implementations around with us for longer than absolutely necessary. So, there probably can’t be a single rule that fits every situation. And removing something from everyone’s toolbox, like qAsConst, might anyway deserve creating a bit more awareness. Hence, a suggestion: When it’s time to phase out one of our own qNN implementations, then 1) propose the change here first to raise awareness, and to give people time to ask questions and/or raise objections Even if the people doing the work all agree, a lot of maintainers and contributors will still be impacted (at least by the tool being removed). The proposal should come with some data about how prevalent the usage of the relevant construct is in Qt. It makes a difference whether we’d have to touch a few dozen lines, or several hundred to remove all usage. 2) If possible, add a warning to the sanity bot so that no new usage is added This is trivial in some cases, not so trivial in others. Rationale: For changes that impact a larger amount of code, there’ll be plenty of time between those changes getting merged, and the old Qt-implementation ultimately getting removed or fully deprecated (which we can’t/shouldn’t do while we still have usage in Qt itself). For example, we now have some qAsConst back in the qtbase code. Whether we then do a bulk replacement in Qt, or whether we just stop using old stuff in new code and phase it out over time as we touch code (until here’s perhaps little enough left to make a bulk change), depends on the discussion. If we do make a bulk change, then making that change in stable branches to avoid cherry-picking conflicts would probably be ok as well
[Development] How qAsConst and qExchange lead to qNN
Hi, After getting my head washed by Volker, lemme provide background on these two functions. TL;DR: we created real maintenance and porting problems by not removing stop-gap functionality in a timely fashion, qNN presented as a way to ensure this won't happen again. Both qAsConst and qExchange are 1:1 reimplementations of std functionality we couldn't rely on back then (as_const is C++17 and exchange is C++14), so they're exactly equivalent to the std versions. Or were, when they were added. Neither std::as_const nor qAsConst have changed, so the replacement is easy: s/qAsConst/std::as_const/. It cannot not compile because qAsConst comes from qglobal.h and that transitively includes . This is what was intended from the get-go: https://codereview.qt-project.org/c/qt/qtbase/+/114548 > Intended as internal API, but not put into the QtPrivate > namespace to make it simpler to use. > > We could wait for std::as_const, but that is far, far > away (just entered the current C++17 draft as of this > writing), and the Qt containers with their tendency to > detach are a problem _now_. I don't remember why sometime later we (I) documented it as public API, but I failed to add the magic sentence > Use std::as_const if you can (C++17). qAsConst will be removed is a > future version of Qt We seem to be removing these kinds of sentences from API (currently on Gerrit for Q_FOREACH), but I don't think we should. Anyway, that's qAsConst. It does what std::as_const does, so moving to The Real McCoy is trivial. Unfortunately, as it always seems to happen, both std::exchange and qExchange changed. They're still 100% equivalent, but qExchange is now equivalent to std::exchange in C++20 (constexpr) and C++23 (conditionally noexcept, technically a DR), so we can _not_ blindly replace qExchange with std::exchange, because we only require C++17. It is important to dwell on the difference between the two: std::as_const never changed, so qAsConst never had to. std::exchange changed, so it was felt that qExchange had to, too. The outcome is that we don't need to maintain qAsConst, and can trivially port away from it, but we have (continued) to maintain qExchange, and can _not_ trivially port away from it. What was the mistake? The mistake was to not limit the qExchange implementation to the C++14 version, but directly going for C++20 semantics (constexpr; mea culpa) and therefore not being able to remove qExchange when we _could_ (in 6.0, when a C++14-only qExchange and std::exchange would have been semantically identical). Instead, we kept dragging it on with unclear semantics (C++14 or C++20?), until C++23 semantics were added to qExchange() extending the lifetime even further. Had we limited qExchange to C++14 and removed qExchange when we had the chance, this lock-in would not have happened. But why do the deprecation _now_? It turns out, when we decomposed qglobal.h to speed up compilation (a high-prio request from users), we ended up with a clean qglobal.h including only other includes, except qAsConst and qExchange for which we didn't find a good home. My idea, then, was to deprecate qAsConst and qExchange, introducing q20::exchange (see below), so that they could stay in qglobal.h, itself to be deprecated in favour of the more fine-grained headers and port the code base away from qAsConst and qExchange to pave the way for rolling out the finer-grained headers. It turns out I was too slow and qAsConst and qExchange are now in qttypetraits.h (go figure), and we have problems deprecating them _there_, because the Qt deprecation macros aren't defined, yet, when that file is included in some TUs. One problem after the other. This is just to illustrate that there's a very real cost associated with keeping stop-gap reimplementations of std functionality around for longer than absolutely necessary. So, late in Qt 5 I came to experiment with how we could break this vicious cycle: On one hand, we want to provide some std library reimplementations when we just don't want to or cannot wait for them to become available. But OTOH we want these to be stop-gaps, until we can depend on a version of C++ in which they ship, and remove them as soon as possible to avoid the above-mentioned trap where we need to maintain them indefinitely. And certainly, we don't want to follow the std implementation changes lest we never catch up with the std version. For some time, I thought just making these functions private would be enough (qmemory_p.h for q_make_unique), but private API means we can't use it in public headers. Also, there's no indication when we can remove the stop-gap again. Was make_unique C++14 or C++17? Hmm, hmm... Finally, there's the problem of whitespace: assume we've solved the problem with use in public headers, and I could write auto p = q_make_unique(foo, , bar, ); I can safely