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

Raymond Chen

Raymond

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);

7 comments

Comments are closed. Login to edit/delete your existing comments

  • Harold H
    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
      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 Chen
        Raymond ChenMicrosoft logo

        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.

  • Avatar
    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>);

  • Avatar
    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 time.