9 апр. 2007 г.

Описание данных для кодогенерации с помощью Boost.Preprocessor (часть 1)

Boost.Preprocessor Sequence (последовательность) — удобный способ представления данных для препроцесора.

Выглядит примерно так:
#define MY_FRUITS (apples)(oranges)(pears)
Работать с этими данными можно, например, так:
BOOST_PP_SEQ_ENUM(MY_FRUITS)
Эта конструкция раскроется в список, разделенный запятыми:
apples, oranges, pears
Более сложная конструкция:
#define MY_DECLARE_FRUIT(r, data, elem) \
data elem = 0;

/* (1) */
BOOST_PP_SEQ_FOR_EACH(MY_DECLARE_FRUIT, int, MY_FRUITS)

#define MY_FRUIT_ARG(s, data, elem) \
data elem

/* (2) */
void TakeAllFruits(
BOOST_PP_SEQ_ENUM(
BOOST_PP_SEQ_TRANSFORM(
MY_FRUIT_ARG,
int,
MY_FRUITS
)
)
);

/* (3) */
TakeAllFruits(BOOST_PP_SEQ_ENUM(MY_FRUITS));
Получаем:
/* (1) */
int apples = 0;
int oranges = 0;
int pears = 0;

/* (2) */
void TakeAllFruits(
int apples,
int oranges,
int pears
);

/* (3) */
TakeAllFruits(apples, oranges, pears);
Практика показывает, что большей гибкостью обладает подход, при котором все генерирующие код конструкции полностью заворачиваются в макросы. То есть генерацию заголовка функции TakeAllFruits в пункте (2) лучше оформить таким образом:
#define MY_TAKE_ALL_FRUITS(fruits) \
void TakeAllFruits( \
BOOST_PP_SEQ_ENUM( \
BOOST_PP_SEQ_TRANSFORM( \
MY_FRUIT_ARG, \
int, \
fruits \
) \
) \
);

MY_TAKE_ALL_FRUITS(MY_FRUITS)
Последовательности можно делать вложенными. Это позволяет, например, реализовать на препроцессоре эквивалент «записей» (records).

Возьмем для примера условную структуру данных, описывающую геометрические фигуры:
Фигура:
  1. Способ рисования
  2. Список свойств
При помощи последовательностей этот набор данных можно выразить примерно таким образом:
#define MY_TRIANGLE   (0)
#define MY_SQUARE (1)
#define MY_QUADRANGLE (2)
#define MY_CIRCLE (3)

#define MY_SHAPES \
( \
(Triangle)(MY_TRIANGLE)(Polygon) \
( \
((Point)(vertex1)) \
((Point)(vertex2)) \
((Point)(vertex3)) \
) \
) \
( \
(Square)(MY_SQUARE)(Isogon) \
( \
((Point)(center)) \
((float)(radius)) \
) \
) \
( \
(Quadrangle)(MY_QUADRANGLE)(Polygon) \
( \
((Point)(vertex1)) \
((Point)(vertex2)) \
((Point)(vertex3)) \
((Point)(vertex4)) \
) \
) \
( \
(Circle)(MY_CIRCLE)(Circle) \
( \
((Point)(center)) \
((float)(radius)) \
) \
)
Да, кстати, совет общего характера: при написании многострочных макросов не забывайте следить за тем, чтобы конкатенирующий слэш '\' был последним символом в строке. Если после него окажется любой символ кроме перевода строки (даже пробел), следующая строка уже не будет считаться частью макроса. Хорошо если ваш редактор обращает внимание на этот факт при подсветке синтаксиса. Если же нет, включайте отображение пробельных символов когда пишете макросы.

Комментариев нет: