Specializing a template method of a template class in a header file

Posted by Pedro Melendez on July 9, 2015

Ok, the title is quite specific but I ran into this not long ago and Google wasn’t helpful so I’m throwing this in here in case somebody (a.k.a Me) needs to do or remember this in the future.

Let’s say we need to do this

// header.h

class Data
{
    public:
    template <typename T>
    T read() const
    {
        // Mockup to read data
        T result;
        return result;
    }

    std::string read_string() const
    {
        return "";
    }
};

template <typename T>
class DataReader
{
    const T& my_resource;
    public:
    DataReader(const T& resource_value): my_resource(resource_value) {}

    template <typename ReadType>
    ReadType read()
    {
        ReadType foo = my_resource.template read<ReadType>();

        //Do the reading here...
        return foo;
    }
};

//main.cpp
#include <iostream>
#include <header.h>

int main()
{
    Data d;

    DataReader<Data> reader(d);
    auto myval = reader.read<int>();
} 

So far so good, we need to make a reader and we need to be able to instantiate it using a data holder and we want to use static polymorphism.

But… what if we need to handle special cases? The code above might work well for primitive type such as int, doubles, float, etc. But we might need to handle std::string differently. The problem arises when we try to use an specialization, let’s say something like this:

template <>
std::string read()
{
    return "N/A yet";
}

Clang would tell us something like this:

main.cpp:37:17: error: cannot specialize a function 'read' within class scope
    std::string read()
    
main.cpp:49:33: error: expected '(' for function-style cast or type construction
    auto myval = reader.read<int>();
                             ~~~^
main.cpp:49:35: error: expected expression
    auto myval = reader.read<int>();

Right… We need to specialize this outside the scope of the class. Also notice that we have a template method inside a template class. Usually we could just do this:

template<> std::string DataReader::read()
{
    return "NA";
}

But, Clang shouts this:

main.cpp:46:24: error: 'DataReader' is not a class, namespace, or scoped enumeration
template<> std::string DataReader::read<std::string>()

Of course, DataReader is not class but a template class so we need to add two specializations, one for the class and one for the method. So finally, this is what we need to write:

template<> template<> std::string DataReader<Data>::read<std::string>()
{
    return my_resource.read_string();
}

After writting this it seems so obvious that I don’t know how I didn’t realize this sooner (I should stop coding at night). Anyway, I just finished writting this so it would not hurt anyone leaving this post here. Would it?


PS: This was tested in both gcc and clang/llvm. I just refer to clang here because I like its error messages better.