About QString initialization and QT_NO_CAST_FROM_ASCII

Here's the place for discussion related to coding in FreeCAD, C++ or Python. Design, interfaces and structures.
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
Post Reply
aapo
Posts: 615
Joined: Mon Oct 29, 2018 6:41 pm

About QString initialization and QT_NO_CAST_FROM_ASCII

Post by aapo »

Hello!

As can be seen from wmayer's post https://forum.freecadweb.org/viewtopic. ... 20#p552169, FreeCAD has an intentional #define QT_NO_CAST_FROM_ASCII, which makes certain QString constructor private, and consequently disables implicit casting to QString from ascii strings. Presumably it has been decided, because it encourages the coder to use the tr() mechanism for translatable user-visible strings, but it has also other advantages, because the coder has to think what is actually going to be converted to QString. Is it user input or read from file, and possibly having uft-8, utf-16, wide strings or plain ascii? Or is it a simple ASCII string literal?

Anyways, reading documentation I discovered some things that were new to me, and I'd like to share them. The main conclusion is that for intialization with string literals one should always use either QStringLiteral or QLatin1String, as both are compile-time constructed. The difference with the two is that QStringLiteral is proper utf-8 and internally uses 2 bytes per character in the executable, but QLatin1String is one-byte latin1. Also, QStringLiteral is not able to re-use the same literals, but reserves room in the excutable (and memory) for each repetition of the same literal in the source code, whereas QLatin1String can share the space for identical literals. So, QLatin1String is faster in certain rare situations, but casting it to QString causes a dynamical creation of QString and conversion, so in most cases it ends up being slower and eating up resources. At least TechDraw module cpp sources are littered with non-optimal QString initializations, which are both slow and eat up lot of resources. So, this is the motivation to write this post. And please comment here, if some of these assumptions are not valid, so I can edit this post accordingly!

Correct usage:

Code: Select all

QString str = QStringLiteral("\\d*.\\d*");  // Correct, makes a compile-time constant
str.append(QLatin1String("\\d*.\\d*"));  // Correct, makes a compile-time constant and QString::append() has fast QLatin1String overload!
QString str = tr("Press any key.");  // Correct, we'll want to translate this
QString str = QString::fromStdString(standard_utf8_string);  // Correct, QString::fromStdString assumes utf-8, but works with ascii or latin1, too
QString str = QString::fromStdString(standard_latin1_string);  // Correct, QString::fromStdString assumes utf-8, but works with ascii or latin1, too
QString str = QString::fromStdU16String(standard_utf16_string);  // Correct, supported from Qt 5.5, FreeCAD 0.20 is gonna require Qt 5.9 
QString str = QString::fromStdU32String(standard_utf32_string);  // Correct, supported from Qt 5.5, FreeCAD 0.20 is gonna require Qt 5.9 
QString str = QString::fromStdWString(standard_wide_string);  // Correct, supports both Windows and Unix variants of the format 
Correct, but not optimal:

Code: Select all

QString str = QString::fromUtf8("\\d*.\\d*");  // Works, but slow: creates an extra run-time QString object and copies it
QString str = QString::fromLatin1("\\d*.\\d*");  // Works, but slow: creates an extra run-time QString object and copies it
QString str = QLatin1String("\\d*.\\d*");  // Works, but slow: creates an extra run-time QString object and copies it
QString str = QString::fromUtf8(standard_utf8_string.c_str());  // Works, but the conversion to C-string is unnecessary
Wrong:

Code: Select all

QString str = tr("\\d*.\\d*");  // WRONG, we certainly don't want to translate regular expressions!
QString str = QStringLiteral("Press any key.");  // WRONG, we'd certainly want to translate this user-visible string
QString str = QString::fromLatin1(standard_utf8_string.c_str());  // WRONG, you'll destroy all non-ASCII characters outside latin1
So, in conclusion: Use QStringLiteral and tr() for QString initialization. In some places use QLatin1String if you're sure what you do, and need that extra bit of speed and memory efficiency.

Here are some links for extra information:
QStringLiteral is preferred by Qt documentation: https://doc.qt.io/qt-5/qstring.html#QStringLiteral
When QLatin1String is better than QStringLiteral: https://forum.qt.io/topic/78540/qstring ... tin1string
What is optimal for converting std::string to QString: https://stackoverflow.com/questions/236 ... to-qstring
About QStringLiteral not being always optimal: https://www.kdab.com/qstringview-diarie ... ngliteral/
wmayer
Founder
Posts: 20241
Joined: Thu Feb 19, 2009 10:32 am
Contact:

Re: About QString initialization and QT_NO_CAST_FROM_ASCII

Post by wmayer »

When concatenating several strings with the + operator then this article is interesting: https://www.qt.io/blog/efficient-qstrin ... xpressions
aapo
Posts: 615
Joined: Mon Oct 29, 2018 6:41 pm

Re: About QString initialization and QT_NO_CAST_FROM_ASCII

Post by aapo »

wmayer wrote: Sat Jan 22, 2022 3:10 pm When concatenating several strings with the + operator then this article is interesting: https://www.qt.io/blog/efficient-qstrin ... xpressions
Hmm, that was indeed an interesting read! Luckily Qt uses the recursive expression template trick under the hood, so concatenating QStrings with + operator is not terribly bad, if I understood correctly. And, in the future (Qt6?) it should become optimal with fold expressions without any user interventions, so in the future str += QStringLiteral("My ") + QStringLiteral("string ") + QStringLiteral("is ") + QStringLiteral("ready") should be as efficient as str.append(QStringLiteral("My ")).append("QStringLiteral("string ")).append("QStringLiteral("is ")).append("QStringLiteral("ready")).
Post Reply