samedi 30 janvier 2016

Paradigm regarding template class specialization

I'm currently writing a template class for archiving (or serializing) and unarchiving data into/from a binary format. First off, I'm trying to close on what pattern I'll use. I am mostly inclined to using templates because unarchivers don't have an input type for method overloading. For instance, the following example is OK:

Archiver ar;
int i;

archive(ar, i);

But it's counterpart isn't:

Unarchiver unar;
int i;

i = unarchive(unar);

I would like to avoid using the function's name, such as unarchive_int because it would be troublesome when using templates. Say:

template <class T> class SomeClass
{
public:

   void doSomething()
   {
      // Somewhere
      T value = unarchive(unar);
   }
};

This would make things messy, and as such I rather really use templates for this, whereas the previous expression would be T value = unarchive<T>(ar);. It also seems silly (arguably) to write a global function if either the first or only parameter are always the archiver and unarchiver objects; a template class seems to be in order:

template <class T> class Archiver
{
public:

    void archive(T obj);
};

This works, but the archiving method always copies its input object. This is OK with POD data types, but not so much which classes. The solution seems obvious, and instead use a const reference as in void archive(const T & obj), but now it also seems silly to be passing integers, floats, and other PODs by reference. Although I would be happy with this solution, I tried to go a little further and have the object make the distinction instead. My first approach is std::enable_if, while assuming a copy by default (for all non-class members) and provide a class specialization where the archive method gets its input by reference instead. It doesn't work. Here's the code:

template <class T, class E = void>
class Archiver
{
public:

    // By default, obj is passed by copy
    void archive(T obj);
};

template <class T>
class Archiver<T, typename std::enable_if<std::is_class<T>::value && !std::is_pod<T>::value>::type>
{
public:

    // I would expect this to be used instead if is_class<T> && !is_pod<T>
    void archive(const T & obj);
};

The problem is that the second declaration is not visible at all to the compiler, and here's proof:

template <> void Archiver<std::uint8_t>::archive(uint8_t obj);
template <> void Archiver<std::string>::archive(const std::string & obj);

The former compiles fine, but the later gives:

out-of-line declaration of 'archive' does not match any declaration in 'Archiver<std::__1::basic_string<char>, void>'

On the other hand, if I get the std::string instead by copy if compiles just fine. I think I know why this happens, the compiler chooses the first template as it's generic enough for both declarations, but then how do I make it choose the more specialized version?

Aucun commentaire:

Enregistrer un commentaire