【问题标题】:Generate boilerplate code by transforming arguments to string literals通过将参数转换为字符串文字来生成样板代码
【发布时间】:2016-07-29 06:38:00
【问题描述】:

在我的一个项目中,我试图实现一种更通用的方法来编写我们内部的简化 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 将成为这个好的解决方案的一部分。

但是,我不知道如何实现预期的结果。没用过这个库。幸运的是,我们的编译器支持可变参数模板参数。

有谁知道如何达到预期的效果?

【问题讨论】:

    标签: c++ xml template-meta-programming boost-fusion boost-preprocessor


    【解决方案1】:

    如果您对使用 Boost.Preprocessor 库感兴趣,您需要熟悉两种基本的“数据类型”:sequencetuple。您可以在the documentation 的参考部分找到该库使用的完整宏列表。我将在下面解释我使用的那些。

    接口中有两个宏:XML_TAGDEFINE_XML_TAGS
    XML_TAG 非常简单,它只是将其参数放在两组括号内。这导致无论您使用多少 XML_TAGs 都将转换为元素为元组的序列 (struct_name,sequence_of_type_and_name_pairs)
    DEFINE_XML_TAGS 是完成所有工作的宏。它使用三个辅助宏 GENERATE_STRUCT_DEFSGENERATE_VARIANT_OF_TYPESGENERATE_XMLTAGS

    GENERATE_VARIANT_OF_TYPES
    调用 ENUMERATE_TYPES(TAG_SEQ) 以获得逗号分隔的类型列表。 现在TAG_SEQ((Person,(int,age)))((Company,(int,noEmployees)(std::string,location))),我们想要Person,CompanyBOOST_PP_ENUM(SEQ) 接受一个序列并返回以逗号分隔的元素。所以我们需要BOOST_PP_ENUM((Person)(Company))BOOST_PP_SEQ_FOR_EACH(MACRO,DATA,SEQ) 使用 SEQ 中的每个元素以及您传递的任何数据调用 MACRO。所以BOOST_PP_SEQ_FOR_EACH(GET_TYPE_SEQUENCE,_,TAG_SEQ)(Person,(int,age))(Company,(int,noEmployees)(sd:string,location)) 调用GET_TYPE_SEQUENCEGET_TYPE_SEQUENCE 然后简单地获取每个元组的第一个元素,并使用 BOOST_PP_TUPLE_ELEM 将其放入一组括号中。

    GENERATE_XML_TAGS
    它调用GENERATE_PAIRS,后者又使用GENERATE_ONE_PAIR 调用SEQ_FOR_EACH。 如上一节所述,GENERATE_ONE_PAIR 获取每个元组(struct_name、sequence_of_type_name_pairs)。它获取名称并在其后添加一对括号,然后使用 sequence_of_type_name_pairs 调用GENERATE_VECTOR_OF_MEMBER_NAMESGENERATE_VECTOR_OF_MEMBER_NAMES 首先添加强制的“名称”成员,然后使用 BOOST_PP_ENUM 执行与上面解释的宏非常相似的操作,不同之处在于它需要做一些小技巧,因为当前的元组序列没有两组括号(这在第三种方法中解释为here)。 GENERATE_MEMBER_NAME_SEQUENCE 然后简单地获取成员的名称,将其转换为字符串,然后在其周围放置一组括号。

    GENERATE_STRUCT_DEFS
    BOOST_PP_REPEAT(N,MACRO,DATA) 调用 MACRO N 次,传递 DATA 和当前的重复索引。 GENERATE_ONE_STRUCT_DEF 获取序列的第 index 个元素,然后首先获取结构的名称,最后获取类型名称对的序列,并使用这些值调用 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)
    

    ma​​in.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;
    }
    

    【讨论】:

    • 哇。非常感谢这个详细而彻底的解决方案。它真的对我有用。即使我需要一些时间来消化宏。我还只是理解简单的那些。我不得不承认boost-preprocessor 并不像我最初想的那样令人生畏。如果XML_TAG 不包含除name 之外的其他属性,则仍然存在缺点。在XML_TAG(Container) 的情况下发生在我身上。编译器显示错误 C2065:"BOOST_PP_SEQ_ENUM_0": non-declared identifier.。看来我必须自己解决这个问题。
    • 我目前没有时间修改答案,但我认为this (Visual c++ on rextester) 解决了除了name 之外没有其他属性的结构的问题。
    猜你喜欢
    • 2021-06-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-02
    • 2015-09-06
    • 2018-11-13
    • 2013-07-27
    相关资源
    最近更新 更多