I have just tagged the version 1.0 Verdigris.
I am taking this opportunity to write an article about two C++ tricks used in its implementation.
Verdigris is a header-only C++ library which lets one use Qt without the need of moc. I've written an
introductory blog post about it two years
ago and have since then
received several contributions on GitHub that extend the software.
Optionally Removing Parentheses in a Macro
This trick is used in the W_PROPERTY and the W_OBJECT_IMPL macro.
The first argument of W_PROPERTY is
a type. Typically: W_PROPERTY(QString, myProperty MEMBER m_myProperty).
But what happens when the type
contains one or several commas, as in: W_PROPERTY(QMap<QString, int>, myProperty MEMBER m_myProperty)?
That's not valid, macro expansion does not consider template and therefore the first argument would be up to
the first comma. The solution is to put the type name in parentheses. The new problem is then how can we ignore parentheses
in the implementation
of the macro.
Let's rephrase the problem with simplified macros. Imagine we want to do a macro similar to this:
The question is: How can we implement DECLARE_GETTER so both line A and line B produce the expected result?
Can we get the macro to remove the parentheses.
Let's make a first attempt:
We managed to remove the parentheses, but we broke the case where there are no parentheses.
Which lead to a sub-question: How to remove a specific token if present? In this case, how to
remove "REMOVE_PARENT_HELPER" from the expansion
The above code should give you an idea on how things should work. But it is not yet working.
We need to add a few layers of indirection so all macros arguments gets expanded
Here is
the real code from Verdigris:
Note that the W_MACRO_MSVC_EXPAND is there only to work around a MSVC bug.
Building a constexpr State in a Class from a Macro
Conceptually, this is what the macro does
But what's the state? How do we represent it?
The idea is to have a static function (let's call it w_state) whose return value contains the state.
Each W_INVOKABLE macro would then expand to a new definition of that function. Of course, it needs
to take a different argument, so this just declares a new overload. We do it by having a
w_number<N> class template, which inherits from w_number<N-1>.
(This idea is basically inspired from CopperSpice's cs_counter, whose authors
described in a talk at CppCon 2015)
Here is a simplified expanded version:
This is working pretty well. At the end of this simplified example,
our state is a std::tuple containing
&Foo::xx and &Foo::yy, from which we could build the meta object.
(In the real implementation, the state is a bit more complicated and contains more
data)
This works because the call to w_state(w_number<255>())) that
we use to get the size of the tuple, is referring to the previous declaration of w_state. Since our current
function is not yet defined yet, the most appropriate function is the remaining one with the
highest number.
Notice that we have to repeat the same code in the decltype and after the return and we cannot
use return type deduction because we need to use that function before the class is fully defined.
So far so good. However, I've hit what I think is a compiler bug when doing that with class template
(eg, Foo would be template<typename> Foo).
For this reason, instead of using static functions,
I have used friend functions in verdigris. The principle is exactly the same. It is not well-known,
but friend functions can be declared inline in the class, despite still being global functions.
(I've also used that fact in the implementation of Q_ENUM)
One just needs to add a type which relates to the current class as an argument.
I use a pointer to a pointer to the class instead of just a pointer because I don't want potential
pointer conversion when calling it with a derived class.
Conclusion
Please let me know when you find Verdigris useful or want to help out, either here in the comments or contribute directly on GitHub.