Skip to main content
GameDev.net gamedev.net
🔒 Locked

The fruits of laziness

Started by Trienco Jul 11, 2015 at 5:08 PM 4 replies 5.2k views
Original Post
Trienco
Trienco

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.

f@dzhttp://festini.device-zero.de
SmkViper
SmkViper
And to think - you're not even looking at all the extra magic boost does to support a wide range of compilers with varying levels of support for C++ features...
Trienco
Trienco

I'd be too worried about my sanity if I ever dived into it that far. Usually, when I need to save the preprocessor output just to figure out where the problem is, that's a good time to start looking for a way out of the rabbit hole.

f@dzhttp://festini.device-zero.de
Sik_the_hedgehog
Sik_the_hedgehog




I still can't tell if that represents reaching a new level of C++ or entering a new circle of hell.

Is there any difference between them?

Don't pay much attention to "the hedgehog" in my nick, it's just because "Sik" was already taken =/ By the way, Sik is pronounced like seek, not like sick.
Oberon_Command
Oberon_Command
It looks like you're trying to approximate Lisp with C++ macros.
Servant of the Lord
Servant of the Lord

I still can't tell if that represents reaching a new level of C++ or entering a new circle of hell.

Is there any difference between them?

According to Dante, there's only nine circles of Hell; ...but C++ recurses forever.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.