May 8th, 2020

The C++ preprocessor doesn’t understand anything about C++, and certainly not templates

The C and C++ preprocessors are like the Windows command prompt batch language: They implement a very simple language that people still use for some reason, usually by pushing it way beyond its original design boundaries.

The preprocessors don’t really understand the C or C++ language. It does, fortunately, use the same arithmetic operators that the C and C++ language uses, but its understanding of them is limited to integers.

The treatment of commas in preprocessor arguments is very simple: Commas separate macro arguments. The only way to protect a comma is to enclose it in matched parentheses.

#define M(cond, action) if (cond) action // horrible macro

M(function(2, 3), return) // okay

Note that the less-than and greater-than signs do not protect commas. This can create confusion if you want to pass a template instantiation as a macro parameter.

M(std::is_same_v<T, U>, return); // doesn't compile

The preprocessor isn’t smart enough to realize that what you passed was intended to be a template instantiation. After all, maybe you wanted this:

M(value < 0 || height > 1000, return out_of_range);

In this case, the less-than and greater-than signs are intended to be the comparison operators. But from the preprocessor’s point of view, the angle brackets in

    std::is_same_v< T, U >

and

    value < 0 || height > 1000

are basically the same thing. It has no way of knowing that the first case of matched angle brackets is a template instantiation, whereas the second is just a pair of comparison operators. Not even the compiler knows, because we are still preprocessing. Compilation hasn’t happened yet.

The solution is to insert seemingly-spurious parentheses around the macro argument.

M((std::is_same_v<T, U>), return);
Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

7 comments

Discussion is closed. Login to edit/delete existing comments.

  • Alexis Ryan

    template code in a preprocessor macro sounds like asking for a confusing really long error message if your macro generates some bad code.

    I'm now wondering what happens to the parentheses if the macro passes that argument to another macro. I imagine nothing happens to them. Also wondering about what expressions might not be valid with the parentheses but valid without. My intuition says parentheses around a type name is going to lead to a bad...

    Read more
  • John Melas

    A good trick to avoid the need of parenthesis when macro arguments contain commas is to use __VA_ARGS__ as macro argument.
    For example we could write the above macro as

    #define M(action, …) if (__VA_ARGS__) action // still horrible macro

    and then call as

    M(return, std::is_same_v<T, U>);

    • Raymond ChenMicrosoft employee Author

      Until you want to do

      M(return std::is_same_v<T, U>, std::is_same_v<X, Y>)

      !

  • Harold H

    Angle brackets have been used for “less than” and “greater than” for a very long time. Maybe using them to signify template instantiation wasn’t a very good design decision.

    • Jonathan Sharman

      Humans and machines can both understand template brackets just fine. I’d rather lay the blame on the unsafe and rather unintelligent C preprocessor than on the template syntax.

      • Raymond ChenMicrosoft employee Author

        In order to make the preprocessor more intelligent, it would have to run on concert with the compiler rather than as an earlier translation phase. Whether M(a<b,c>-1) is a macro invocation with one parameter or two depends on whether a names a template. Sometimes the best solution for a system that isn’t smart enough is to make it stupider and therefore more predictable.

      • Brendan Patrick Lyon

        Not to mention angle brackets are used in other languages to signify generics, so it makes sense to be consistent.