Имеется структура данных, подобная следующей:
Group_1
Type_1_1 Variable_1_1
. . .
Type_1_N Variable_1_N
Group_1_1 Name_1_1
Type_1_1_1 Variable_1_1_1
. . .
Type_1_1_N1 Variable_1_1_N1
. . .
Group_1_M Name_1_M
Type_1_M_1 Variable_1_M_1
. . .
Type_1_M_N2 Variable_1_M_N2
Важно, что число групп и число уровней вложения может быть произвольным. Внутри каждой группы могут быть как другие группы, так и переменные. В общем случае на типы переменных никакие ограничения не накладываются.
Для указанной структуры необходимо построить C++-код следующего вида:
class Group_1_1 {
public :
Type_1_1_1 Variable_1_1_1;
// . . .
Type_1_1_N1 Variable_1_1_N1;
void f(Worker& worker) {
worker.begin("Group_1_1");
worker.work("Variable_1_1_1", Variable_1_1_1);
// . . .
worker.work("Variable_1_1_N1", Variable_1_1_N1);
worker.end();
}
}
// . . .
class Group_1 {
public :
Type_1_1 Variable_1_1;
// . . .
Type_1_N Variable_1_N;
Group_1_1 Name_1_1;
// . . .
Group_1_M Name_1_M;
void f(Worker& worker) {
worker.begin("Group_1");
worker.work("Variable_1_1", Variable_1_1_1);
// . . .
worker.work("Variable_1_N", Variable_1_1_N1);
Name_1_1.f(worker);
// . . .
Name_1_M.f(worker);
worker.end();
}
}
Здесь Worker - некоторый внешний класс.
Построить код необходимо без использования каких-либо внешних инструментов, можно использовать только стандартные возможности компилятора и препроцессора. То есть можно использовать макросы и шаблоны, но нельзя запускать какие-либо программы, не входящие в состав компилятора. Можно при этом ограничиться стандартом c++11, совместимость с предыдущими стандартами не требуется. Решение должно работать по меньшей мере со следующими компиляторами: GCC 4.9+ (Linux), MinGW 4.9+ (Windows), MSVC 2015.
Собственно вопрос: можно ли это реализовать и если можно, то как?
Ответ
Предлагаю решение с ипользованием boost::preprocessor. Пока что это решение только половины задачи, генерации отдельных классов.
Входные данные должны быть представлены примерно так:
#define GEN_GROUP 1
#define GEN_VAR 0
#define MY_STRUCT \
(GEN_GROUP, AType, A, (GEN_VAR, int, AInt) \
, (GEN_GROUP, BType, B, (GEN_VAR, int, BInt) \
, (GEN_VAR, float, BFloat) \
) \
, (GEN_VAR, char, AChar) \
)
В понятиях boost::preprocessor это кортежи (tuple). А точнее кортежи которые содержат другие кортежи и таким образом образуют дерево. Струтура это дерева такая: элемент 0 - тип узла (группа или переменная), элемент 1 - тип данных, 2 - имя узла, и в случае группы остальные элементы начиная с 3 это поля или подгруппы:
группа = (1, тип, имя, [переменная | группа]+ )
переменная = (0, тип, имя)
Использовать эту структуру надо следующим образом, приведу просто код с коментариями:
#include
#define GEN_NODE_TYPE(node) BOOST_PP_TUPLE_ELEM(0, node)
#define GEN_TYPE(node) BOOST_PP_TUPLE_ELEM(1, node)
#define GEN_NAME(node) BOOST_PP_TUPLE_ELEM(2, node)
#define GEN_CHILDS(node) \ // конвертируем в список, выкусываем хвост начиная с элемента 3
BOOST_PP_LIST_REST_N(3, BOOST_PP_TUPLE_TO_LIST(node))
// функтор для генерации полей класа вида "тип имя;"
#define GENERATE_MEMBER_PRED(r, data, member) \
GEN_TYPE(member) GEN_NAME(member);
// функтор для генерации вызовов worker внутри фнукции f
#define GENERATE_WORK_CALL_PRED(r, data, member) \
BOOST_PP_IF(GEN_NODE_TYPE(member) \ // если тип "группа"
, GEN_NAME(member).f(worker) \ // то "имя.f(worker)"
, worker.work(BOOST_PP_STRINGAZE(GEN_NAME(member)) \ // заковычиваем имя
,GEN_NAME(2, member)) \ // иначе worker.work("имя", имя)
); // точка с запятой для всех
#define GENERATE_CLASS(node) \
class GEN_TYPE(node) \ // "class тип"
{ \
public: \
\ // генерируем поля
BOOST_PP_LIST_FOR_EACH( \
GENERATE_MEMBER_PRED \ // выполняем для каждого элемента
, _ \ // здесь можно пусто
, GEN_CHILDS(node) \ // список детей
) \
\
void f(Worker & worker) \
{ \
worker.begin(BOOST_PP_STRINGAZE(GEN_TYPE(member))); \ // заковычивам тип
\
\ // генерируем вызовы worker для полей
BOOST_PP_LIST_FOR_EACH( \
GENERATE_WORK_CALL_PRED \ // выполняем для каждого ребенка
, _ \ // здесь можно пусто
, GEN_CHILDS(node) \ // список детей
) \
\
worker.end(); \
} \
};
Результат для GENERATE_CLASS(MY_STRUCT)
class AType
{
public:
int AInt;
BType B;
char AChar;
void f(Worker & worker)
{
worker.begin("AType");
worker.work("AInt", AInt);
B.f(worker);
worker.work("AChar", AChar);
worker.end();
}
};
Как видите, классы для вложенных групп не генерируются, но это уже хоть что-то. Чтобы сделать вложенные группы необходимо реализовать обход входного дерева, при котором для каждого узла, если он группа, вызывать GENERATE_CLASS. Так как препроцессор не поддерживает рекурсию макросов, обход надо делать нерекурсивный со стеком. На boost::preprocessor по идее это возможно, но сложно, предлагаю это для самостоятельной проработки.
Внимание! Самое большое ограничение такого подхода в том, что бесконечную структуру обработать не получится. Так как количество итераций в любом циклическом операторе boost::preprocessor ограничено, равно как и в любом другом подобном инструменте основанном на препроцессоре Си. Сейчас, например, максимальное количество итераций для FOR_* составляет 256. Поэтому для больших циклов вам нужно править библиотеку boost::preprocessor либо делать свой клон с каким вам надо количеством итераций.
Комментариев нет:
Отправить комментарий