Аргументы сначала заменяются макросами, а не повторно сканируются, чтобы разделить их запятыми, поэтому макрос, который становится 1, 4
, остается одним аргументом. Но после замены макроса новая последовательность повторно сканируется, и запятые разделяют аргументы.
Per C 2018 6.10.3.1 1, аргументы макроса обрабатываются для замены макроса до того, как сам макрос replace:
… Перед заменой токены предварительной обработки каждого аргумента полностью заменяются макросами, как если бы они формировали остальную часть файла предварительной обработки; другие токены предварительной обработки недоступны.
Итак, в FIRST_ARG(ELEMENTS(FIRST_ARG))
мы сначала заменим ELEMENTS(FIRST_ARG)
, который выдает FIRST_ARG(1, 2, 3), FIRST_ARG(4, 5, 6)
. Эта замена повторно сканируется для дальнейшей замены, согласно 6.10.3.4 1:
… Затем результирующая последовательность токенов предварительной обработки затем сканируется вместе со всеми последующими токенами предварительной обработки исходного файла, чтобы заменить другие имена макросов для замены .
В результате повторного сканирования FIRST_ARG(1, 2, 3), FIRST_ARG(4, 5, 6)
становится 1, 4
. Таким образом, результат обработки аргумента для этого оригинального FIRST_ARG
равен 1, 4
. Обратите внимание, что это сам первый аргумент; он не обрабатывается повторно, поэтому запятая вызывает разделение аргументов - она заменяет значение первого параметра на FIRST_ARG
. Затем FIRST_ARG
заменяется этим значением аргумента, 1, 4
.
. В FIRST_ARG_EXPANDED(ELEMENTS(FIRST_ARG))
мы сначала заменяем аргумент, в результате чего получаем аргумент 1, 4
, как указано выше, и затем имеем FIRST_ARG_EXPANDED(1, 4)
. Это заменяется на FIRST_ARG(1, 4)
. Теперь это сканируется, и токены 1, 4
становятся отдельными аргументами для FIRST_ARG
, поэтому результатом является первый аргумент, 1
.
Таким образом, замена аргумента макроса приводит к один аргумент. Если вы хотите, чтобы он повторно сканировался для разделения на несколько аргументов через запятую, вы должны использовать другой макрос.