Search code examples
c++macrosc-preprocessorc++14variadic-macros

how to properly expand a macro?


I need to be able to expand a macro to build a typedef which I use for my application. The macro builds a simple typedef. The question I have is how do __VA_ARGS__ (i.e. do you lose arguments farther down the calls?) act when passed through to numerous macros and how to know when another scan is required to force proper results as I think this might be the source of the problem when creating higher order DERIVED macros.

#define DERIVED0()          rtti::impl::BaseTypedefList<rtti::impl::null>
#define DERIVED1(T1)        rtti::impl::BaseTypedefList<T1, DERIVED0()>
#define DERIVED2(T1, T2)    rtti::impl::BaseTypedefList<T1, DERIVED1(T2)>
#define BUILD(count, ...) DERIVED##count( __VA_ARGS__ )

// inside the classes
#define CLASS_BODY(count, ...) typedef BUILD(count, __VA_ARGS__)    BaseClassList;

// example usages
CLASS_BODY(0)                   // WORKS
CLASS_BODY(1, MeshRenderer)     // WORKS
CLASS_BODY(2, Renderer, Object) // ERROR

Solution

  • Microsoft Visual Studio versions of the C preprocessor (MSVC++,MSVC) have a peculiar notion of an entity that would otherwise be a series of multiple tokens being chunked into a single token. This specifically come into play when expanding variadic macros; __VA_ARGS__ is always expanded as a single token, even if that expansion contains commas. This behavior is peculiar to Microsoft preprocessors.

    In particular, during invocation of CLASS_BODY(2, Renderer, Object), you're invoking:

     BUILD(2, Renderer, Object)
    

    Technically here, Renderer, Object is one token, but at this point it doesn't matter. During argument identification here, class is matched with 2 and ... with Renderer, Object. During argument substitution, this becomes something peculiar:

    DERIVED##count( Renderer, Object )
    

    That looks harmless enough, but the peculiar thing is that Renderer, Object collectively is one token. The comma there isn't processed as a separator. The implication can be seen a bit later... after we walk through the paste:

    DERIVED2( Renderer, Object )
    

    ...then DERIVED2 is invoked. Here, argument identification matches T1 with Renderer, Object. T2 is dangling; it's not matched. This yields a preprocessor error.

    A general rule of thumb here would be to apply an expansion step wherever you're using __VA_ARGS__ in a replacement list, at least if you rely on multiple parameters being parsed as multiple preprocessor tokens (unless for some odd reason you actually want this behavior, but in such cases you'll be locked into the MSVS peculiarity). 99% of the time this form of indirection would work:

    #define EVAL(...) __VA_ARGS__
    #define BUILD(count, ...) EVAL(DERIVED##count( __VA_ARGS__ ))
    

    On occasion you may have to do something like this:

    #define CALL(X,Y) X Y
    #define BUILD(count, ...) CALL(DERIVED##count,( __VA_ARGS__))
    

    Either works in this particular case.