1

In einem meiner Projekte versuche ich einen allgemeineren Ansatz für das Schreiben unserer eigenen vereinfachten XML-Dateien zu erreichen. Dazu habe ich erfolgreich boost-fusion verwendet.Generieren Boilerplate-Code durch Transformieren von Argumenten in String-Literale

Für jedes neue XML-Dateiformat muss der Client Folgendes schreiben. Angenommen, die XML-Datei enthält ein Tag Person und ein Tag 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) { 
} 

Ich bin noch nicht ganz zufrieden mit der Lösung oben, da der Benutzer immer noch definieren xmlTags benötigt, die automatisch erzeugt werden sollen. Auch Types sollte ebenfalls generiert werden. Der Client könnte vergessen, die Map anzupassen, was zu fehlerhaften XML-Dateien führt oder den XML Reader/Writer zum Absturz bringt.

Eine gute Lösung könnte so aussehen:

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

all diese vorformulierten Code generieren für mich. Ich denke, dass Boost-Preprocessor Teil dieser guten Lösung sein würde.

Aber ich habe keine Ahnung, wie Sie das gewünschte Ergebnis erreichen. Habe diese Bibliothek noch nie benutzt. Glücklicherweise unterstützt unser Compiler variadische Template-Argumente.

Kann jemand das gewünschte Ergebnis erzielen?

Antwort

1

Wenn Sie an der Verwendung der Boost.Preprocessor-Bibliothek interessiert sind, müssen Sie sich mit zwei grundlegenden "Datentypen" vertraut machen: sequence und tuple. Sie finden die gesamte Liste der Makros, die die Bibliothek verwendet, im Referenzabschnitt the documentation. Ich erkläre die, die ich unten verwende.

Es gibt zwei Makros in der Schnittstelle: und DEFINE_XML_TAGS.
XML_TAG ist wirklich einfach, es bringt nur seine Argumente in zwei Klammern. Dies führt dazu, dass jedoch viele s, die Sie verwenden, in eine Sequenz konvertiert werden, deren Elemente Tupel (struct_name,sequence_of_type_and_name_pairs) sind.
DEFINE_XML_TAGS ist das Makro, das die ganze Arbeit erledigt. Es verwendet drei Hilfsmakros GENERATE_STRUCT_DEFS, GENERATE_VARIANT_OF_TYPES und GENERATE_XMLTAGS.

GENERATE_VARIANT_OF_TYPES
Invokes ENUMERATE_TYPES(TAG_SEQ) um eine durch Kommata getrennte Liste von Typen zu erhalten. Gerade jetzt TAG_SEQ ist ((Person,(int,age)))((Company,(int,noEmployees)(std::string,location))) und wir wollen Person,Company haben. BOOST_PP_ENUM(SEQ) nimmt eine Sequenz und gibt ihre Elemente getrennt durch Kommas zurück. Also müssen wir BOOST_PP_ENUM((Person)(Company)) haben. BOOST_PP_SEQ_FOR_EACH(MACRO,DATA,SEQ) ruft MACRO mit jedem der Elemente in SEQ und den von Ihnen übergebenen Daten auf. So ruft BOOST_PP_SEQ_FOR_EACH(GET_TYPE_SEQUENCE,_,TAG_SEQ)GET_TYPE_SEQUENCE mit (Person,(int,age)) und (Company,(int,noEmployees)(sd:string,location)). GET_TYPE_SEQUENCE nimmt dann einfach das erste Element jedes Tupels und fügt es unter Verwendung von BOOST_PP_TUPLE_ELEM in Klammern ein.

GENERATE_XML_TAGS
Es ruft GENERATE_PAIRS die eine SEQ_FOR_EACH wiederum GENERATE_ONE_PAIR mit nennt. Wie im vorherigen Abschnitt GENERATE_ONE_PAIR erklärt, erhält jedes der Tupel (struct_name, sequence_of_type_name_pairs). Es nimmt den Namen und fügt ein Paar Klammern danach hinzu und ruft dann GENERATE_VECTOR_OF_MEMBER_NAMES mit der Sequenz_von_Typ_Name_Paaren auf. GENERATE_VECTOR_OF_MEMBER_NAMES zuerst fügt die obligatorische "name" -Member und dann etwas mit BOOST_PP_ENUM sehr ähnlich zu dem oben erläuterten Makro mit dem Unterschied, dass es einen kleinen Trick tun muss, weil die aktuelle Sequenz von Tupel nicht zwei Sätze von Klammern hat (das wird erklärt here im dritten Ansatz). GENERATE_MEMBER_NAME_SEQUENCE nimmt dann einfach den Namen des Members an, konvertiert es in eine Zeichenfolge und fügt dann eine Reihe von Klammern ein.

GENERATE_STRUCT_DEFS
BOOST_PP_REPEAT(N,MACRO,DATA) ruft MACRO N mal, Übergeben von Daten und den aktuellen Wiederholungsindex. GENERATE_ONE_STRUCT_DEF nimmt das index-te Element der Sequenz und nimmt dann zuerst den Namen der Struktur und zuletzt die Sequenz von Typ-Name-Paaren und ruft DO_GENERATE_ONE_STRUCT_DEF mit diesen Werten auf. Schließlich erstellt DO_GENERATE_ONE_STRUCT_DEF den BOOST_FUSION_DEFINE_STRUCT Makroaufruf.

Ich denke, aber ich bin nicht sachkundig genug um sicher zu sein, dass es ein in BOOST_FUSION_ADAPT_STRUCT_NAMESPACE_DEFINITION_END Bug. Es verwendet BOOST_PP_REPEAT_1 direkt, wenn ich denke, es sollte einfach BOOST_PP_REPEAT verwenden. Ich habe undefiniert und redefiniert das Makro mit BOOST_PP_REPEAT und alles scheint zu funktionieren, aber Sie wahrscheinlich sollten nicht blind darauf vertrauen.

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. Vielen Dank für diese detaillierte und gründliche Lösung. Es funktioniert wirklich für mich. Auch wenn ich etwas Zeit brauche, um die Makros zu verdauen. Ich habe nur die einfachen verstanden. Ich muss zugeben, dass 'boost-preprocessor' nicht so entmutigend ist, wie ich zuerst dachte. Es gibt immer noch einen Nachteil, wenn eine 'XML_TAG' keine andere Eigenschaft außer' name' enthält. Happening für mich im Falle von 'XML_TAG (Container)'. Compiler sagt Fehler C2065: '" BOOST_PP_SEQ_ENUM_0 ": nicht-deklarierte Kennung. Scheint, ich muss das selbst ausarbeiten. – Aleph0

+0

Ich habe keine Zeit, die Antwort im Moment zu ändern, aber ich denke, dass [dies] (http://melpon.org/wandbox/permlink/jJDFimOiXLIj2aE8) ([Visual C++ auf Rextester] (http: // Rextester .com/KLC36940)) löst das Problem mit Strukturen, die neben 'name' keine anderen Eigenschaften haben. – llonesmiz