Генерация шаблонного кода путем преобразования аргументов в строковые литералы.

В одном из своих проектов я пытаюсь реализовать более общий подход к написанию наших собственных упрощенных XML-файлов. Для этого я успешно использовал boost-fusion.

Для каждого нового формата файла XML клиент должен написать следующее. Просто предположим, что файл XML содержит тег Person и тег Company.

#include <boost/fusion/include/define_struct.hpp>
#include <boost/variant.hpp>
#include <map>
#include <vector>

BOOST_FUSION_DEFINE_STRUCT(
(),
Person,
(std::string, name) // name is mandatory for all tags
(int, age))

BOOST_FUSION_DEFINE_STRUCT(
(),
Company,
(std::string, name) // name is mandatory for all tags
(int, noEmployees)
(std::string, location)
)

typedef boost::variant<Person, Company> Types;

std::vector<std::pair<Types, std::vector<std::string>>> xmlTags =
{
    {Person(), {"name", "age"}},
    {Company(), {"name", "noEmployees", "location"}},
};

int main(int argc, char**args) {
}

Я все еще не совсем удовлетворен приведенным выше решением, так как пользователю все еще нужно определить xmlTags, который должен генерироваться автоматически. Также должен быть сгенерирован Types. Клиент может забыть адаптировать карту, что приведет к ошибочным XML-файлам или к сбою XML Reader/Writer.

Хорошее решение может выглядеть так:

DEFINE_XML_TAGS(
    XML_TAG(
        Person,
        (int, age)
    )
    XML_TAG(
        Company,
        (int, noEmployees)
        (std::string, location)
    )
)

Генерация всего этого шаблонного кода для меня. Я думаю, что Boost-Preprocessor будет частью этого хорошего решения.

Но я понятия не имею, как добиться желаемого результата. Никогда не пользовался этой библиотекой. К счастью, наш компилятор поддерживает переменные аргументы шаблона.

Кто-нибудь знает, как добиться желаемого результата?


person Aleph0    schedule 29.07.2016    source источник


Ответы (1)


Если вы заинтересованы в использовании библиотеки Boost.Preprocessor, вам необходимо ознакомиться с двумя основными «типами данных»: последовательность и кортеж< /а>. Полный список макросов, которые использует библиотека, можно найти в справочном разделе документации. Я объясню те, которые я использую ниже.

В интерфейсе есть два макроса: XML_TAG и DEFINE_XML_TAGS.
XML_TAG действительно прост, он просто заключает свои аргументы в два набора скобок. Это приводит к тому, что сколько бы XML_TAG вы ни использовали, они будут преобразованы в последовательность, элементами которой являются кортежи (struct_name,sequence_of_type_and_name_pairs).
DEFINE_XML_TAGS — это макрос, который выполняет всю работу. Он использует три вспомогательных макроса GENERATE_STRUCT_DEFS, GENERATE_VARIANT_OF_TYPES и GENERATE_XMLTAGS.

GENERATE_VARIANT_OF_TYPES
Вызывает ENUMERATE_TYPES(TAG_SEQ) для получения списка типов, разделенных запятыми. Сейчас TAG_SEQ это ((Person,(int,age)))((Company,(int,noEmployees)(std::string,location))), и мы хотим иметь Person,Company. BOOST_PP_ENUM(SEQ) принимает последовательность и возвращает ее элементы, разделенные запятыми. Итак, нам нужно иметь BOOST_PP_ENUM((Person)(Company)). BOOST_PP_SEQ_FOR_EACH(MACRO,DATA,SEQ) вызывает MACRO с каждым из элементов в SEQ и любыми данными, которые вы передаете. Итак, BOOST_PP_SEQ_FOR_EACH(GET_TYPE_SEQUENCE,_,TAG_SEQ) звонит GET_TYPE_SEQUENCE с (Person,(int,age)) и (Company,(int,noEmployees)(sd:string,location)). Затем GET_TYPE_SEQUENCE просто берет первый элемент каждого кортежа и заключает его в скобки, используя BOOST_PP_TUPLE_ELEM.

