Препроцессор Boost - странный результат

Проверьте следующий макрос:

#define INPUT (char, "microsecond", "us")(int, "millisecond", "ms")(int, "second", "s")(int, "minute", "min")(float, "hour", "h")

Цель состоит в том, чтобы добавить двойные скобки вокруг каждого кортежа, в результате чего:

((char, "microsecond", "us"))((int, "millisecond", "ms"))((int, "second", "s"))((int, "minute", "min"))((float, "hour", "h"))

Теперь я использую следующие макросы для выполнения этой работы:

#define ADD_PAREN_1(A, B, C) ((A, B, C)) ADD_PAREN_2
#define ADD_PAREN_2(A, B, D) ((A, B, C)) ADD_PAREN_1
#define ADD_PAREN_1_END
#define ADD_PAREN_2_END
#define OUTPUT0 ADD_PAREN_1 INPUT
#define OUTPUT1 BOOST_PP_CAT( OUTPUT0, _END )

Результат выглядит следующим образом:

OUTPUT0 в порядке:

((char, "microsecond", "us")) ((int, "millisecond", C)) ((int, "second", "s")) ((int, "minute", C)) ((float, "hour", "h")) ADD_PAREN_2

Но когда вызывается BOOST_PP_CAT, результатом OUTPUT1 является:

float

Я не понимаю такого поведения. Любые подсказки?

Примечание. Я использую Visual Studio 2010.


person Mark    schedule 22.01.2013    source источник
comment
Совет дня: не используйте макросы.   -  person Alex Chamberlain    schedule 22.01.2013
comment
@Alex: Очень полезно. Спасибо !   -  person Mark    schedule 22.01.2013
comment
почему бы просто не использовать поиск и замену в регионе? Есть ли какая-то причина, по которой скобки должны быть удвоены препроцессором, а не редактором?   -  person AShelly    schedule 22.01.2013
comment
@ASHelly: Да, есть причина. Я уже описал это, но пользователь удалил свой ответ. Причина в том, что результат перенаправляется в макрос BOOST_PP_SEQ_FOR_EACH, который требует дополнительных скобок. Поэтому ручной поиск и замена не вариант.   -  person Mark    schedule 23.01.2013


Ответы (1)


Препроцессор работает путем сканирования и расширения. Поэтому, когда он расширяет ваш макрос OUTPUT0, он дает:

ADD_PAREN_1 INPUT
^

Затем он просматривает следующий токен, чтобы увидеть, является ли он скобкой, и если это так, он вызывает ADD_PAREN_1 как макрос функции. Однако он увидит только INPUT, поэтому не вызывает ADD_PAREN_1. Затем он сканирует и расширяет следующий токен:

ADD_PAREN_1 INPUT
            ^

Что приведет к этому:

ADD_PAREN_1 (char, "microsecond", "us")(int, "millisecond", "ms")(int, "second", "s")(int, "minute", "min")(float, "hour", "h")
            ^

Затем, когда вы попытаетесь использовать OUTPUT1, он расширится до этого:

BOOST_PP_CAT( OUTPUT0, _END )

Какой BOOST_PP_CAT расширит OUTPUT0, а затем объединит токены, так что в конечном итоге вы получите это:

 ADD_PAREN_1 (char, "microsecond", "us")(int, "millisecond", "ms")(int, "second", "s")(int, "minute", "min")(float, "hour", "h") ## _END

Как видите, вы объединяете скобки с _END, что недопустимо и приводит к ошибке компилятора. В Visual Studio вы можете увидеть другие результаты, так как их препроцессор работает загадочным образом.

В конечном счете, чтобы заставить его работать, вам просто нужно применить дополнительное сканирование в макросе OUTPUT0, что-то вроде этого:

#define X(x) x
#define OUTPUT0 X(ADD_PAREN_1 INPUT)

Который будет работать в препроцессорах C, я не знаю, будет ли он точно работать в Visual Studio (сейчас у меня нет доступа к нему, чтобы проверить), но я знаю, что это работает:

#define ADD_PAREN(x) BOOST_PP_CAT(ADD_PAREN_1 x, _END)
#define ADD_PAREN_1(A, B, C) ((A, B, C)) ADD_PAREN_2
#define ADD_PAREN_2(A, B, D) ((A, B, C)) ADD_PAREN_1
#define ADD_PAREN_1_END
#define ADD_PAREN_2_END
#define OUTPUT1 ADD_PAREN(INPUT)

Что похоже на то, как они делают это в boost. Посмотрите, как BOOST_FUSION_ADAPT_ASSOC_STRUCT_FILLER используется макрос здесь.

person Paul Fultz II    schedule 22.01.2013
comment
В Visual Studio вы можете увидеть другие результаты, так как их препроцессор работает загадочным образом. Ага! Только что немного назад проделал тяжелую работу с препроцессором, включающую множество конкатенаций и повторное расширение вновь созданных вызовов макросов, и нашел много забавных фактов о своем препроцессоре... :) (Например, он обрабатывает __VA_ARGS__ как одиночный токен, если вы не расширите его .) Я обычно называю ваш X макрос EXPAND_ARG. +1 - person GManNickG; 23.01.2013
comment
@Paul: Большое спасибо за ваш ответ. Трюк с дополнительным сканированием решил проблему. - person Mark; 23.01.2013