The problem was that, whilst I was indeed explicitly instantiating the template correctly, I was not exporting the symbol for each instantiation.
The solution was to do the following:
namespace DDGL
{
class DLL_EXPORTED XMLData
{
...
// const char* is used to avoid problems with trying to pass an
// std::string over DLL boundaries
template <typename Type> Type getAttribute (const char* attribute) const;
...
};
DLL_TEMPLATE_CLASS_MEMBER float XMLData::getAttribute(const char* attribute) const;
}
Where DLL_TEMPLATE_CLASS_MEMBER
was defined as:
#ifdef DDGL_DLL_BUILDING
#define DLL_TEMPLATE_CLASS_MEMBER template DLL_EXPORTED
#else
#define DLL_TEMPLATE_CLASS_MEMBER extern template DLL_EXPORTED
#endif
Doing this correctly exported the symbols for the explicit template instantiation and allowed me to use them outside the DLL.