Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A stupid C++ trick for defining structs with metadata (serializers, printers, etc) without redundant declarations of members
#include <iostream>
#include <vector>
#include <string>
using std::string;
/*
These macros define a struct with an implicitly defined Show function that visits each member.
The trick is that a typedef is split between each STRUCT_MEMBER instance and the preceding macro,
which allows it to define an empty struct type that identifies the following struct member, while also
knowing the struct type that the preceding macro is using to identify the current member.
It defines a _ShowMembersFollowing function that is overloaded for the empty struct type identifying
the current member to show the current member, then call _ShowMembersFollow with the struct type
identifying the following member.
*/
#define BEGIN_STRUCT(StructName) \
class StructName \
{ \
public: \
friend string Show(const StructName& Struct,const string& LineSeparator = "\n") \
{ \
/* Generate the struct: prefix and call the overloaded _ShowMembersFollowing with the empty struct identifying the first member. */ \
return "struct:" + _ShowMembersFollowing(_FirstMemberId(),LineSeparator + "\t",Struct); \
} \
private: \
typedef StructName _ThisStruct; \
/* Define an empty struct that will identify the first member. The ID is passed to the following macro by a partial typedef declaration. */ \
struct _FirstMemberId {}; \
typedef _FirstMemberId
#define STRUCT_MEMBER(MemberType, MemberName) \
/* Complete the typedef provided by the previous macro with an alias for this member's ID. */ \
_MemberId_##MemberName; \
public: \
MemberType MemberName; \
private: \
/* Define an empty struct that will identify the next member. */ \
struct _NextMemberId_##MemberName {}; \
/* Overload the _ShowMembersFollowing function for this member's ID. */ \
static string _ShowMembersFollowing(_MemberId_##MemberName, const string& LineSeparator, const _ThisStruct& Struct) \
{ \
/* Show this member and recursively call _ShowMembersFollowing for the struct identifying the following member. */ \
return LineSeparator + #MemberName + " = " \
+ ::Show(Struct.MemberName,LineSeparator) \
+ _ShowMembersFollowing(_NextMemberId_##MemberName(), LineSeparator, Struct); \
} \
/* Begin a typedef for the next member's identifying struct; use the _NextMemberId_x struct defined in this macro, */ \
/* but leave it incomplete so the next macro can provide the name it will use to reference the ID. */ \
typedef _NextMemberId_##MemberName
#define END_STRUCT(StructName) \
/* Complete the typedef provided by the previous macro with an alias for this member's ID. */ \
_LastMemberId; \
static string _ShowMembersFollowing(_LastMemberId, const string&, const _ThisStruct&) { return ""; } \
};
// Shows an integer.
string Show(int Int,const string& LineSeparator = "\n") { return std::to_string(Int); }
// Shows a vector.
template<typename ElementType> string Show(const std::vector<ElementType>& Vector,const string& LineSeparator = "\n")
{
string Result;
Result += "array:";
for(auto ElementIt = Vector.begin();ElementIt < Vector.end();++ElementIt)
{
Result += LineSeparator + "\t";
Result += Show(*ElementIt,LineSeparator + "\t");
}
return Result;
}
BEGIN_STRUCT(A)
STRUCT_MEMBER(int,intMember1)
STRUCT_MEMBER(int,intMember2)
END_STRUCT(A)
BEGIN_STRUCT(B)
STRUCT_MEMBER(std::vector<A>,arrayMember)
END_STRUCT(B)
void main()
{
A a1 = {1,2};
A a2 = {4,3};
B b;
b.arrayMember.push_back(a1);
b.arrayMember.push_back(a2);
std::cout << Show(b);
/*
struct:
arrayMember = array:
struct:
intMember1 = 1
intMember2 = 2
struct:
intMember1 = 4
intMember2 = 3
*/
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment