1

I am trying to count the number of parameters to call the correct macro. The concatenation and number of arguments appear to be giving expected results but for some reason on MSVC the number of arguments are not working. I have tried known fixes such as EXPAND(x) x and EXPAND(...) __VA_ARGS__ and CALL(x,y) x y but nothing has worked. I have also hard coded a number that I know works and after compiling it gave the correct results so I have narrowed the problem down to the counting MACRO.

After compiling VS warns about not having enough parameters as it calls the incorrect _#_DERIVED(...) MACRO.

MACROS

#define CONCAT_DETAIL(l, r) l##r
#define CONCAT(l, r) CONCAT_DETAIL(l, r)
#define _COUNT_N(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N
#define _COUNT(...) _COUNT_N("ignore", ## __VA_ARGS__, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, _0)
#define _EXPAND(...) __VA_ARGS__
#define CLASS_BODY(...) CONCAT(_EXPAND(_COUNT(__VA_ARGS__)),_DERIVED)(__VA_ARGS__)

EXAMPLE USAGE

CLASS_BODY(Renderer)
CLASS_BODY(Object, XMLParser)

DESIRED RESULT AFTER MACRO COMPILATION

_0_DERIVED()
_1_DERIVED(arg1)
_2_DERIVED(arg1, arg2)
.
.
.
Matthew
  • 800
  • 3
  • 12
  • 35
  • 3
    `_COUNT` is a name reserved for the implementation., as is `_DERIVED` and `_EXPAND`. Why do people always stick these underscores in the front, which is specifically banned, and never at the end ? – MSalters Oct 25 '17 at 07:53
  • @MSalters I was completely unaware that `_COUNT` was reserved and have since changed the names – Matthew Oct 25 '17 at 14:36

1 Answers1

0

MSalters is right; identifiers with single underscores are reserved. If your macros are active when including anything in the implementation, it could clash when you least expect it.

After compiling VS warns about ...

I suspect based on this quote that you're debugging your macros by trying to compile them. A much better approach would be to use only the preprocessor. In Microsoft, you can do this by running cl /EP foo.h in the command line from a development console (or after running VCVARSALL for your particular implementation).

Now to the macros:

#define _COUNT_N(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N
#define _COUNT(...) _COUNT_N("ignore", ## __VA_ARGS__, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, _0)
#define _EXPAND(...) __VA_ARGS__

_COUNT needs an expansion step, otherwise it's useless. This is better:

#define COUNT_N(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N
#define COUNT(...) EXPAND(COUNT_N( , __VA_ARGS__, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, _0))
#define EXPAND(...) __VA_ARGS__

Now:

COUNT() COUNT(a) COUNT(a,b)

...expands to:

_1 _1 _2

This is technically correct. COUNT() has one argument (it's empty). The reason this doesn't return _0 in Microsoft is because of the expansion step... the very thing allowing the COUNT macro to work in the first place. If you want COUNT() to return 0, you'll need to add another level of indirection and use CALL (since argument list needs to expand once to trigger the comma elision):

#define COUNT_N(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N
#define COUNT_M(...) EXPAND(COUNT_N( __VA_ARGS__, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, _0))
#define COUNT(...) CALL(COUNT_M,(, __VA_ARGS__))
#define CALL(X,Y) X Y
#define EXPAND(...) __VA_ARGS__

...and now COUNT() returns _0; be aware this is Microsoft specific.

Putting it together

#define CONCAT_DETAIL(l, r) l##r
#define CONCAT(l, r) CONCAT_DETAIL(l, r)
#define COUNT_N(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N
#define COUNT_M(...) EXPAND(COUNT_N( __VA_ARGS__, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, _0))
#define COUNT(...) CALL(COUNT_M,(, __VA_ARGS__))
#define CALL(X,Y) X Y
#define EXPAND(...) __VA_ARGS__
#define CLASS_BODY(...) CONCAT(EXPAND(COUNT(__VA_ARGS__)),_DERIVED)(__VA_ARGS__)

CLASS_BODY()
CLASS_BODY(arg1)
CLASS_BODY(arg1,arg2)

...expands to:

_0_DERIVED()
_1_DERIVED(arg1)
_2_DERIVED(arg1,arg2)

Of course, you're just generating identifiers starting with _ now.

H Walters
  • 2,634
  • 1
  • 11
  • 13
  • ...and I'll immediately follow with a question more appropriate for comments: What are you trying to accomplish in general? Macros are very difficult to get right, and this design's already relying on non-portable features. Depending on what it is, there could be different approaches that are much easier to pull off and more portable. – H Walters Oct 25 '17 at 12:13
  • I have a type system that registers a class with a simple `CLASS()` macro and then I have a class body macro as seen in the question that gives the class a couple of helper functions and registers the inheritance as well through the class body macro – Matthew Oct 25 '17 at 14:07
  • @HWalters: Mind you, `_0_DERIVED` is _not_ reserved. It's not the leading underscore itself, but the following uppercase letter which puts `_COUNT` in the set of reserved identifiers (which also includes adjacent underscores anywhere, in regex `(^_[A-Z])|(__)` – MSalters Oct 25 '17 at 14:57
  • @MSalters We were both wrong (outside of my putting "standard" instead of "global"). The set of identifiers you're describing is reserved _for any purpose_. But outside of that, any identifier starting with a single underscore is reserved _as a name in the global namespace_ (at least in 2011). So, yes, `_0_DERIVED` is reserved. See 17.6.4.3.2 – H Walters Oct 25 '17 at 15:17
  • @Matthew I hope this is helpful for you in the short run; don't want to get into a discussion of the entire problem space in comments (or solve anything), but outside of just `dynamic_cast` there's at least things like `is_base_of` and SFINAE available, depending on what you want to do with those registrations. – H Walters Oct 25 '17 at 15:20