В конце этого ответа есть возможное решение.
Является ли такое поведение ошибкой или оно проистекает из какого-то принципа?
Это происходит из двух принципов, взаимодействие которых довольно тонкое. Так что я согласен, что это удивительно, но это не ошибка.
Эти два принципа заключаются в следующем:
Внутри замены вызова макроса этот макрос не раскрывается. (См. Руководство GCC, раздел 3.10.5, Самореферентные макросы или стандарт C, 6.10.3.4, абзац 2.) Это предотвращает рекурсивное раскрытие макросов, которое в большинстве случаев приведет к бесконечной рекурсии, если это разрешено. Хотя вполне вероятно, что никто не ожидал такого использования, оказывается, что будут способы использования рекурсивного расширения макросов, которые не приведут к бесконечной рекурсии (см. документация библиотеки препроцессоров Boost для тщательное обсуждение этой проблемы), но сейчас стандарт не изменится.
Если ##
применяется к аргументу макроса, он подавляет раскрытие макроса этого аргумента. (См. Руководство GCC, раздел 3.5, Объединение, или стандарт C, 6.10. 3.3, абзац 2.) Подавление расширения является частью стандарта C, но расширение GCC/Clang, позволяющее использовать ##
для условного подавления запятой, предшествующей __VA_ARGS__
, является нестандартным. (См. Руководство GCC, раздел 3.6, Макросы Variadic.) Очевидно, расширение по-прежнему соблюдает стандартное правило о нерасширении объединенных аргументов макроса.
Любопытно, что второй момент, касающийся необязательного подавления запятых, заключается в том, что вы почти никогда не замечаете этого на практике. Вы можете использовать ##
для условного подавления запятых, и аргументы по-прежнему будут расширяться как обычно:
#define SHOW_ARGS(arg1, ...) Arguments are (arg1, ##__VA_ARGS__)
#define DOUBLE(a) (2 * a)
SHOW_ARGS(DOUBLE(2))
SHOW_ARGS(DOUBLE(2), DOUBLE(3))
Это расширяется до:
Arguments are ((2 * 2))
Arguments are ((2 * 2), (2 * 3))
И DOUBLE(2)
, и DOUBLE(3)
раскрываются нормально, несмотря на то, что один из них является аргументом оператора конкатенации.
Но в расширении макросов есть одна тонкость. Расширение происходит дважды:
Во-первых, расширяются аргументы макроса. (Это расширение находится в контексте текста, который вызывает макрос.) Эти расширенные аргументы заменяются параметрами в теле замены макроса (но только в том случае, если параметр не является аргументом для #
или ##
).
Затем к списку токенов замены применяются операторы #
и ##
.
Наконец, полученные заменяющие токены вставляются во входной поток, чтобы они снова расширялись. На этот раз расширение находится в контексте макроса, поэтому рекурсивный вызов подавляется.
Имея это в виду, мы видим, что в SHOW_ARGS(DOUBLE(2), DOUBLE(3))
DOUBLE(2)
расширяется на шаге 1 перед вставкой в список маркеров замены, а DOUBLE(3)
расширяется на шаге 3 как часть списка маркеров замены.
Это не имеет значения для DOUBLE
внутри SHOW_ARGS
, так как это разные макросы. Но разница стала бы очевидной, если бы это был один и тот же макрос.
Чтобы увидеть разницу, рассмотрим следующий макрос:
#define INVOKE(A, ...) A(__VA_ARGS__)
Этот макрос создает вызов макроса (или вызов функции, но здесь нас интересует только тот случай, когда это макрос). То есть по очереди INVOKE(X, Y)
в X(Y)
. (Это упрощение полезной функции, когда именованный макрос фактически вызывается несколько раз, возможно, с немного разными аргументами.)
Это отлично работает с SHOW_ARGS
:
INVOKE(SHOW_ARGS, one arg)
⇒ Arguments are (one arg)
Но если мы попытаемся INVOKE
сам макрос INVOKE
, мы обнаружим, что запрет на рекурсивный вызов вступает в силу:
INVOKE(INVOKE, SHOW_ARGS, one arg)
⇒ INVOKE(SHOW_ARGS, one arg)
«Конечно», мы могли бы расширить INVOKE
в качестве аргумента до INVOKE
:
INVOKE(SHOW_ARGS, INVOKE(SHOW_ARGS, one arg))
⇒ Arguments are (Arguments are (one arg))
Это прекрасно работает, потому что внутри INVOKE
нет ##
, поэтому раскрытие аргумента не подавляется. Но если расширение аргумента было подавлено, то аргумент будет вставлен в тело макроса нераскрытым, а затем станет рекурсивным расширением.
Итак, что происходит в вашем примере:
#define M0(x, ...) { x, ## __VA_ARGS__ }
M0(M0(1,2), M0(3,4), M0(5,6))
⇒ { { 1,2 }, M0(3,4), M0(5,6) }
Здесь первый аргумент внешнего M0
, M0(1,2)
, не используется с ##
, поэтому он расширяется как часть вызова. Два других аргумента являются частью __VA_ARGS__
, который используется с ##
. Следовательно, они не расширяются до замены в списке замены макроса. Но как часть списка замены макроса, их расширение подавляется правилом запрета рекурсивных макросов.
Вы можете легко обойти это, определив две версии макроса M0
с одинаковым содержимым, но разными именами (как предлагается в комментарии к OP):
#define M0(x, ...) { x, ## __VA_ARGS__ }
M0(M1(1,2), M1(3,4), M1(5,6))
⇒ { { 1,2 }, { 3,4 }, { 5,6 } }
Но это не очень приятно.
Решение: используйте __VA_OPT__
C++2a будет включать новую функцию, разработанную специально для подавления запятых в вызовах с переменным числом аргументов: макрос, похожий на функцию __VA_OPT__
. Внутри вариативного расширения макроса __VA_OPT__(x)
заменяется своим аргументом при условии, что в вариативных аргументах есть хотя бы один токен. Но если __VA_ARGS__
расширяется до пустого списка токенов, то же самое происходит и с __VA_OPT__(x)
. Таким образом, __VA_OPT__(,)
можно использовать для условного подавления запятой точно так же, как расширение GCC ##
, но в отличие от ##
оно не вызывает подавление расширения макроса.
В качестве расширения стандарта C последние версии GCC и Clang реализуют __VA_OPT__
как для C, так и для C++. (См. Руководство GCC, раздел 3.6, Макросы Variadic.) вы готовы полагаться на относительно свежие версии компилятора, есть очень чистое решение:
#define M0(x, ...) { x __VA_OPT__(,) __VA_ARGS__ }
M0(M0(1,2), M0(3,4), M0(5,6))
⇒ { { 1 , 2 } , { 3 , 4 }, { 5 , 6 } }
Примечания:
Вы можете увидеть эти примеры на Godbolt.
Этот вопрос изначально был закрыт как дубликат Макросы Variadic: расширение вставленных токенов но я не думаю, что этот ответ действительно адекватен для этой конкретной ситуации.
person
rici
schedule
16.02.2020
M0(x, ...)
иN0(x, ...)
с одним и тем же расширением у меня работает, если я использую, например.N0(M0(1,2), M0(3,4), M0(5,6))
илиN0(M0(1), M0(3), M0(5))
. (Я не знаю, является ли это гарантированным поведением.) - person Bodo   schedule 14.02.2020#define M1(...) { __VA_ARGS__ }
при использовании какM1(M1(1, 2), M1(3, 4), M1(5, 6))
илиM1(M1(1), M1(3), M1(5))
. - person Bodo   schedule 14.02.2020...
и__VA_ARGS__
без##
, но я потратил много времени на то, чтобы разобраться со странным поведением, которого я до сих пор не понимаю - person Maciek Godek   schedule 14.02.2020