Two C++ tricks used in Verdigris implementation
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:
// Naive implementation of a macro that declares a getter function #define DECLARE_GETTER(TYPE, NAME) TYPE get_##NAME() // Can be used like this DECLARE_GETTER(QString, property1); // line A // OK: expands to "QString get_property1()" // But this does not work: DECLARE_GETTER(QMap<QString, int>, property2); // ERROR: 3 arguments passed to the macro, but only 2 expected // And this DECLARE_GETTER((QMap<QString, int>), property3); // line B // ERROR: expands to "(QMap<QString, int>) get_property3()" // Can we get rid of the parenthesis?
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:
// REMOVE_PAREN will be our macro that removes the parenthesis #define DECLARE_GETTER(TYPE, NAME) REMOVE_PAREN(TYPE) get_##NAME() // Forward to REMOVE_PAREN_HELPER #define REMOVE_PAREN(A) REMOVE_PAREN_HELPER A #define REMOVE_PAREN_HELPER(...) __VA_ARGS__ DECLARE_GETTER((QMap<QString, int>), property1); // OK: expands to "QMap<QString, int> get_property1()" // This worked because "REMOVE_PAREN_HELPER (QMap<QString, int>)" was expanded to "QMap<QString, int>" DECLARE_GETTER(QString, property2); // ERROR: expands to "REMOVE_PAREN_HELPER QString get_property2()" // There was no parenteses after REMOVE_PAREN_HELPER so it was not taken as a macro"
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
// Same as before #define DECLARE_GETTER(TYPE, NAME) REMOVE_PAREN(TYPE) get_##NAME() // Macro that removes the first argument #define TAIL(A, ...) __VA_ARGS__ // This time, we add a "_ ," in front of the arguments #define REMOVE_PAREN_HELPER(...) _ , __VA_ARGS__ #define REMOVE_PAREN(A) REMOVE_PAREN2(REMOVE_PAREN_HELPER A) #define REMOVE_PAREN2() TAIL(REMOVE_PAREN_HELPER_##__VA_ARGS__) // ##__VA_ARGS__ will "glue" the first token of its argument with "REMOVE_PAREN_HELPER_" // The first token is: // - "_" if REMOVE_PAREN_HELPER was expanded, in which case we have "REMOVE_PAREN_HELPER__" // which will be removed by the TAIL macro; or // - "REMOVE_PAREN_HELPER if it was not expanded, in chich case we now have // "REMOVE_PAREN_HELPER_REMOVE_PAREN_HELPER" // So we define a macro so that it will be removed by the TAIL macro #define REMOVE_PAREN_HELPER_REMOVE_PAREN_HELPER _,
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:
#define W_MACRO_MSVC_EXPAND(...) __VA_ARGS__ #define W_MACRO_DELAY(X,...) W_MACRO_MSVC_EXPAND(X(__VA_ARGS__)) #define W_MACRO_DELAY2(X,...) W_MACRO_MSVC_EXPAND(X(__VA_ARGS__)) #define W_MACRO_TAIL(A, ...) __VA_ARGS__ #define W_MACRO_REMOVEPAREN(A) W_MACRO_DELAY(W_MACRO_REMOVEPAREN2, W_MACRO_REMOVEPAREN_HELPER A) #define W_MACRO_REMOVEPAREN2(...) W_MACRO_DELAY2(W_MACRO_TAIL, W_MACRO_REMOVEPAREN_HELPER_##__VA_ARGS__) #define W_MACRO_REMOVEPAREN_HELPER(...) _ , __VA_ARGS__ #define W_MACRO_REMOVEPAREN_HELPER_W_MACRO_REMOVEPAREN_HELPER , #define DECLARE_GETTER(TYPE, NAME) W_MACRO_REMOVEPAREN(TYPE) get_##NAME() // And now it works as expected: DECLARE_GETTER(QString, property1); DECLARE_GETTER((QMap<QString, int>), property2);
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
class Foo : public QObject { W_OBJECT(Foo) // init the state int xx(); W_INVOKABLE(xx) // add things to the state int yy(); W_INVOKABLE(yy) // add more things }; W_OBJECT_IMPL(Foo); // Do something with the state
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:
template<int N> struct w_number : public w_number<N - 1> { static constexpr int value = N; static constexpr w_number<N-1> prev() { return {}; } }; // Specialize for 0 to break the recursion. template<> struct w_number<0> { static constexpr int value = 0; }; class Foo { public: // init the state (expanded from W_OBJECT) static constexpr tuple<> w_state(w_number<0>) { return {}; } int xx(); // add &Foo::xx to the state by defining w_state(w_number<1>) static constexpr auto w_state(w_number<tuple_size< decltype(w_state(w_number<255>()))>::value + 1> n) -> decltype(tuple_cat(w_state(n.prev()), make_tuple(&Foo::xx))) { return tuple_cat(w_state(n.prev()), make_tuple(&Foo::xx)); } int yy(); // add &Foo::yy to the state by defining w_state(w_number<2>) static constexpr auto w_state(w_number<tuple_size< decltype(w_state(w_number<255>()))>::value + 1> n) -> decltype(tuple_cat(w_state(n.prev()), make_tuple(&Foo::yy))) { return tuple_cat(w_state(n.prev()), make_tuple(&Foo::yy)); } }; // Use that state constexpr auto FooMetaObject = buildMetaObject(Foo::w_state(w_number<255>()));
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.
class Bar { public: // init the state (expanded from W_OBJECT) friend constexpr tuple<> w_state(Bar **, w_number<0>) { return {}; } int xx(); friend constexpr auto w_state(Bar **t, w_number<tuple_size<decltype(w_state( static_cast<Bar**>(nullptr), w_number<255>()))>::value + 1> n) -> decltype(tuple_cat(w_state(t, n.prev()), make_tuple(&Bar::xx))) { return tuple_cat(w_state(t, n.prev()), make_tuple(&Bar::xx)); } int yy(); friend constexpr auto w_state(Bar **t, w_number<tuple_size<decltype(w_state( static_cast<Bar**>(nullptr), w_number<255>()))>::value + 1> n) -> decltype(tuple_cat(w_state(t, n.prev()), make_tuple(&Bar::yy))) { return tuple_cat(w_state(t, n.prev()), make_tuple(&Bar::yy)); } }; // Use that state constexpr auto BarMetaObject = buildMetaObject(w_state(static_cast<Bar**>(nullptr), w_number<255>()));
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.
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 15 February 2018
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.