Sunday 30 March 2008

QString conversions, bugs, suprises and design

1. A trivial bug


It started with a pretty simple piece of code:
    QDomElement e = n.toElement(); // try to convert the node to an element.

if(!e.isNull())
{
TRACE(e.tagName());
....
where TRACE() sends an object to cout. The simplest of tasks you'd say, but it didn't compile. What? What year is it now? Are we in the early nineties? Doesn't library writers know about the standard library? It turned out, they know, so I used a slightly modified code:
    TRACE(e.tagName().toStdString());
but it always crashed with Qt 4.3, Windows XP and VisualStudio 2005! Why? No time to check. After some reading of Qt docs, I settled with:
    TRACE(e.tagName().toAscii().data());
It worked, but the code looked extremely ugly! After copy-pasting it for n times (no, n wasn't equal to 3, as it should be according to the Agile gospel, sorry!!!) I longed for something more elegant and explicit, something like:
    TRACE(qstr_cast<const char*>(e.tagName()))
The code was quickly written and worked instantly:
    // helper for QString conversions
// -- cute and explicit syntax: qstr_cast<const char*>()!


template <class T>
inline T qstr_cast(const QString& s)
{
return T(s.toAscii().data());
}

inline const char* qstr_cast(const QString& s)
{
return s.toAscii().data();
}
Well, it worked on my machine but not in the target environment, and only in one (different) case. Why? Of course I blamed the Qt runtime support, which already let me down with the toStdString() function. Then I saw the same effect in the debugger on my developemnt machine, and I blamed it on multithreading: there must be something wrong with locking when accessing this particular QString instance. But at last I found time to remove this bug, and looked at the sychronisation, and it was 100% correct. The bug was hidden elsewhere. The new (correct) code is:
    // helper for QString conversions
// -- cute and explicit syntax: qstr_cast<const char*>()!


template <class T>
inline QByteArray qstr_cast(const QString& s, T* t = 0)
{
// the QByteArray's const char* conversion op. will be applied
// by the compiler in the const char* context!

return s.toAscii();
}
As you can see, the old code returned a pointer to the data of a temporary instance of an QByteArray object, and of course it pointed into the void. End of story!

2. Discussion of the trivial bug


Why am I writing this? Isn't it just a banal and stupid bug? I don't think so. I think there are some points to be made.

The first one is that Qt doesn't follow the "Principle of Least Surprise"*. This code should work out of the box: cout << qtStringObj;! Why? Because C++ programmers wrote code like this for centuries! Well, almost. In the "freedom languages" ;-)** you are accustomed to writing just: print someObj; and it always works. Mind I didn't use the word "modern languages" but in modern times we expect it just to be working!

The second one is that Qt doesn't want me to use the standard library! There is no shift operator for std::ostream instead they want me to use their QStream class, which I don't know and have no desire to learn. As for QString class the Qt partisans maintain that it is vastly superior to the std::string class, but what about cout? It is somehow an C++ keyword by now. And besides, why aren't I allowed to make my own choices, even if I choose to use an inferior alternative? Maybe I don't have to be that fast in my prototype implementation? You somehow feel trapped in the proprietary Qt world, and somehow cast back in time. Is that the backwards compatibility with the original 80-ties or 90-ties design? It feels somehow frumpy.

The third one is that once I decided on the syntactic appearance of the function (i.e. qstr_cast<>() and not for example cstr_ptr() or CSTR()), I subconsciously settled on a implementation: just get the internal string data and export the pointer to the char buffer. Alas, in case of the QString this doesn't work that way! Moreover, it's not only the name choice alone, this code would work with all the string classes I knew in my long C++ life. So maybe I chose the name because subconsciously I already knew how to implement it? We pride ourself on being rational beings, but we don't know how much we depend on subconscious shortcuts, which will normally work in a familiar territory, but fail when trying something new. Just like me, as I'm relatively new to Qt.

And what's the moral? It's elementary dear Watson: RTFM first! Or perhaps: don't mix Qt and STL???

---
* for example "Applying the Rule of Least Surprise" from "The Art of Unix Programming" by E.S. Raymond: http://www.faqs.org/docs/artu/ch11s01.html or "Principle of Least Astonishment" at Portland Pattern Repository: http://c2.com/cgi/wiki?PrincipleOfLeastAstonishment

** I thought it was a joke, but not, it's an essay by Kevin Barnes: http://codecraft.info/index.php/archives/20/