1

В одном из моих проектов я пытаюсь достичь более общего подхода для написания наших внутренних упрощенных 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 будет частью этого хорошего решения.

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

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

ответ

1

Если вы заинтересованы в использовании библиотеки Boost.Preprocessor, вам необходимо ознакомиться с двумя основными «типами данных»: sequence и tuple. Вы можете найти весь список макросов, которые библиотека использует в справочном разделе the documentation. Я объясню те, которые я использую ниже.

В интерфейсе есть два макроса: XML_TAG и DEFINE_XML_TAGS.
XML_TAG действительно прост, он просто ставит свои аргументы внутри двух наборов круглых скобок. Это приводит к тому, что любое количество используемых вами XML_TAG s будет преобразовано в последовательность, элементы которой являются кортежами (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 is ((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 и в зависимости от того, что передается DATA. Так 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 получает каждый из кортежей (struct_name, sequence_of_type_name_pairs). Он берет имя и добавляет пару круглых скобок после него, а затем вызывает GENERATE_VECTOR_OF_MEMBER_NAMES с sequence_of_type_name_pairs. GENERATE_VECTOR_OF_MEMBER_NAMES сначала добавляет обязательный член «имя», а затем делает что-то с BOOST_PP_ENUM, очень похожее на макрос, объясненный выше, с той разницей, что ему нужно сделать небольшой трюк, потому что в текущей последовательности кортежей нет двух наборов круглых скобок (это объясняется here в третьем подходе). GENERATE_MEMBER_NAME_SEQUENCE затем просто берет имя члена, преобразует его в строку и затем помещает в него круг круглых скобок.

GENERATE_STRUCT_DEFS
BOOST_PP_REPEAT(N,MACRO,DATA) называет MACRO N раз, передавая данные и текущий индекс повторения. 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 и все, кажется, сработало, но вы, вероятно, не должны доверять ему вслепую.

Test Running on 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; 
} 
+0

Wow. Большое спасибо за это подробное и полное решение. Это действительно работает для меня. Хотя мне нужно некоторое время, чтобы переварить макросы. Я просто понял только простые. Я должен признать, что «форсированный препроцессор» не такой уж сложный, как я думал в первую очередь. По-прежнему существует недостаток, если 'XML_TAG' не содержит другого свойства, кроме' name'. Случается для меня в случае 'XML_TAG (Container)'. Компилятор говорит об ошибке C2065: '" BOOST_PP_SEQ_ENUM_0 ": не объявленный идентификатор.'. Кажется, мне нужно это самостоятельно решить. – Aleph0

+0

У меня нет времени, чтобы изменить ответ на данный момент, но я думаю, что [это] (http://melpon.org/wandbox/permlink/jJDFimOiXLIj2aE8) ([Visual C++ on rextester] (http: // rextester .com/KLC36940)) решает проблему с structs, у которых нет других свойств помимо 'name'. – llonesmiz

 Смежные вопросы

  • Нет связанных вопросов^_^