С++ 11 вложенный вызов макроса?

Он говорит в С++ std 16.3.4:

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

Если имя заменяемого макроса найдено во время этого сканирования списка замены (не включая остальная часть токенов предварительной обработки исходных файлов), он не заменяется.

Кроме того, если любые вложенные замены сталкиваются с именем заменяемого макроса, он не заменяется.

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

Что такое замена вложенных макросов?

В частности, рассмотрим:

#define f(x) 1 x
#define g(x) 2 x

g(f)(g)(3)

Я бы ожидал следующего:

g(f)(g)(3)    <-- first replacement of g, ok
2 f(g)(3)     <-- nested replacement of f, ok
2 1 g(3)      <-- second nested replacement of g, don't replace, stop

Однако gcc неожиданно продолжает вторую замену g, создавая:

2 1 2 3

Любые идеи?

Update:

После долгих исследований позвольте мне прояснить эту проблему с помощью более простого примера:

#define A(x) B
#define B(x) A(x)

A(i)(j)

Это расширяется следующим образом:

A(i)(j)
B(j)
A(j)

В стандарте не указывается, следует ли расширять A(j) до B или нет. Комитет решил оставить его таким образом, потому что не предполагается, что программы реального мира будут зависеть от такого поведения, поэтому оба оставшихся A(j) нерасширенные и расширяющиеся A(j) до B считаются совместимыми.

Ответы

Ответ 1

Это объясняет первоначальное намерение и почему в стандарт по этому вопросу не добавлено никаких разъяснений:

http://open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#268


268. Подавление имени макроса в тексте с повторной заменой

Раздел: 16.3.4 [cpp.rescan]   Статус: открыть   Отправитель: Bjarne Stroustrup   Дата: 18 января 2001 г.

Из Стандарта не ясно, каков будет результат следующего примера:

#define NIL(xxx) xxx
#define G_0(arg) NIL(G_1)(arg)
#define G_1(arg) NIL(arg)
G_0(42)

Соответствующий текст стандарта приведен в пункте 16.3.4 [cpp.rescan]:

[snipped]

Последовательность разложения G0(42) выглядит следующим образом:

G0(42)
NIL(G_1)(42)
G_1(42)
NIL(42)

Вопрос в том, подходит ли использование NIL в последней строке этой последовательности для замены без замены в цитированном тексте. Если это так, результат будет NIL(42). Если это не так, результат будет просто 42.

Первоначальное намерение комитета J11 в этом тексте состояло в том, что результат должен быть 42, о чем свидетельствует первоначальное псевдокод-описание алгоритма замены, предоставленного Dave Prosser, его автором. В английском описании, однако, опускаются некоторые из тонкостей псевдокода и, следовательно, возможно, он дает неверный ответ для этого случая.

Предлагаемое разрешение (Майк Миллер): [snipped]

Примечания (через Tom Plum) с апреля 2004 года Совещание WG14:

Еще в 1980 году несколько представителей WG14 поняли, что между "не заменяющей" формулировкой и попытками создания псевдокода существуют небольшие различия. Решение комитета состояло в том, что никакие реалистичные программы "в дикой природе" не решаются в этой области, и попытка уменьшить неопределенности не стоит риска изменения статуса соответствия реализаций или программ.

Ответ 2

g(x) всегда заменяется на 2 x. Во второй вложенной замене g вы вызываете g(x) с помощью x=3, поэтому это приводит к результату 2 3. Я понимаю, что компилятор не заменяет макрос его значением, если это вызывает бесконечный цикл:

#define g( x ) f( x )
#define f( x ) g( x )

g( 1 ) -- > f( 1 ) --> stop 

Ответ 3

Потому что он упоминает "(не считая остальных токенов предварительной обработки исходных файлов)." Первая замена g видит только f. Теперь f имеет право на замену токенами предварительной обработки, взятыми из остальной части файла, но нигде программа не записывает, что f была создана g. Замена f(g) дает g, который также не используется рекурсией.

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