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

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

    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.

      • Brendan Patrick Lyon

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

      • Raymond ChenMicrosoft employee

        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.

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

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