Расширение макросов с переменным числом аргументов MSVC ++

Итак, у меня есть макрос, который хорошо работает в GCC, но не в компиляторе Microsoft C ++. Я надеюсь, что кто-нибудь может знать об обходном пути или, возможно, сможет объяснить мне, почему он так себя ведет.

Я уверен, что этот макрос не совсем «стандартный», но он мне действительно поможет.

Вот функциональный пример макроса:

#define VA_NARGS_IMPL(_1, _2, _3, _4, _5, N, ...) N
#define VA_NARGS(...) VA_NARGS_IMPL(__VA_ARGS__, 5, 4, 3, 2, 1)

#define FULLY_EXPANDED(count, ...) \
  MAC ## count (__VA_ARGS__)

#define SEMI_EXPANDED(count, ...) FULLY_EXPANDED(count, __VA_ARGS__)

#define EXPAND_THESE(...) SEMI_EXPANDED(VA_NARGS(__VA_ARGS__), __VA_ARGS__)

#define ACTUAL_MACRO(x) parent->GetProperty<x>();
#define MAC1(a) ACTUAL_MACRO(a)
#define MAC2(a,b) MAC1(a) ACTUAL_MACRO(b)
#define MAC3(a,b,c) MAC2(a,b) ACTUAL_MACRO(c)
#define MAC4(a,b,c,d) MAC3(a,b,c) ACTUAL_MACRO(d)
#define MAC5(a,b,c,d,e) MAC4(a,b,c,d) ACTUAL_MACRO(e)

Вот как я могу использовать этот макрос:

struct MyStructure
{
  void Foo()
  {
    EXPAND_THESE(Property1, Property2, Property3, Property4)
  }

  Base * parent;
}

Вот как GCC расширяет вышесказанное:

struct MyStructure
{
  void Foo()
  {
    parent->GetProperty<Property1>(); 
    parent->GetProperty<Property2>(); 
    parent->GetProperty<Property3>(); 
    parent->GetProperty<Property4>();
  }

  Base * parent;
}

Но Microsoft почему-то раскрывает все мои __VA_ARGS__ как один аргумент:

struct MyStructure
{
  void Foo()
  {
    parent->GetProperty<Property1, Property2, Property3, Property4>();
  }

  Base * parent;
}

Кто-нибудь знает, почему это так? Есть ли какой-нибудь трюк, который я могу использовать, чтобы заставить Microsoft расширить это, как GCC? Может быть, добавить пару лишних скобок?

Подобные макросы действительно могли бы помочь мне заменить кучу «связующего» кода, но из-за этой проблемы я не могу перенести его в свой проект VS. Любая помощь будет принята с благодарностью!

Спасибо.


