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