Est. 2011

Two C++ tricks used in Verdigris implementation

A verdigris statue

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

// 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


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

// 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__


//  ##__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

// So we define a macro so that it will be removed by the TAIL macro

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_TAIL(A, ...) __VA_ARGS__



// 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 {
    // 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 {

    // 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>()));


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+.

Submit on reddit Submit on reddit Tweet about it Share on Facebook Post on Google+

Article posted by Olivier Goffart on 15 February 2018

Load Comments...
Loading comments embeds an external widget from
Check disqus privacy policy for more information.
Get notified when we post a new interesting article!

Click to subscribe via RSS or e-mail on Google Feedburner. (external service).

Click for the privacy policy of Google Feedburner.
© 2011-2023 Woboq GmbH
Google Analytics Tracking Opt-Out