Sometimes you just get very tired of writing endless tons of boiler plate code. Case in point, importing/exporting nested data hierarchies. Due to reasons, fields aren't explicitly looked for, since they might have been "inherited" through referenced sub-objects and will only be partially overwritten.
So after staring at endless screens full of if-else-if-else-if string comparisons for each element name, you develop a desire to automate this kind of mess.
Enter the scary depths of boost::fusion and the black magic that can invoked by macros and template meta-programming.
So, this now automagically "just works":
DEFINE_ENUM(MyEnum,
(ALPHA)
(BETA))
BOOST_FUSION_DEFINE_STRUCT((test), TestStructNested,
(float, myFloat)
(unsigned, noSign)
(MyEnum, greekLetter))
BOOST_FUSION_DEFINE_STRUCT((test), TestStruct,
(bool, yesNo)
(std::string, someString)
(test::TestStructNested, nested))
int main()
{
test::TestStruct myStruct;
pugi::xml_document doc;
doc.load_file("in.xml");
fillStructFromXml(doc.first_child(), myStruct);
pugi::xml_document newDoc;
addStructToXml(newDoc.append_child("root"), myStruct);
newDoc.save_file("out.xml");
return 0;
}
And yet, despite the luxury of being able to add new fields and structs and enums without writing a single line of code to actually read/write them, the real horror is what makes it work and the bad feeling about unleashing it unto mankind.
Having functions to convert enums from/to strings isn't too uncommon, so some people have already been unfortunate enough to see things like this:
#define ADAPT_ENUM_VALUE_TO_STRING(R, ENUM, ENUM_VALUE)\
case ENUM::ENUM_VALUE: return BOOST_PP_STRINGIZE(ENUM_VALUE); break;
#define ADAPT_ENUM_VALUE_FROM_STRING(R, ENUM, ENUM_VALUE)\
else if (!strcmp(str, BOOST_PP_STRINGIZE(ENUM_VALUE))) v = ENUM::ENUM_VALUE;
#define ADAPT_ENUM(ENUM, ENUM_VALUES)\
inline const char* toString(const ENUM& value) {\
switch (value) {\
BOOST_PP_SEQ_FOR_EACH(ADAPT_ENUM_VALUE_TO_STRING, ENUM, ENUM_VALUES)\
default: throw std::runtime_error(std::string("invalid value for " BOOST_PP_STRINGIZE(ENUM) ": ") + std::to_string((unsigned)value));\
}\
}\
inline void fromString(const char* str, ENUM& v) {\
if (!str) throw std::runtime_error("String can't be null");\
BOOST_PP_SEQ_FOR_EACH(ADAPT_ENUM_VALUE_FROM_STRING, ENUM, ENUM_VALUES)\
else throw std::runtime_error(std::string("invalid " BOOST_PP_STRINGIZE(ENUM) " value: ") + str);\
}
#define VALUE(R, _, ENUM_VALUE) ENUM_VALUE,
#define DEFINE_ENUM(ENUM, ENUM_VALUES)\
enum class ENUM {BOOST_PP_SEQ_FOR_EACH(VALUE, _, ENUM_VALUES)};\
ADAPT_ENUM(ENUM, ENUM_VALUES)
But the real "beauty" is the code that iterates over the adapted struct members until the member name matches the xml element name and recursively delves into nested structs...
template<class XmlNodeType, class MemberType>
typename std::enable_if<std::is_class<MemberType>::value, void>::type
inline fromElement(const XmlNodeType& node, MemberType& member)
{
for (const auto& child : getNodeChildren(node))
setMemberFromXml(child, member);
}
template<class XmlNodeType, class MemberType>
typename std::enable_if<std::is_enum<MemberType>::value, void>::type
inline fromElement(const XmlNodeType& node, MemberType& member) {
fromString(getNodeText(node), member);
}
template<class XmlNodeType, class ValueType>
typename std::enable_if<std::is_integral<ValueType>::value, void>::type
inline fromElement(const XmlNodeType& node, ValueType& value) {
value = static_cast<ValueType>(strtol(getNodeText(node), nullptr, 0));
}
template<class XmlNodeType>
inline void fromElement(const XmlNodeType& node, std::string& value) {
value = getNodeText(node);
}
//Bunch of additional overloads
template<class XmlNodeType>
struct SetMemberByName
{
SetMemberByName(const XmlNodeType& node) : xmlNode(node) {}
template<typename Iter, typename End>
void doIt(const Iter& it, const End& end, boost::mpl::false_)
{
const char* memberName = boost::fusion::extension::struct_member_name<Iter::seq_type, Iter::index::value>::call();
if (!strcmp(getNodeName(xmlNode), memberName))
{
fromElement(xmlNode, *it);
return;
}
doIt(boost::fusion::next(it), end,
boost::fusion::result_of::equal_to<typename boost::fusion::result_of::next<Iter>::type, End>());
}
template<typename Iter, typename End>
void doIt(const Iter&, const End&, boost::mpl::true_) {}
const XmlNodeType& xmlNode;
};
template<class XmlNodeType, class StructType>
void setMemberFromXml(const XmlNodeType& node, StructType& obj)
{
using namespace boost;
SetMemberByName<XmlNodeType>(node).doIt(
fusion::begin(obj),
fusion::end(obj),
fusion::result_of::equal_to<
typename fusion::result_of::begin<StructType>::type,
typename fusion::result_of::end<StructType>::type>());
}
template<class XmlNodeType, class StructType>
void fillStructFromXml(const XmlNodeType& root, StructType& obj)
{
fromElement(root, obj);
}
I still can't tell if that represents reaching a new level of C++ or entering a new circle of hell. The mere thought of what must be going on in those boost headers is pure nightmare fuel.