Can Qt's moc be replaced by C++ reflection?
The Qt toolkit has often been criticized for extending C++ and requiring a non-standard code generator (moc
) to provide introspection.
Now, the C++ standardization committee is looking at how to extend C++ with introspection and reflection.
As the current maintainer of Qt's moc I thought I could write a bit about the need of Qt,
and even experiment a bit.
In this blog post, I will comment on the current proposal draft, and try to analyze what one would need to be able to get rid of moc.
Current draft proposal
Here is the draft proposal:
N3951: C++ type reflection via variadic template expansion.
It is a clever way to add compile time introspection to C++.
It gives new meaning to typedef
and typename
such that it would work like this:
/* Given a simple class */ class SomeClass { public: int foo(); void bar(int x); }; #if 0 /* The new typename<>... and typedef<>... 'operators' : */ vector<string> names = { typename<SomeClass>... } ; auto members = std::make_tuple(typedef<SomeClass>...) ; #else /* Would be expanded to something equivalent to: */ vector<string> names = {"SomeClass","foo","bar"}; auto members = std::make_tuple(static_cast<SomeClass*>(nullptr), &SomeClass::foo, &SomeClass::bar); #endif
We can use that to go over the member of a class at compile time and do stuff like generating a QMetaObject with a normal compiler.
With the help of some more traits that is a very good start to be able to implement moc features in pure C++.
The experiment
I have been managing to re-implement most of the moc features such as signals and slots and properties using the proposals, without the need of moc. Of course since the compiler obviously doesn't have support for that proposal yet, I have been manually expanding the typedef...
and typename...
in the prototype.
The code does a lot of template tricks to handle strings and array at compile time and generates a QMetaObject
that is even binary compatible to the one generated by moc
About Qt and moc
Qt is a cross platform C++ toolkit specialized for developing applications with user interfaces.
Qt code is purely standard C++ code, however it needs a code generator to provide introspection data: the Meta Object Compiler (moc).
That little utility parses the C++ headers and generates additional C++ code that is compiled alongside the program. The generated code contains the implementations of the Qt signals, and builds the QMetaObject
(which embeds string tables with the names of all methods and properties).
Historically, the first mission of the moc was to enable signals and slots using a nice syntax. It is also used for the property system. The first use of the properties was for the property editor in Qt designer, then it became used for integration with a scripting language (QtScript), and is now widely used to access C++ objects from QML.
(For an explanation of the inner working of the signals and slots, read one of my previous articles: How Qt signals and slots work.)
Generating the QMetaObject at compile time
We could ask the programmer to add a macro in the .cpp such as Q_OBJECT_IMPL(MyObject)
which would be expanded to that code:
const QMetaObject MyObject::staticMetaObject = createMetaObject<MyObject>(); const QMetaObject *MyObject::metaObject() const { return &staticMetaObject; } int MyObject::qt_metacall(QMetaObject::Call _c, int _id, void** _a) { return qt_metacall_impl<MyObject>(this, _c, _id, _a); } void MyObject::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void** _a) { qt_static_metacall_impl<MyObject>(_o, _c, _id, _a); }
The implementation of createMetaObject uses the reflection capabilities to find out all the slots, signals and properties in order to build the metaobject at compile time. The function qt_metacall_impl and qt_static_metacall_impl are generic implementations that use the same data to call the right function. Click on the function name if you are interested in the implementation.
Annotating signals and slots
We could perhaps use C++11 attributes for that. In that case, it would be convenient if attributes could be placed next to the access specifiers. (There is already a proposal to add group access specifiers, but it does not cover the attributes.)
class MyObject : public QObject { Q_OBJECT public [[qt::slot]]: void fooBar(); void otherSlot(int); public [[qt::signal]]: void mySignal(int param); public: enum [[qt::enum]] Foobar { Value1, Value2 }; };
Then we would need compile time traits such as has_attribute<&MyObject::myFunction>("qt::signal")
Function traits
I just mentioned has_attribute
. Another trait will be needed to determine if the function is public, protected or private.
The proposal also mentioned we could use typename<&MyObject::myFunction>...
to get the parameter names. We indeed need them as they are used when you connect to a signal in QML to access the parameters.
And currently we are able to call a function without specifying all the parameters if there are default parameters. So we need to know the default parameters at compile time to create them at run time.
However, there is a problem with functions traits in that form: non-type template parameters of function type need to be function literals. (See this stackoverflow question.) Best explained with this code:
struct Obj { void func(); }; template<void (Obj::*)()> struct Trait {}; int main() { Trait<&Obj::func> t1; //Ok. The function is directly written constexpr auto var = &Obj::func; Trait<var> t2; //Error: var is not a function directly written. }
But as we are introspecting, we get, at best, the functions in constexpr
form. So this restriction would need to be removed.
The properties
We have not yet solved the Q_PROPERTY
feature.
I'm afraid we will have to introduce a new macro because it is most likely not possible to keep the source compatibility with Q_PROPERTY
.
A way to do it would be to add static constexpr members of a recognizable type. For example, this is my prototype implementation:
template <typename Type, typename... T> struct QProperty : std::tuple<T...> { using std::tuple<T...>::tuple; using PropertyType = Type; }; template <typename Type, typename... T> constexpr auto qt_makeProperty(T&& ...t) { return QProperty<Type, typename std::decay<T>::type...>{ std::forward<T>(t)... }; } #define Q_PROPERTY2(TYPE, NAME, ...) static constexpr auto qt_property_##NAME = \ qt_makeProperty<TYPE>(__VA_ARGS__);
To be used like this
Q_PROPERTY2(int, foo, &MyObject::getFoo, &MyObject::setFoo)
We can find the properties by looking for the QProperty<...>
members and removing the "qt_property_"
part of the name. Then all the information about the getter, setter and others are available.
And if we want to keep the old Q_PROPERTY
?
I was wondering if it is possible to even keep the source compatibility using the same macro: I almost managed:
template<typename... Fs> struct QPropertyHolder { template<Fs... Types> struct Property {}; }; template<typename... Fs> QPropertyHolder<Fs...> qPropertyGenerator(Fs...); #define WRITE , &ThisType:: #define READ , &ThisType:: #define NOTIFY , &ThisType:: #define MEMBER , &ThisType:: #define Q_PROPERTY(A) Q_PROPERTY_IMPL(A) /* expands the WRITE and READ macro */ #define Q_PROPERTY_IMPL(Prop, ...) static void qt_property_ ## __COUNTER__(\ Prop, decltype(qPropertyGenerator(__VA_ARGS__))::Property<__VA_ARGS__>) = delete; class MyPropObject : public QObject { Q_OBJECT typedef MyPropObject ThisType; // FIXME: how do do that automatically // from within the Q_OBJECT macro? signals: // would expand to public [[qt::signal]]: void fooChanged(); public: QString foo() const; void setFoo(const QString&); Q_PROPERTY(QString foo READ foo WRITE setFoo NOTIFY fooChanged) };
This basically creates a function with two arguments. The name of the first argument is the name of the property, which we can get via reflection. Its type is the type of the property. The second argument is of the type QPropertyHolder<...>::Property<...>
, which contains pointers to the member functions for the different attributes of the property. Introspection would allow us to dig into this type.
But the problem here is that it needs to do a typedef ThisType
. It would be nice if there was something like deltype(*this)
that would be working in the class scope without any members, then we would put this typedef within the Q_OBJECT
macro.
Re-implementing the signals
This is going to be the big problem as I have no idea how to possibly do that. We need, for each signal, to generate its code. Something that could look like this made-up syntax:
int signalId=0; /* Somehow loop over all the signals to implement them (made up syntax) */ for(auto signal : {typedef<MyObject requires has_attribute("qt::signal")>... }) { signalId++; signal(auto... arguments) = { SignalImplementation<decltype(signal), signalId>::impl(this, arguments...); } }
The implementation of SignalImplementation::impl is then easy.
Summary: What would we need
In summary, this is what would be needed in the standard to implement Qt like features without the need of moc:
- The N3951 proposal: C++ type reflection via variadic template expansion would be a really good start.
- Allow attributes within the access specifier (
public [[qt::slot]]:
) - Traits to get the attributes (
constexpr std::has_attribute<&MyClass::mySignal>("qt::signal");
- Traits to get the access of a function (public, private, protected) (for
QMetaMethod::access
) - A way to declare functions.
- Getting default value of arguments.
- Accessing function traits via
constexpr
expression. - Listing the constructors. (for
Q_INVOKABLE
constructors.)
What would then still be missing
Q_PLUGIN_METADATA
which allows to load a JSON file, and put the information in the binary:
I'm afraid we will still need a tool for that. (Because I hardly see the c++ compiler opening a file and parsing JSON.) This does not really belong inmoc
anyway and is only there because moc was already existing.- Whatever else I missed or forgot. :-)
Conclusion: will finally moc disappear?
Until Qt6, we have to maintain source and binary compatibility. Therefore moc
is not going to disappear,
but may very well be optional for new classes. We could have a Q_OBJECT2
which does not need moc, but would use
only standard C++.
In general, while it would be nice to avoid the moc, there is also no hurry to get rid of it. It is generally working fine and serving its purpose quite well. A pure template C++ implementation is not necessarily easier to maintain. Template meta-programming should not be abused too much.
For a related experiment, have a look at my attempt to reimplement moc using libclang
Update: Verdigris, a library with macros to create a QMetaObject without moc, was based on this work.
Woboq is a software company that specializes in development and consulting around Qt and C++. Hire us!
If you like this blog and want to read similar articles, consider subscribing via our RSS feed (Via Google Feedburner, Privacy Policy), by e-mail (Via Google Feedburner, Privacy Policy) or follow us on twitter or add us on G+.
Article posted by Olivier Goffart on 11 March 2014
Click to subscribe via RSS or e-mail on Google Feedburner. (external service).
Click for the privacy policy of Google Feedburner.
Google Analytics Tracking Opt-Out
Loading comments embeds an external widget from disqus.com.
Check disqus privacy policy for more information.