GENERATE_XML_TAGS
Он вызывает GENERATE_PAIRS, который, в свою очередь, вызывает SEQ_FOR_EACH, используя GENERATE_ONE_PAIR. Как объяснялось в предыдущем разделе, GENERATE_ONE_PAIR получает каждый из кортежей (имя_структуры, последовательность_пар_имен_типа). Он берет имя и добавляет после него пару круглых скобок, а затем вызывает GENERATE_VECTOR_OF_MEMBER_NAMES с парами sequence_of_type_name_pairs. GENERATE_VECTOR_OF_MEMBER_NAMES сначала добавляет обязательный элемент "имя", а затем делает что-то с BOOST_PP_ENUM очень похожее на макрос, описанный выше, с той разницей, что он должен сделать небольшой трюк, потому что текущая последовательность кортежей не имеет двух наборов скобок (это объясняется
здесь в третьем подходе). Затем GENERATE_MEMBER_NAME_SEQUENCE просто берет имя члена, преобразует его в строку, а затем заключает его в скобки.

GENERATE_STRUCT_DEFS
BOOST_PP_REPEAT(N,MACRO,DATA) вызывает MACRO N раз, передавая DATA и текущий индекс повторения. GENERATE_ONE_STRUCT_DEF берет индекс-й элемент последовательности, затем сначала берет имя структуры и, наконец, последовательность пар имя-тип и вызывает DO_GENERATE_ONE_STRUCT_DEF с этими значениями. Наконец, DO_GENERATE_ONE_STRUCT_DEF создает вызов макроса BOOST_FUSION_DEFINE_STRUCT.

Я думаю, но я недостаточно осведомлен, чтобы быть уверенным, что в BOOST_FUSION_ADAPT_STRUCT_NAMESPACE_DEFINITION_END есть ошибка. Он использует BOOST_PP_REPEAT_1 напрямую, когда я думаю, что он должен просто использовать BOOST_PP_REPEAT. Я не определил и переопределил этот макрос, используя BOOST_PP_REPEAT, и все, кажется, работает, но вы, вероятно, не должны слепо доверять ему.

Выполнение теста на WandBox

define_xml_tags.hpp

#include <boost/fusion/include/define_struct.hpp>
#include <boost/variant.hpp>
#include <vector>
#include <utility>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/preprocessor/seq/enum.hpp>
#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/seq/size.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/tuple/elem.hpp>

//I think there is a bug in the original macro, it uses BOOST_PP_REPEAT_1 where I think it should use BOOST_PP_REPEAT, but I don't know enough to know for sure
#undef BOOST_FUSION_ADAPT_STRUCT_NAMESPACE_DEFINITION_END

#define BOOST_FUSION_ADAPT_STRUCT_NAMESPACE_DEFINITION_END(NAMESPACE_SEQ)       \
    BOOST_PP_REPEAT(                                                          \
        BOOST_PP_DEC(BOOST_PP_SEQ_SIZE(NAMESPACE_SEQ)),                         \
        BOOST_FUSION_ADAPT_STRUCT_NAMESPACE_END_I,                              \
        _)

//helps form a SEQUENCE of TUPLES
#define XML_TAG(NAME,MEMBER_SEQ) ((NAME,MEMBER_SEQ)) 

//helpers for GENERATE_STRUCT_DEFS, read from the bottom to the top
#define DO_GENERATE_ONE_STRUCT_DEF(NAME,MEMBER_SEQ) \
BOOST_FUSION_DEFINE_STRUCT( (), NAME, (std::string, name) MEMBER_SEQ)

#define GENERATE_ONE_STRUCT_DEF(Z,INDEX,TAG_SEQ) \
DO_GENERATE_ONE_STRUCT_DEF(BOOST_PP_TUPLE_ELEM(2,0,BOOST_PP_SEQ_ELEM(INDEX,TAG_SEQ)), BOOST_PP_TUPLE_ELEM(2,1,BOOST_PP_SEQ_ELEM(INDEX,TAG_SEQ)))

#define GENERATE_STRUCT_DEFS(TAG_SEQ) \
BOOST_PP_REPEAT(BOOST_PP_SEQ_SIZE(TAG_SEQ),GENERATE_ONE_STRUCT_DEF,TAG_SEQ)


//helpers for GENERATE_VARIANT_OF_TYPES, bottom to top
#define GET_TYPE_SEQUENCE(R,DATA,NAME_MEMBERSEQ_TUPLE) \
(BOOST_PP_TUPLE_ELEM(2,0,NAME_MEMBERSEQ_TUPLE))

#define ENUMERATE_TYPES(TAG_SEQ) \
BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_FOR_EACH(GET_TYPE_SEQUENCE,_,TAG_SEQ))

#define GENERATE_VARIANT_OF_TYPES(TAG_SEQ) \
typedef boost::variant<ENUMERATE_TYPES(TAG_SEQ)> Types;


