As this weird year, 2020, comes to a close, I noticed that I am still using the preprocessor in my C++ programs. And not just for #include
s which might, at last, slowly fade away with C++20’s modules. The preprocessor’s got a pretty bad rep, and new C++ programmers are usually taught to stay as far away as possible. Justifiably so – some things, like the dreaded X-Macros really should go the way of the dinosaurs.
But there are still some good uses left in the thing, and here’s my top 3 of those:
0. Commenting out big-chunks of code
I’ve often seen people comment out big parts of code with block comments: /* this is not active */
. However, that will only work as long as the code does not contain any other block comments, let alone a stray */
in a string. A great alternative is to use the preprocessor:
#if 0 auto i_do_not_want_to_compile_this() -> auto { std::vector<std::deque<std::mutex>> baz{}; return baz; } #endif
This can easiely by wrapped multiple times around bigger parts of code, which is very helpful when refactoring large chunks of legacy code. It can very easiely be toggled on and off while in this state. And the IDE will usually still show a dimmed version of syntax highlighting in the disabled region.
1. Conditionally throw away “cross-cutting” concerns
Some parts of aspects of programs can be “cross-cutting”, which means they cannot easiely be separated from the rest of the code-base by putting them in a separate module. The most prominent example is probably logging. While you can typically modularize the actual implementation, the actual log calls will be all over your code. Another of those concerns is “profiling”. This is also something that you typically want to take out of your application when deploying it, because users will rarely profile the end-product. Again, the preprocessor comes to the rescue. For example, in the excellent Optick, most of the code you insert is actually macros that can be completely eliminated with a simple compile-time switch. Consider this “tag” that add some additional metric to your profile:
OPTICK_TAG("CoolMetric", compute_cool_metric());
When Optick is turned off via the aforementioned compile-time switch, compute_cool_metric()
is never called. The call is not even compiled. Just turning Optick off will completely remove it from your source. Now this can be potentially dangerous, if the function has a side effect, but you wouldn’t do that anyways, would you?
2. Making forward declarations more visible
Presumably owing to its history as a continuously-evolved language, C++ has a very limited set of reserved keywords, often avoiding to introduce keywords to not interfere with any working software out there. Do not get me wrong, that is a great reason. But because of this, some language constructs can sometimes be a bit cryptic, for example forward declarations: class will_be_defined;
. If you ever worked with a big, old or big and old code-base with lots of those, you probably know that maintaining them can be a bit of a chore and prone to error. So I think it is a great idea to at least make them more visible with your own macro “KEYWORD”:
#define FORWARD_DECL(x) class x FORWARD_DECL(will_be_defined);
That FORWARD_DECL
immediatly stands out visually and helps me keep track of those subtle declarations.