Thursday 27 June 2013

Those pesky evil macros

I've always been suspicious of Absolutes. Well, not always; after all, that's what our formative years are for - to be filled with certainties that we'll eventually replace with doubt throughout the rest of our lives. Anyway...

When I started developing, one of the first Absolutes I included in the "suspicious" category was the "X is evil" statement. And these days, with all the good sources of information available (stackoverflow, isocpp.org, or reddit/cpp, to name a few), I believe there's little reason for an Absolute like this. With a few exceptions, most "X is evil" statements need some qualifying, such as "X is evil, except in these cases, where it's perfectly acceptable", and it's this qualified (and quality) information that needs to be spread around, and not some utter hogwash Absolute.

So, let's tackle some evil, shall we?

I've been doing some research on C++ macros. Well, C macros, actually, but let's not get worked up over it. Namely, something like this, which would allow me to write this:

// YES, ONE DAY I'LL LOOK INTO BOOST PREPROCESSOR
#define FE_1(WHAT, X) WHAT (X)
#define FE_2(WHAT, X, ...) WHAT (X) FE_1(WHAT, __VA_ARGS__)
#define FE_3(WHAT, X, ...) WHAT (X) FE_2(WHAT, __VA_ARGS__)
#define FE_4(WHAT, X, ...) WHAT (X) FE_3(WHAT, __VA_ARGS__)
#define FE_5(WHAT, X, ...) WHAT (X) FE_4(WHAT, __VA_ARGS__)
 

#define GET_MACRO(_1,_2,_3,_4,_5,NAME,...) NAME
#define FOR_EACH(action,...) \
  GET_MACRO(__VA_ARGS__,FE_5,FE_4,FE_3,FE_2,FE_1)(action,__VA_ARGS__)
 

#define MYPC_LOG(level, ...) \
    BOOST_LOG_TRIVIAL(level) \
    FOR_EACH(<<, __VA_ARGS__)
 
MYPC_LOG(info, "info message: ", GetCode(), "some more info");
MYPC_LOG(info, "another info message: ", GetCode(), "some more info: ", 42);
MYPC_LOG(error, "error: ", GetCode(), "error info: ", 42, 12);


Why this solution? Because it's a simple way (once you understand it) to translate function call syntax (comma-separated list of values) into stream syntax. That means I can further isolate my code from the knowledge of how the logger works, and that's a Good Thing(TM).

Ah, but I can almost hear you say: "You don't need macros for that". And you're absolutely right, I don't. So, macros being - generally speaking and, you know, in a qualified way - evil, and there being better alternatives, why macros?

For two reasons, actually.
  1. As I've written in previous posts, I'm using __FILE__ and __LINE__, and the preprocessor relies on their position in the code to generate their values. So, I need these to be at the actual call site, and not on some function (even inline).
  2. One fundamental property of logging code is to generate as little overhead as possible. Look at this: MYPC_LOG(info, "info message: ", GetCode(), "some more info"). See the GetCode() in there? Ideally, we want to evaluate this only if it will be logged, i.e., if our logging level is INFO or lower (I'm ignoring other possible filters, here). I couldn't do this if MYPC_LOG() were a function.
Well, regarding point #2, yes, I could, using lambdas or functors. However, that would lead to a more complex solution and I'd still have point #1 to deal with.

Finally, with regards to spreading qualified information, here it goes. Note that logging/debugging with __FILE__ and __LINE__ shows up a few times as a perfectly good reason for using macros.

So, whenever someone berates you for using "evil constructs", do some research and see what your peers are saying. If it turns out there's a better alternative, learn and adapt; otherwise, sometimes you're stuck with some degree of evil. Just don't get used to it, or you could end up like this fellow, who only turned out all right in the end by sheer luck; or, as someone put it, because of someone else's pity.

No comments:

Post a Comment