想象一下,我们有一些带有数百种消息类型的协议,每种消息类型都要由C ++类建模。由于每个类都应该能够自动处理每个字段,因此一个自然的解决方案就是拥有一个包含所有必需类型的std::tuple
:
std::tuple<int, double, char> message;
print(message); // the usual variadic magic
这一切都很好。但是,现在我想给每个字段命名,我希望能够在引用代码中的字段时使用该名称,并获得它的文本表示。天真地,或者在C中,我可能写过:
struct Message
{
int header;
double temperature;
char flag;
};
这样我们就失去了元组的递归自动处理能力,但我们可以逐字命名每个字段。在C ++中,我们可以通过枚举来做到这两点:
struct Message
{
enum FieldID { header, temperature, flag };
static const char * FieldNames[] = { "header", "temperature", "flag" };
typedef std::tuple<int, double, char> tuple_type;
template <FieldID I>
typename std::tuple_element<I, tuple_type>::type & get()
{ return std::get<I>(data); }
template <FieldID I>
static const char * name() { return FieldNames[I]; }
tuple_type data;
};
现在我可以说,Message m; m.get<Message::header>() = 12;
等等,我可以对这些字段进行递归,并使每个字体打印出自己的值,并以自己的名字为前缀等。
现在的问题是:如何有效地编写此类代码而不重复?
理想情况下,我希望能够这样说:
START_MESSAGE(Message)
ADDFIELD(int, header)
ADDFIELD(double, temperature)
ADDFIELD(char, flag)
END_MESSAGE
有没有办法,结合预处理器,Boost和C ++ 11,在不需要外部生成工具的情况下实现这样的目标? (我认为Boost.Preprocessor称之为“水平”和“垂直”重复。我需要以某种方式“转置”字段数据。)这里的关键特征是我永远不必重复任何信息,并且修改或添加一个字段只需要一次更改。
答案 0 :(得分:3)
您可以使用boost的预处理器序列来完成此操作。
#define CREATE_MESSAGE(NAME, SEQ) ...
CREATE_MESSAGE(SomeMessage,
(int)(header)
(double)(temperature)
(char)(flag)
)
您需要迭代每对以生成定义。我没有任何示例代码方便,但如果有趣的话我可以安排一些。
有一次,我有一个类似这样的生成器,它也生成了字段的所有序列化。我觉得它有点太过分了。我觉得这些领域的具体定义和声明访问者更直接。如果其他人不得不在我之后维护代码,那就不那么神奇了。我显然不清楚你的情况,在实施之后我仍然有所保留。 :)
再看看C ++ 11的功能会很酷,虽然我没有机会。
更新
仍有一些问题需要解决,但这主要是有效的。
#include <boost/preprocessor.hpp>
#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/arithmetic/mod.hpp>
#include <boost/preprocessor/control/if.hpp>
#include <tuple>
#define PRIV_CR_FIELDS(r, data, i, elem) \
BOOST_PP_IF(BOOST_PP_MOD(i, 2),elem BOOST_PP_COMMA,BOOST_PP_EMPTY)()
#define PRIV_CR_STRINGS(r, data, i, elem) \
BOOST_PP_IF(BOOST_PP_MOD(i, 2),BOOST_PP_STRINGIZE(elem) BOOST_PP_COMMA,BOOST_P
#define PRIV_CR_TYPES(r, data, i, elem) \
BOOST_PP_IF(BOOST_PP_MOD(i, 2),BOOST_PP_EMPTY,elem BOOST_PP_COMMA)()
#define CREATE_MESSAGE(NAME, SEQ) \
struct NAME { \
enum FieldID { \
BOOST_PP_SEQ_FOR_EACH_I(PRIV_CR_FIELDS, _, SEQ) \
}; \
std::tuple< \
BOOST_PP_SEQ_FOR_EACH_I(PRIV_CR_TYPES, _, SEQ) \
> data;\
template <FieldID I> \
auto get() -> decltype(std::get<I>(data)) { \
return std::get<I>(data); \
} \
template <FieldID I> \
static const char * name() { \
static constexpr char *FieldNames[] = { \
BOOST_PP_SEQ_FOR_EACH_I(PRIV_CR_STRINGS, _, SEQ) \
}; \
return FieldNames[I]; \
} \
};
CREATE_MESSAGE(foo,
(int)(a)
(float)(b)
)
#undef CREATE_MESSAGE
int main(int argc, char ** argv) {
foo f;
f.get<foo::a>() = 12;
return 0;
}
get的decltype存在问题。我还没有真正用元组知道那里有什么期待。不过,我认为它与你如何生成类型或字段无关。
以下是预处理器使用-E生成的内容:
struct foo {
enum FieldID { a , b , };
std::tuple< int , float , > data;
template <FieldID I>
auto get() -> decltype(std::get<I>(data)) {
return std::get<I>(data);
}
template <FieldID I> static const char * name() {
static constexpr char *FieldNames[] = { "a" , "b" , };
return FieldNames[I];
}
};
答案 1 :(得分:1)
这不是一个答案,而只是另一个(可怕的)想法。我有一个我写过的inl
文件,有点类似。就在这里:http://ideone.com/6CvgR
基本概念是来电者这样做:
#define BITNAME color
#define BITTYPES SEPERATOR(Red) SEPERATOR(Green) SEPERATOR(Blue)
#define BITTYPE unsigned char
#include "BitField.inl"
并且inl
文件通过重新定义SEPERATOR
然后再次使用BITTYPES
来创建具有命名成员的自定义位域类型。然后可以轻松使用,包括ToString
函数。
colorBitfield Pixel;
Pixel.BitField = 0; // sets all values to zero;
Pixel.Green = 1; // activates green;
std::cout << "Pixel.Bitfield=" << (int)Pixel.BitField << std::endl; //this is machine dependant, probably 2 (010).
Pixel.BitField |= (colorBitfield::GreenFlag | colorBitfield::BlueFlag); // enables Green and Blue
std::cout << "BlueFlag=" << (Pixel.BitField & colorBitfield::BlueFlag) << std::endl; // 1, true.
std::cout << "sizeof(colorBitField)=" << sizeof(colorBitfield) << std::endl;
内联文件本身就是可怕的代码,但有些模糊的方法可能会简化调用者的使用。
如果我有空的时间,我会看看我是否可以根据你想要的东西制作一些东西。
答案 2 :(得分:1)
根据Tom Kerr的建议,我查找了Boost.Preprocessor序列。这就是我想出的:
#include <boost/preprocessor/seq.hpp>
#include <boost/preprocessor/comma_if.hpp>
#include <boost/preprocessor/arithmetic.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <tuple>
#define PROJECT1(a,b) a
#define PROJECT2(a,b) b
#define BOOST_TT_projectqu(r,data,t) BOOST_PP_COMMA_IF(BOOST_PP_SUB(r, 2)) BOOST_PP_STRINGIZE(PROJECT2 t)
#define BOOST_TT_project1(r,data,t) BOOST_PP_COMMA_IF(BOOST_PP_SUB(r, 2)) PROJECT1 t
#define BOOST_TT_project2(r,data,t) BOOST_PP_COMMA_IF(BOOST_PP_SUB(r, 2)) PROJECT2 t
template <typename T> struct Field { };
#define MESSAGE(classname, data) struct classname \
{ \
typedef std::tuple<BOOST_PP_SEQ_FOR_EACH(BOOST_TT_project1, ~, data)> tuple_type; \
\
static constexpr char const * FieldNames[BOOST_PP_SEQ_SIZE(data)] = { BOOST_PP_SEQ_FOR_EACH(BOOST_TT_projectqu, ~, data) }; \
\
enum FieldID { BOOST_PP_SEQ_FOR_EACH(BOOST_TT_project2, ~, data) }; \
\
template <FieldID I> using type = typename std::tuple_element<I, tuple_type>::type; \
\
template <FieldID I> typename std::tuple_element<I, tuple_type>::type & get() { return std::get<I>(dat); } \
template <FieldID I> typename std::tuple_element<I, tuple_type>::type const & get() const { return std::get<I>(dat); } \
\
private: \
tuple_type dat; \
};
MESSAGE(message, \
((int, header)) \
((double,temperature)) \
((char, flag)) \
)
用gcc -std=c++11 -E -P
编译整个事物(并重新格式化)给出:
template <typename T> struct Field { };
struct message {
typedef std::tuple< int , double , char > tuple_type;
static constexpr char const * FieldNames[3] = { "header" , "temperature" , "flag" };
enum FieldID { header , temperature , flag };
template <FieldID I> using type = typename std::tuple_element<I, tuple_type>::type;
template <FieldID I> typename std::tuple_element<I, tuple_type>::type & get() { return std::get<I>(dat); }
template <FieldID I> typename std::tuple_element<I, tuple_type>::type const & get() const { return std::get<I>(dat); }
private: tuple_type dat; };
答案 3 :(得分:0)
您可以执行类似于BOOST_SERIALIZATION_NVP
(来自Boost.Serialization库)的操作。宏创建了一个(短命的)包装器结构,它将其参数的名称和值绑定在一起。然后,这个名称 - 值对由库代码处理(名称实际上仅在XML序列化中很重要,否则将被丢弃)。
因此,您的代码可能如下所示:
int header = 42;
double temperature = 36.6;
char flag = '+';
print (Message () + MY_NVP (header) + MY_NVP (temperature) + MY_NVP (flag));