count __VA_ARGS__ MSVC дает неожиданные результаты

Я пытаюсь подсчитать количество параметров для вызова правильного макроса. Конкатенация и количество аргументов, похоже, дают ожидаемые результаты, но по какой-то причине в MSVC количество аргументов не работает. Я пробовал известные исправления, такие как EXPAND(x) x and EXPAND(...) __VA_ARGS__ и CALL(x,y) x y, но ничего не помогло. Я также жестко закодировал число, которое, как я знаю, работает, и после компиляции оно дало правильные результаты, поэтому я сузил проблему до подсчета МАКРО.

После компиляции VS предупреждает о нехватке параметров, так как вызывает неверный _#_DERIVED(...) MACRO.

МАКРОСЫ

#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__)

ПРИМЕР ИСПОЛЬЗОВАНИЯ

CLASS_BODY(Renderer)
CLASS_BODY(Object, XMLParser)

ЖЕЛАЕМЫЙ РЕЗУЛЬТАТ ПОСЛЕ КОМПИЛЯЦИИ МАКРО

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

person Matthew    schedule 24.10.2017    source источник
comment
_COUNT — это имя, зарезервированное для реализации, как и _DERIVED и _EXPAND. Почему люди всегда вставляют эти символы подчеркивания в начале, что специально запрещено, и никогда в конце?   -  person MSalters    schedule 25.10.2017
comment
@MSalters Я совершенно не знал, что _COUNT был зарезервирован, и с тех пор изменил имена   -  person Matthew    schedule 25.10.2017


Ответы (1)


М.Солтерс прав; идентификаторы с одним символом подчеркивания зарезервированы. Если ваши макросы активны при включении чего-либо в реализацию, это может привести к конфликту, когда вы меньше всего этого ожидаете.

After compiling VS warns about ...

Я подозреваю, основываясь на этой цитате, что вы отлаживаете свои макросы, пытаясь их скомпилировать. Гораздо лучшим подходом было бы использование только препроцессора. В Microsoft вы можете сделать это, запустив cl /EP foo.h в командной строке из консоли разработки (или после запуска VCVARSALL для вашей конкретной реализации).

Теперь к макросам:

#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 нужен шаг расширения, иначе он бесполезен. Это лучше:

#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__

В настоящее время:

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

... расширяется до:

_1 _1 _2

Это технически правильно. COUNT() имеет один аргумент (пустой). Причина, по которой это не возвращает _0 в Microsoft, заключается в шаге расширения... в первую очередь это позволяет макросу COUNT работать. Если вы хотите, чтобы COUNT() возвращал 0, вам нужно добавить еще один уровень косвенности и использовать CALL (поскольку список аргументов должен расширяться один раз, чтобы вызвать исключение запятой):

#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__

...и теперь COUNT() возвращает _0; имейте в виду, что это специфично для Microsoft.

Собираем это вместе

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

... расширяется до:

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

Конечно, сейчас вы просто генерируете идентификаторы, начинающиеся с _.

person H Walters    schedule 25.10.2017
comment
... и я сразу же задам вопрос, более подходящий для комментариев: чего вы пытаетесь достичь в целом? Макросы очень трудно сделать правильно, и этот дизайн уже полагается на непереносимые функции. В зависимости от того, что это такое, могут быть разные подходы, которые намного проще реализовать и которые более портативны. - person H Walters; 25.10.2017
comment
У меня есть система типов, которая регистрирует класс с помощью простого макроса CLASS(), а затем у меня есть макрос тела класса, как показано в вопросе, который дает классу пару вспомогательных функций и также регистрирует наследование через макрос тела класса. - person Matthew; 25.10.2017
comment
@HWalters: Имейте в виду, _0_DERIVED не зарезервировано. Это не само ведущее подчеркивание, а следующая заглавная буква, которая помещает _COUNT в набор зарезервированных идентификаторов (который также включает соседние подчеркивания в любом месте, в регулярном выражении (^_[A-Z])|(__) - person MSalters; 25.10.2017
comment
@MSalters Мы оба были неправы (за пределами моего стандарта вместо глобального). Описываемый вами набор идентификаторов зарезервирован для любых целей. Но помимо этого любой идентификатор, начинающийся с одного символа подчеркивания, зарезервирован как имя в глобальном пространстве имен (по крайней мере, в 2011 году). Итак, да, _0_DERIVED зарезервировано. См. 17.6.4.3.2. - person H Walters; 25.10.2017
comment
@ Мэтью, я надеюсь, что это поможет вам в краткосрочной перспективе; не хочу вступать в обсуждение всего проблемного пространства в комментариях (или решать что-либо), но помимо dynamic_cast есть как минимум такие вещи, как is_base_of и SFINAE, в зависимости от того, что вы хотите делать с этими регистрациями. - person H Walters; 25.10.2017