person Robert Kelly    schedule 07.02.2012    source источник
comment
Its a [bug](http://connect.microsoft.com/VisualStudio/feedback/details/380090/variadic-macro-replacement) and I dont думает, что они планируют исправить это в ближайшее время.   -  person Jesse Good    schedule 08.02.2012
comment
Связанный дубликат: Как исправить проблемы, связанные с переменным макросом, с «перегрузкой макроса» в MSVC ++ (Microsoft Visual studio)? - @ JesseGood Спасибо за указание на ошибку.   -  person iammilind    schedule 10.02.2018
comment
Возможный дубликат MSVC не расширяет __VA_ARGS__ правильно   -  person TooTone    schedule 26.11.2019
comment
developercommunity.visualstudio.com/content/problem/460154/   -  person Antti Haapala    schedule 13.07.2020


Ответы (3)


По совпадению, я столкнулся с этой проблемой только сегодня, и, приложив достаточно усилий, я думаю, что нашел решение для своих целей. Ошибка заключается в том, что MSVC рассматривает __VA_ARGS__ как отдельный токен в списках аргументов. Но вы можете обойти это, не используя его непосредственно в списке аргументов вызова макроса. Этот комментарий предлагает начало ответа на ваши проблемы:

#define VA_NARGS(...) VA_NUM_ARGS_IMPL_((__VA_ARGS__, 5,4,3,2,1))
#define VA_NARGS_IMPL_(tuple) VA_NUM_ARGS_IMPL tuple
#define VA_NARGS_IMPL(_1,_2,_3,_4,_5,N,...) N

Но тогда я подозреваю, что вы, вероятно, столкнетесь с проблемой обеспечения полного расширения до фактического «N», которое вы хотите, а не, скажем, до VA_NARGS_IMPL (arg1, arg2, 5, 4, 3, 2, 1). Я обнаружил, что мой код (который выглядел как ваш) пришлось изменить, чтобы развернуть MAC##code все как одно целое, а затем его нужно было отдельно объединить со списком аргументов. Вот код, который я обнаружил, у меня сработал:

#define ASSERT_HELPER1(expr) singleArgumentExpansion(expr)
#define ASSERT_HELPER2(expr, explain) \
   twoArgumentExpansion(expr, explain)

/*
 * Count the number of arguments passed to ASSERT, very carefully
 * tiptoeing around an MSVC bug where it improperly expands __VA_ARGS__ as a
 * single token in argument lists.  See these URLs for details:
 *
 *   http://connect.microsoft.com/VisualStudio/feedback/details/380090/variadic-macro-replacement
 *   http://cplusplus.co.il/2010/07/17/variadic-macro-to-count-number-of-arguments/#comment-644
 */
#define COUNT_ASSERT_ARGS_IMPL2(_1, _2, count, ...) \
   count
#define COUNT_ASSERT_ARGS_IMPL(args) \
   COUNT_ASSERT_ARGS_IMPL2 args
#define COUNT_ASSERT_ARGS(...) \
   COUNT_ASSERT_ARGS_IMPL((__VA_ARGS__, 2, 1, 0))
 /* Pick the right helper macro to invoke. */
#define ASSERT_CHOOSE_HELPER2(count) ASSERT_HELPER##count
#define ASSERT_CHOOSE_HELPER1(count) ASSERT_CHOOSE_HELPER2(count)
#define ASSERT_CHOOSE_HELPER(count) ASSERT_CHOOSE_HELPER1(count)
 /* The actual macro. */
#define ASSERT_GLUE(x, y) x y
#define ASSERT(...) \
   ASSERT_GLUE(ASSERT_CHOOSE_HELPER(COUNT_ASSERT_ARGS(__VA_ARGS__)), \
               (__VA_ARGS__))

int foo()
{
  ASSERT(one); // singleArgumentExpansion(one)
  ASSERT(two, "foopy"); // twoArgumentExpansion(two, "foopy")
}

После нескольких часов решения моих собственных проблем в моем уме слишком много кашицы, чтобы затем полностью решить вашу, извините. :-) Но я думаю, что этого достаточно, чтобы вы с небольшой работой добрались до чего-то, что работает.

person Jeff Walden    schedule 18.02.2012

Я знаю, что этому вопросу больше двух лет, но я подумал, что постараюсь дать более точный ответ тем, кто все еще натыкается на него, как и я.

Ответ Джеффа Уолдена работает и все, но вы должны объявить FOO_CHOOSE_HELPER / 1/2 для каждого макроса FOO, который вы хотите иметь вариативные аргументы. Я разработал слой абстракции для решения этой проблемы. Учтите следующее:

#define GLUE(x, y) x y

#define RETURN_ARG_COUNT(_1_, 
#define ERROR1(title) printf("Error: %s\n", title)
#define ERROR2(title, message)\
    ERROR1(title);\
    printf("Message: %s\n", message)
#define ERROR(...) CALL_OVERLOAD(ERROR, __VA_ARGS__)

#define ASSERT1(expr) singleArgumentExpansion(expr)
#define ASSERT2(expr, explain) twoArgumentExpansion(expr, explain)
#define ASSERT(...) CALL_OVERLOAD(ASSERT, __VA_ARGS__)
,
#define ERROR1(title) printf("Error: %s\n", title)
#define ERROR2(title, message)\
    ERROR1(title);\
    printf("Message: %s\n", message)

#define ERROR_CHOOSE_HELPER2(count) ERROR##count
#define ERROR_CHOOSE_HELPER1(count) ERROR_CHOOSE_HELPER2(count)
#define ERROR_CHOOSE_HELPER(count) ERROR_CHOOSE_HELPER1(count)

#define ERROR(...) GLUE(ERROR_CHOOSE_HELPER(COUNT_ARGS_MAX5(__VA_ARGS__)),\
    (__VA_ARGS__))

#define ASSERT1(expr) singleArgumentExpansion(expr)
#define ASSERT2(expr, explain) twoArgumentExpansion(expr, explain)

#define ASSERT_CHOOSE_HELPER2(count) ASSERT##count
#define ASSERT_CHOOSE_HELPER1(count) ASSERT_CHOOSE_HELPER2(count)
#define ASSERT_CHOOSE_HELPER(count) ASSERT_CHOOSE_HELPER1(count)

#define ASSERT(...) GLUE(ASSERT_CHOOSE_HELPER(COUNT_ARGS_MAX5(__VA_ARGS__)),\
    (__VA_ARGS__))
