Страницы

Поиск по вопросам

вторник, 16 октября 2018 г.

Автогенерация классов в C++

Имеется структура данных, подобная следующей:
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 либо делать свой клон с каким вам надо количеством итераций.

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

Отправить комментарий