//helpers for GENERATE_XMLTAGS, go from bottom to top in order to understand

//Heavily "inspired" from BOOST_FUSION_ADAPT_STRUCT
#define GENERATE_NAME_SEQUENCE_FILLER_0(X, Y)  \
    ((X, Y)) GENERATE_NAME_SEQUENCE_FILLER_1
#define GENERATE_NAME_SEQUENCE_FILLER_1(X, Y)  \
    ((X, Y)) GENERATE_NAME_SEQUENCE_FILLER_0
#define GENERATE_NAME_SEQUENCE_FILLER_0_END
#define GENERATE_NAME_SEQUENCE_FILLER_1_END

#define GENERATE_MEMBER_NAME_SEQUENCE(R,DATA,INDEX,TYPE_NAME_TUPLE) (BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(2,1,TYPE_NAME_TUPLE)))

#define GENERATE_VECTOR_OF_MEMBER_NAMES(MEMBER_SEQ) \
{ "name", BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_FOR_EACH_I(GENERATE_MEMBER_NAME_SEQUENCE,_,BOOST_PP_CAT(GENERATE_NAME_SEQUENCE_FILLER_0 MEMBER_SEQ,_END))) }

#define GENERATE_ONE_PAIR(R,DATA,NAME_MEMBERSEQ_TUPLE) \
{ BOOST_PP_TUPLE_ELEM(2,0,NAME_MEMBERSEQ_TUPLE)(), GENERATE_VECTOR_OF_MEMBER_NAMES(BOOST_PP_TUPLE_ELEM(2,1,NAME_MEMBERSEQ_TUPLE)) },

#define GENERATE_PAIRS(TAG_SEQ) \
BOOST_PP_SEQ_FOR_EACH(GENERATE_ONE_PAIR,_,TAG_SEQ)

#define GENERATE_XMLTAGS(TAG_SEQ) \
const std::vector<std::pair<Types,std::vector<std::string>>> xmlTags = { GENERATE_PAIRS(TAG_SEQ) };


//This is the actual macro, it simply invokes three different macros that do a different task each
#define DEFINE_XML_TAGS(TAG_SEQ) \
GENERATE_STRUCT_DEFS(TAG_SEQ) \
GENERATE_VARIANT_OF_TYPES(TAG_SEQ) \
GENERATE_XMLTAGS(TAG_SEQ)

main.cpp

#include <iostream>
#include <boost/fusion/include/io.hpp>
#include <boost/fusion/include/as_vector.hpp>
#include <boost/variant/static_visitor.hpp>

#include "define_xml_tags.hpp"



DEFINE_XML_TAGS(
    XML_TAG(
        Person,
        (int, age)
    )
    XML_TAG(
        Company,
        (int, noEmployees)
        (std::string, location)
    )
)

struct printer : boost::static_visitor<void> {
    void operator()(const Person& p) const
    {
        std::cout << "This is a person:" << boost::fusion::as_vector(p) << '\n';
    }

    void operator()(const Company& c) const
    {
        std::cout << "This is a company:" << boost::fusion::as_vector(c) << '\n';
    }
};

void identify(Types v)
{
    boost::apply_visitor(printer(),v);
}


int main() 
{
    Person p;
    p.name="John";
    p.age = 18;

    identify(p);

    Company c;
    c.name="Mpany Co";
    c.noEmployees=123;
    c.location="Fake St";
    identify(c);


    std::cout << "\nChecking xmlTags:\n";
    for(const auto& pair : xmlTags)
    {
        identify(pair.first);
        std::cout << "It has the following members:\n";
        for(const auto& str : pair.second)
            std::cout << str << '\n';
    }

    std::cout << std::endl;
}
person Community    schedule 30.07.2016
comment
Ух ты. Большое спасибо за это подробное и тщательное решение. Это действительно работает для меня. Хотя мне нужно время, чтобы переварить макросы. Я пока только простые понял. Должен признать, что boost-preprocessor не так страшен, как я думал изначально. Есть еще недостаток в случае, если XML_TAG не содержит другого свойства, кроме name. Происходит для меня в случае XML_TAG(Container). Компилятор пишет ошибку C2065: "BOOST_PP_SEQ_ENUM_0": non-declared identifier.. Кажется, я должен разобраться с этим самостоятельно. - person Aleph0; 01.08.2016
comment
В данный момент у меня нет времени изменять ответ, но я думаю, что это ( Visual c++ на rextester) решает проблему со структурами, не имеющими других свойств, кроме name. - person llonesmiz; 01.08.2016