,
int foo()
{
    ASSERT(one); // singleArgumentExpansion(one)
    ASSERT(two, "foopy"); // twoArgumentExpansion(two, "foopy")

    ERROR("Only print a title");
    ERROR("Error title", "Extended error description");
}
, _5_, count, ...) count #define EXPAND_ARGS(args) RETURN_ARG_COUNT args #define COUNT_ARGS_MAX5(...) EXPAND_ARGS((__VA_ARGS__, 5, 4, 3, 2, 1, 0)) #define OVERLOAD_MACRO2(name, count) name##count #define OVERLOAD_MACRO1(name, count) OVERLOAD_MACRO2(name, count) #define OVERLOAD_MACRO(name, count) OVERLOAD_MACRO1(name, count) #define CALL_OVERLOAD(name, ...) GLUE(OVERLOAD_MACRO(name, COUNT_ARGS_MAX5(__VA_ARGS__)), (__VA_ARGS__))

С помощью этой архитектуры вы можете определять макросы с переменным числом аргументов как таковые:

#define ERROR1(title) printf("Error: %s\n", title)
#define ERROR2(title, message)\
    ERROR1(title);\
    printf("Message: %s\n", message)
#define ERROR(...) CALL_OVERLOAD(ERROR, __VA_ARGS__)

#define ASSERT1(expr) singleArgumentExpansion(expr)
#define ASSERT2(expr, explain) twoArgumentExpansion(expr, explain)
#define ASSERT(...) CALL_OVERLOAD(ASSERT, __VA_ARGS__)

С ответом Джеффа вам нужно будет определить макросы следующим образом:

#define ERROR1(title) printf("Error: %s\n", title)
#define ERROR2(title, message)\
    ERROR1(title);\
    printf("Message: %s\n", message)

#define ERROR_CHOOSE_HELPER2(count) ERROR##count
#define ERROR_CHOOSE_HELPER1(count) ERROR_CHOOSE_HELPER2(count)
#define ERROR_CHOOSE_HELPER(count) ERROR_CHOOSE_HELPER1(count)

#define ERROR(...) GLUE(ERROR_CHOOSE_HELPER(COUNT_ARGS_MAX5(__VA_ARGS__)),\
    (__VA_ARGS__))

#define ASSERT1(expr) singleArgumentExpansion(expr)
#define ASSERT2(expr, explain) twoArgumentExpansion(expr, explain)

#define ASSERT_CHOOSE_HELPER2(count) ASSERT##count
#define ASSERT_CHOOSE_HELPER1(count) ASSERT_CHOOSE_HELPER2(count)
#define ASSERT_CHOOSE_HELPER(count) ASSERT_CHOOSE_HELPER1(count)

#define ASSERT(...) GLUE(ASSERT_CHOOSE_HELPER(COUNT_ARGS_MAX5(__VA_ARGS__)),\
    (__VA_ARGS__))

Это не имеет большого значения, но мне нравится, чтобы мой код был максимально лаконичным. Это также помогает экспоненциально, если вы используете несколько вариативных макросов, для уменьшения дублирования кода и возможных осложнений. Насколько мне известно, этот метод тоже переносимый. Я тестировал его на многих наиболее распространенных компиляторах, и они дали одинаковые результаты.

Пример использования:

int foo()
{
    ASSERT(one); // singleArgumentExpansion(one)
    ASSERT(two, "foopy"); // twoArgumentExpansion(two, "foopy")

    ERROR("Only print a title");
    ERROR("Error title", "Extended error description");
}
person Braden Steffaniak    schedule 04.06.2014
comment
Обратите внимание, мне нужно было удалить ';' в конце #define CALL_OVERLOAD или я получаю сообщение об ошибке error: expected ')' before ';' token с gcc4.9 - person ideasman42; 19.07.2014
comment
Основываясь на этом, вот пример, который использует до 16 аргументов для реализации макроса ELEM на основе var-args, stackoverflow.com/a/24837037 / 432509 (может быть интересно) - person ideasman42; 19.07.2014
comment
Поскольку этот ответ, IMHO, пока лучший, вероятно, стоит отметить, что он (довольно) переносимый, что он работает с MSVC, GCC, Clang (еще не проверял Intel). (не только MSVC) - person ideasman42; 23.07.2014

Microsoft переписала препроцессор C / C ++, но по умолчанию он не включен для обратной совместимости, то есть они предпочитают совместимость с ошибками со своими собственными продуктами вместо переносимости или соответствия стандартам.

Кажется, вы можете исправить __VA_ARGS__ обработку, добавив _ 2_ указать в командной строке.

person Antti Haapala    schedule 13.07.2020
comment
Для заинтересованных это официальное заявление Microsoft по теме: devblogs.microsoft.com/cppblog/ - person Rhaokiel; 30.07.2020
comment
Поддерживается с VS2019.6 с /Zc:preprocessor - person linquize; 15.12.2020