3 good uses for the C++ preprocessor in 2020

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 #includes 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.

4 thoughts on “3 good uses for the C++ preprocessor in 2020”

  1. My pet peeve is the #if WINDOWS #elif LINUX sequences. They are distracting and confusing. I found a technique that addresses those issues and implemented. A few years latter looked at the code and other developers went back to that approach. My solution was to create an os.H file for each OS. It contained wrappers for all the OS function calls. There was no GUI so relatively few OS specific calls. The verion was selected by changing the include path to bring in “windows/os.h’ or “linux/os.h”.

    Have to think about the “FORWARD_DECL”.

  2. My thoughts:
    0. This is okay for temporary use during refactoring. It is otherwise not advisable to keep commented code. Version control has the older code always. Moreover, it is possible to move parts of functions into a lambda expression and never invoke it. This achieves the same effect as with macros while refactoring. To exclude whole functions though or multiple functions one has to be back to macros!
    1. Is a very valid use case!
    2. We don’t need macros here to highlight forward declarations. C++ attributes help us do the same and compilers will ignore any attributes they do not understand.

    [[Forward_Declaration]] class will_be_defined;

    The above macro approach enables only forward declaration of classes. Could we need this for functions or other constructs also?
    [[Forward_Declaration]] void fn_will_be_defined(int);

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.