Any way to generate repetitive operator<< definitions?
I have a few dozen of these types of structs and I'm hoping there is a clever way of generating the operator<< methods using macros or template meta-programming. Please also notice that endianess is also consideration and makes it a bit more tricky... Looking for a solution that would be at least as performant as the one outlined.
#define SEP '|'
struct MyStruct {
char c;
char s[10];
uint32_t i;
unsigned short us;
friend ostream& operator<<(ostream& os, HeartbeatMessage& r) {
return os \
<< "c=" << c << 开发者_如何转开发SEP
<< "s=" << s << SEP
<< "i=" << bswap_32(i) << SEP
<< "us=" << bswap_16(us) << SEP
}
As noted in the comments, this would be nice with reflection. Since C++ doesn't have built-in reflection, the best thing you can do is use some library or home brew code to simulate reflection and implement your printing in a generic way.
I suggest you use Boost.Fusion, specifically BOOST_FUSION_ADAPT_STRUCT
[Link] to make your type workable as a fusion sequence and then use Fusion's for_each to define the printing.
The increased work required for basically repeating the struct's definition for reflection-emulation will quickly pay off if you have several methods operating on the members. However, if all you really have is that single operator<<
, it's probably not worth the effort (or you decide, at least you can define all your printing&formatting in a central place).
once I had similar problem (a lot of different network packets for custom protocol), used Boost Preprocessor for this, result looked like this:
//////////////////////////////////////////////////////////////////////////
DEF_PACKET_STRUCT(name, members)
Example:
DEF_PACKET_STRUCT(
Test_Struct,
((float) (f) (0.4f))
((std::string) (str))
);
defines Test_Struct with 2 members:
float f;
std::string str;
generates def ctor:
Test_Struct(): f(0.4f) {}
generates serialization and operator<<(std::ostream&...)
//////////////////////////////////////////////////////////////////////////
DEF_DERIVED_PACKET_STRUCT(name, bases, members)
the same as above + derivation from given bases
Example:
DEF_DERIVED_PACKET_STRUCT(Test_Struct,
(Base1)(Base2),
((std::string) (str_multi_derived) ("multi_derived"))
)
Note that even if it should be derived from single base, it should be specified in (), e.g. (Base1)
you endianess feature can be implemented the same way as member default value in this example
Presumably member names and types are different in your structs, otherwise no problem.
And presumably you need your structs to be POD?
In that case, different POD structs, the main constraint is that templates can't handle ordinary C++ identifiers (which is what you necessarily have for the members in a POD struct). So you then need a macro to define the operator<<
; only macros can handle identifiers. And then the main problem is, how to pass a variable number of arguments to the macro?
Well, there are ways to pass variable number of arguments and iterate over them, using the Boost library's macro support, but that's complicated.
You will have to provide the data member names anyway, and so it will be not be more clean than the code that you already have.
Keeping that in mind, and refraining from the temptation to use Boosted variadic macros, it can look like this (I wouldn't use it, but if you like it you may consider also defining a macro for the function head declaration):
#include <iostream>
#include <string>
#define bswap_32( x ) x
#define bswap_16( x ) x
typedef unsigned uint32_t;
char const sep = '|';
template< class Type >
inline void write( Type const& v, std::ostream& stream )
{
stream << v;
}
template<>
inline void write( uint32_t const& v, std::ostream& stream )
{
stream << bswap_32( v );
}
template<>
inline void write( unsigned short const& v, std::ostream& stream )
{
stream << bswap_16( v );
}
template< class Type >
inline void write( char const legend[], Type const& v, std::ostream& stream )
{
stream << legend; write( v, stream ); stream << '|';
}
#define IMPLEMENT_OUTPUT_1( \
name1 \
) \
write( #name1 "=", r.name1, os );
#define IMPLEMENT_OUTPUT_2( \
name1, name2 \
) \
IMPLEMENT_OUTPUT_1( name1 ) \
write( #name2 "=", r.name2, os );
#define IMPLEMENT_OUTPUT_3( \
name1, name2, name3 \
) \
IMPLEMENT_OUTPUT_2( name1, name2 ) \
write( #name3 "=", r.name3, os );
#define IMPLEMENT_OUTPUT_4( \
name1, name2, name3, name4 \
) \
IMPLEMENT_OUTPUT_3( name1, name2, name3 ) \
write( #name4 "=", r.name4, os );
struct MyStruct
{
char c;
char s[10];
uint32_t i;
unsigned short us;
// friend std::ostream& operator<<( std::ostream& os, MyStruct const& r )
// {
// return os
// << "c=" << r.c << sep
// << "s=" << r.s << sep
// << "i=" << bswap_32( r.i ) << sep
// << "us=" << bswap_16( r.us ) << sep;
// }
friend std::ostream& operator<<( std::ostream& os, MyStruct const& r )
{
IMPLEMENT_OUTPUT_4( c, s, i, us )
return os;
}
};
int main()
{
using namespace std;
MyStruct const o = { 'A', "Bbbbbbb", 3, 4 };
cout << o << endl;
}
Again, I would not use a scheme like this. But, except for Boost'ing, it's the closest you get for different POD structs. So I might be worth having seen it.
Cheers & sorry that this probably not helps,
There is an alternative, but it gets ugly.
One solution is to create a generic base class, such as Object
(in other languages).
The next step is to create a containers of pointers to Object
.
And finally at some point write a method that applies operator<<
to each object in the container (via the pointers).
Otherwise, I go with something like this:
struct Annotation_Interface
{
virtual std::string annotate(const std::string& indentation = "") = 0;
};
class MyStruct : Annotation_Interface
{
char c;
char s[10];
uint32_t i;
unsigned short us;
public:
std::string annotate(const std::string& indentation)
{
std::ostringstream output;
output << "c=" << c << SEP
<< "s=" << s << SEP
<< "i=" << bswap_32(i) << SEP
<< "us=" << bswap_16(us) << SEP;
return output.str();
}
};
精彩评论