通过将参数转换为字符串文字来生成样板代码
Generate boilerplate code by transforming arguments to string literals
在我的一个项目中,我试图实现一种更通用的方法来编写我们内部简化的 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
将是这个好的解决方案的一部分。
但是,我不知道如何实现预期的结果。从来没有使用过这个库。幸运的是,我们的编译器支持可变模板参数。
有谁知道如何达到预期的效果?
如果您有兴趣使用 Boost.Preprocessor 库,您需要熟悉两个基础 "data types":sequence and tuple. You can find the whole list of macros that the library uses in the reference section of the documentation。我将在下面解释我使用的那些。
界面中有两个宏: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)
采用一个序列,returns 其元素以逗号分隔。所以我们需要 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_SEQUENCE
。
GET_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_NAMES
。 GENERATE_VECTOR_OF_MEMBER_NAMES
首先添加必需的 "name" 成员,然后用 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
宏调用。
I think, but I'm not knowledgeable enough to be sure, that there is a
bug in BOOST_FUSION_ADAPT_STRUCT_NAMESPACE_DEFINITION_END
. It uses
BOOST_PP_REPEAT_1
directly when I think it should just use
BOOST_PP_REPEAT
. I have undefined and redefined that macro using
BOOST_PP_REPEAT
and everything seems to work, but you probably
shouldn't trust it blindly.
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;
}
在我的一个项目中,我试图实现一种更通用的方法来编写我们内部简化的 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
将是这个好的解决方案的一部分。
但是,我不知道如何实现预期的结果。从来没有使用过这个库。幸运的是,我们的编译器支持可变模板参数。
有谁知道如何达到预期的效果?
如果您有兴趣使用 Boost.Preprocessor 库,您需要熟悉两个基础 "data types":sequence and tuple. You can find the whole list of macros that the library uses in the reference section of the documentation。我将在下面解释我使用的那些。
界面中有两个宏: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)
采用一个序列,returns 其元素以逗号分隔。所以我们需要 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_SEQUENCE
。
GET_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_NAMES
。 GENERATE_VECTOR_OF_MEMBER_NAMES
首先添加必需的 "name" 成员,然后用 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
宏调用。
I think, but I'm not knowledgeable enough to be sure, that there is a bug in
BOOST_FUSION_ADAPT_STRUCT_NAMESPACE_DEFINITION_END
. It usesBOOST_PP_REPEAT_1
directly when I think it should just useBOOST_PP_REPEAT
. I have undefined and redefined that macro usingBOOST_PP_REPEAT
and everything seems to work, but you probably shouldn't trust it blindly.
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;
}