2021-01-05

As if you needed another reason to hate the preprocessor

C++ inherited from c a compilation model that discards nearly all information about types and symbols. This is a mixed blessing. On one hand it simplifies a number of things and made c portable to a wide varienty of machines including some with minimal resources. On the other hand it means debuggers need added support to do their work and that reflection is not natively supported (in c) or requires special work (as in RTTI in c++). Unfortunately there are some very nice things you can do in the testing domain if you have reflection that are quite difficult without it.

This can be worked around in a number of ways, but most approaches make heavy use of the preprocessor: using function-like macros as well as the built in location macros (__FILE__, __LINE__ and their more modern counterparts). Indeed, your humble author once wrote a unit-testing framework for c and c++ in a combination of c-preprocessor and GNU make.1 More seriously Qt's native signals/slots mechanism and test framework make use of a variety of macros (Q_DECALRE_METATYPE, QCOMPARE, and so on).

Enter c++ templates. In particular templates taking more than one parameter. For example, say I'm using QTest and I want to perform a test of a computation that returns a three element stadard array of floats. I write something like QCOMPARE(actualArray, expectedArray);, compile (no problems) and run the tests which results in a hard crash in the bowels of qmetatype.h.

Oh yeah, I needed to declare the type to the metatype system.2

No problem. Scroll to the top of the file and add Q_DECLARE_METATYPE(std::array<float, 3>);, but that won't even compile (or if you're using Qt Creator the linter will catch it for you). Do you see why?

Q_DECALRE_METATTYPE is a macro. It's arguments are parsed as comma separated plain text. Which means the preproecssor reads that line as having two arguments where only one is expected. Crap.

To be sure you can work around this issue. My favorite approach is to use a type aliases like

using floatAry3 = std::array<float, 3>;
Q_DECLARE_METATYPE(floatAry3);

Keep in mind however, that this approach has its own trap: Qt's metatype system won't let you register the same type twice. The same underlying type, not the same name. Which means that you have to use a single name for each type to avoid inadvertent duplication, but you can't use the underlying typename for any multiple argument template types.

Anyway, the point is that you can't mix funciton-like macros with multi-argument templates without pain.


1 About all that can be said for it is I learned a lot and it worked well enough for me to use on some of my toy projects.

2 Not that you can tell from the crash or even from the stack trace in the debugger. All the behind the scenes magic that goes into QTest hides the origin of the error. I assume that most every QTest user has spent a frustring hour or so learning this the hard way.

No comments:

Post a Comment