Search code examples
c++pugixml

How to write a generic templated wrapper for type specific acess functions?


I'm trying to write a generic wrapper for the C++ pugi xml library that can save and store values to/from xml.

They have implemented their xml node attributes' (stored as strings) access functions into functions such as attribute.as_int(), attribute.as_bool() etc.

I want to achieve the same functionality seen in the nlohmann::json library where you can call .get<T>() on some json object and get some type.

The only way I can think of (which may not even work) is to use template specialization:

template <>
int foo<int>(xml_attribute param)
{
    return param.as_int();
}

template <>
bool foo<bool>(xml_attribute param)
{
    return param.as_bool();
}

And so forth.

Which seems to result in almost as much code as writing a non-generic wrapper...


Solution

  • We can use an extra parameter to create different overloads based on type, and then use overload resolution to find the right function. TypeTag is an empty class we can use to distinguish between different overloads:

    template<class T>
    struct TypeTag{ /* empty */ };
    

    Once we have that, we can write read_as functions based on the tag:

    int read_as(xml_attribute param, TypeTag<int>) {
        return param.as_int(); 
    }
    bool read_as(xml_attribute param, TypeTag<bool>) {
        return param.as_bool(); 
    }
    float read_as(xml_attribute param, TypeTag<float>) {
        return param.as_float(); 
    }
    double read_as(xml_attribute param, TypeTag<double>) {
        return param.as_double();
    }
    const pugi::char_t* read_as(xml_attribute param, TypeTag<const pugi::char_t*) {
        return param.as_string(); 
    }
    

    Making a generic template is now pretty simple:

    template<class T>
    T read_as(xml_attribute param) {
        return read_as(param, TypeTag<T>()); 
    }
    

    This solution works in C++11, and it will compile faster than a series of if constexpr checks because the compiler can look up the correct version through overload resolution, rather than having to do a series of checks.