Удивительное расширение вариативных макросов препроцессора GNU C при наличии оператора ##

Если мы определяем макрос

#define M(x, ...) { x, __VA_ARGS__ }

а затем используйте его, передавая себя в качестве аргумента

M(M(1, 2), M(3, 4), M(5, 6))

затем он расширяется до ожидаемой формы:

{ { 1, 2 }, { 3, 4 }, { 5, 6 } }

Однако, когда мы используем оператор ## (для предотвращения появления в выводе висячей запятой в случае вызова одного аргумента, как описано в документации в руководстве GCC), т.е.

#define M0(x, ...) { x, ## __VA_ARGS__ }

то расширение аргументов в

M0(M0(1,2), M0(3,4), M0(5,6))

вроде останавливается после первого аргумента, т.е. получаем:

{ { 1,2 }, M0(3,4), M0(5,6) }

Является ли такое поведение ошибкой или оно проистекает из какого-то принципа?

(Я также проверил это с помощью clang, и оно ведет себя так же, как GCC)


person Maciek Godek    schedule 14.02.2020    source источник
comment
@ЕвгенийШ. это касается того же вопроса; в комментарии jdolan утверждает, что в руководстве GCC говорится, что вставленные токены (все, что после ##) не расширяются, но он не дал указателя на руководство.   -  person Maciek Godek    schedule 14.02.2020
comment
Когда я использую два макроса с разными именами, например. 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
comment
И для меня эта версия также работает #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
comment
@bodo да, в конце концов я выбрал ... и __VA_ARGS__ без ##, но я потратил много времени на то, чтобы разобраться со странным поведением, которого я до сих пор не понимаю   -  person Maciek Godek    schedule 14.02.2020


Ответы (1)


В конце этого ответа есть возможное решение.

Является ли такое поведение ошибкой или оно проистекает из какого-то принципа?

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

Эти два принципа заключаются в следующем:

  1. Внутри замены вызова макроса этот макрос не раскрывается. (См. Руководство GCC, раздел 3.10.5, Самореферентные макросы или стандарт C, 6.10.3.4, абзац 2.) Это предотвращает рекурсивное раскрытие макросов, которое в большинстве случаев приведет к бесконечной рекурсии, если это разрешено. Хотя вполне вероятно, что никто не ожидал такого использования, оказывается, что будут способы использования рекурсивного расширения макросов, которые не приведут к бесконечной рекурсии (см. документация библиотеки препроцессоров Boost для тщательное обсуждение этой проблемы), но сейчас стандарт не изменится.

  2. Если ## применяется к аргументу макроса, он подавляет раскрытие макроса этого аргумента. (См. Руководство 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) раскрываются нормально, несмотря на то, что один из них является аргументом оператора конкатенации.

Но в расширении макросов есть одна тонкость. Расширение происходит дважды:

  1. Во-первых, расширяются аргументы макроса. (Это расширение находится в контексте текста, который вызывает макрос.) Эти расширенные аргументы заменяются параметрами в теле замены макроса (но только в том случае, если параметр не является аргументом для # или ##).

  2. Затем к списку токенов замены применяются операторы # и ##.

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

Примечания:

  1. Вы можете увидеть эти примеры на Godbolt.

  2. Этот вопрос изначально был закрыт как дубликат Макросы Variadic: расширение вставленных токенов но я не думаю, что этот ответ действительно адекватен для этой конкретной ситуации.

person rici    schedule 16.02.2020