Two days ago, on his blog on some Qt Creator news, Christian wrote:
We’re currently prototyping what would happen if we replaced our own C++ code model with clang’s.
The first comment in the blog was praising that research, but that got me thinking: what do we gain from it? Qt Creator already has a full C++ code model, so what’s the advantage of replacing that with clang’s?
The first thing I thought was reduced maintenance: by using clang’s code, we don’t have to maintain ours. Is that all? What else can we gain from having this code, or the experience in writing it?
That reminded me of another blog I had read a couple of days before, entitled Making clang GObject aware, where the author was talking about a a plugin he implemented for clang that would produce warnings for setting and getting properties that didn’t exist in a given class. The parallel to moc was quite clear: QObject’s property getter and setter are also string-based, so we could use the same solution. Moreover, traditional signal-slot connections are also string-based and could use the same solution for providing warnings to the developer at code creation time, a feature that is already present inside Qt Creator.
Given that GCC also supports plugins in recent versions, it would be a nice addition to provide those diagnostics during compilation. This would benefit users who choose not to use Qt Creator and it might be even more powerful than what you can do with Qt Creator, seeing as that the IDE must deal with possibly broken code and recover from it — without hogging the CPU and freezing up.
Improving moc itself
Once we have a clang-based code model that can interpret the QObject markings in the source code, another thing we can do is replace moc itself. The moc codebase is extremely old. In fact, you can’t see this, but the old Qt historical repository began with moc being imported into CVS (files moc.l and moc.y). Since then, it’s been rewritten a couple of times, most recently for Qt 4 with a hand-written parser.
Another invention done for Qt 4 was the qt3to4 tool, which required a better understanding of C++ to do more than simple search-and-replace. That tool had a real C++ parser that deal with and recovered from some errors. Later, that C++ parser was reused in the QtScript binding generator and in Qt Creator’s C++ code model (it’s libCPlusPlus.so.1).
Unfortunately, moc was never rewritten to take advantage of that processor. That means today it still has some problems with C++ namespaces in some corner cases. It also doesn’t fail when an include is missing, so it may mis-parse things because a preprocessor expansion was missing.
So instead of rewriting using Roberto’s parser, maybe we should skip it and use clang?
Obviating the need to run moc at all
If we put the two things above together, maybe we wouldn’t have to run moc at all. Think about it: if we have a plugin to clang that can properly understand the Qt Meta Object markings (Q_OBJECT, Q_PROPERTY, etc.) and we have a clang-based codebase that generates the required code for proper operation, can’t we do both operations at the same time? We could teach clang to load a plugin that would interpret the markings, provide the necessary diagnostics as well as emit the necessary symbols at the appropriate place.
At this point, the Qt detractors will be able to correctly claim that Qt requires extensions to the C++ language! Finally they may be right about some thing!
But we still need moc
Of course, we cannot completely get rid of moc, as Qt is supposed to work with other compilers, not just GCC and Clang. So we’d need to provide moc for those people or for versions of the compilers that cannot load plugins.
I know most of the comments are going to be about “why don’t you use a modern C++ signal-slot implementation” or similar, so I’ll address that preemptively. First of all, we are already implementing a modern C++ signal-slot mechanism — Olivier has done most of the work and the Merge Request (number 42) has already been submitted, one that should be on-par with any of the other implementations.
Next, we have some extra requirements that those other implementations don’t fulfill. One of them is the ability to add signals to an existing class without breaking binary compatibility, which immediately excludes most of the other implementations because they use a signal object. Another requirement we have is to keep source compatibility with Qt 4 for the time being (Qt 6 may not require that).
Those two requirements above already require us to have extra code which cannot be done exclusively by templates and inline functions. The only thing we can add to a class that doesn’t break binary compatibility are non-virtual member functions and, unfortunately, pointers-to-member functions (PMFs) are a weird class of citizens in C++. Therefore, not only do we need to implement those functions somewhere, we also need class-specific code that can convert a PMF to something more manageable like an integer ID. Those can only be done with generated code.
What’s more, the most important reason why we need moc is not related to the signals and slots. It’s about everything else that moc allows us to do, which cannot be done without. With the Meta Object system, we can:
- enumerate the properties, signals, slots and other methods as well as decorations (classinfo) present in a class (this feature is called “type reflection”, which is common in scripted languages, but is also present in Java)
- get and set properties by name and without knowing th exact type of the property (though technically the latter is a product of the related, but independent Meta Type system)
- place calls to extracted member functions (the “invokable” functions) by name
- the last two features together allow us also to integrate C++ classes easily with other languages and IPC mechanisms without the need to write specialised parsers (D-Bus, EcmaScript, QML, but also the many bindings to Qt like Python or C#)
- instantiate types by simply having access to the meta object
Finally, having code generators isn’t exactly news. I don’t see anyone complaining about uic, rcc, or qdbusxml2cpp, which parse XML and produce C++ code. I also haven’t seen people complain about tools like lex, yacc, bison, gperf that are very commonplace and serve specific purposes. So why do people give moc a hard time? I cannot accept an answer that it’s because you’re forced to use it — it’s possible to live without it, but your life would be harder. When was the last time you wrote a perfect hash by hand?
Besides, adding type reflection/introspection features to static languages isn’t exclusive to Qt. The GObject Introspection that I mentioned before is one such example and it was only created because there are useful features you can get from it. Now, my knowledge of GObject Introspection is incomplete, but from what I gather, the major difference from that to the Qt Meta Object markings is that GObject Introspection is external to the source code (it’s out-of-band). You need to generate the data and place it in separate files or at least a separate code section in the library. The Qt solution places the markings inside the source code.
Moc is here to stay. What will it evolve to, though?