Стандартное поведение препроцессора С++

Я изучаю стандарт С++ о точном поведении препроцессора (мне нужно реализовать какой-то препроцессор С++). Из того, что я понимаю, должен быть приведен пример, который я составил (чтобы помочь моему пониманию) ниже:

#define dds(x) f(x,
#define f(a,b) a+b
dds(eoe)
su)

Я ожидаю, что первая функция, такая как вызов макроса dds(eoe), будет заменена на f(eoe, (обратите внимание на запятую в заменяющей строке), которая затем рассматривается как f(eoe,su) при повторном сканировании ввода.

Но тест с VС++ 2010 дал мне это (я сказал VС++ вывести предварительно обработанный файл):

eoe+et_leoe+et_l
su)

Это противоречит интуиции и, очевидно, неверно. Это ошибка с VС++ 2010 или мое непонимание стандарта С++? В частности, неправильно ли вводить запятую в конце строки замены, как я? Мое понимание стандартной грамматики С++ заключается в том, что там разрешен любой preprocessing-token.

EDIT:

У меня нет GCC или других версий VС++. Может ли кто-нибудь помочь мне проверить эти компиляторы.

Ответы

Ответ 1

Насколько я понимаю, в [cpp.subst/rescan] частях стандарта нет ничего, что делает незаконным, а clang и gcc имеют право расширять его как eoe+su, а поведение MSC (Visual С++) должен быть указан как ошибка.

Мне не удалось заставить его работать, но мне удалось найти уродливое обходное решение MSC для вас, используя переменные - вы можете найти его полезным или не можете, но в любом случае это:

#define f(a,b) (a+b
#define dds(...) f(__VA_ARGS__)

Развертывается как:

(eoe+
su)

Конечно, это не будет работать с gcc и clang.

Ответ 2

Мой ответ действителен для препроцессора C, но согласно Является ли препроцессор С++ идентичным препроцессору C?, различия не имеют отношения к этому случаю.

Из C, Справочное руководство, 5-е издание:

Когда функция-макро-вызов активируется, весь вызов макроса заменяется после обработки параметров копией тела. параметр обработка продолжается следующим образом. Фактические строки токенов аргументов связанных с соответствующими формальными именами параметров. Копия затем создается тело, в котором каждое вхождение формального параметра имя заменяется копией фактической последовательности маркеров параметра связанные с ним. Затем эта копия тела заменяет макрос вызов. [...] Как только макровызов был расширен, сканирование макросов возобновляется в начале расширения, так что имена макросов могут быть признанным в рамках расширения с целью дальнейшего макроса замена.

Обратите внимание на слова в расширении. Это делает ваш пример недействительным. Теперь объедините его с этим: UPDATE: прочитайте комментарии ниже.

[...] Макрос вызывается путем записи его имени, левой скобки, то после фактической последовательности токенов-аргументов для каждого формального параметра, затем правую круглую скобку. Фактические последовательности токенов-аргументов разделенные запятыми.

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

Об этом может быть трудно думать, но я считаю, что то, что должно произойти с вашим примером, заключается в том, что имя макроса f распознается во время повторного сканирования, а так как последующая обработка токена показывает вызов макроса для f(), ваш пример правильно и должно выводить то, что вы ожидаете. GCC и clang дают правильный результат, и в соответствии с этим рассуждением это также будет справедливо (и выдает эквивалентные выходы):

#define dds f
#define f(a,b) a+b

dds(eoe,su)

И действительно, вывод предварительной обработки в обоих примерах одинаковый. Что касается вывода, который вы получаете с VС++, я бы сказал, что вы обнаружили ошибку.

Это согласуется с разделом C99 6.10.3.4, а также с стандартным разделом С++ 16.3.4, повторным сканированием и последующей заменой:

После замены всех параметров в списке замены и # и ## обработка, все маркеры предварительной обработки метки метки помещаются. Затем результирующая повторяющаяся последовательность токенов предварительной обработки, а также все последующие preeccessing tokens исходного файла, для замены других имен макросов.

Ответ 3

Ну, проблема, которую я вижу, заключается в том, что препроцессор выполняет следующие

ddx (x) становится f (x,

Однако f (x, также определяется (даже если он определен как f (a, b)), поэтому f (x, расширяется до x + мусора.

Итак, ddx (x), наконец, преобразуется в x + мусор (потому что вы определили f (smthing,).

Ваш dds (eoe) фактически расширяется в + b, где a - eoe, а b - et_l. И он делает это дважды по любой причине:).

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