vendredi 31 juillet 2015

Is partial specialization in a cpp file not "well-formed"

This a follow-up to [question]: No generated code for explicitly specialized template even with explicit instantiation.

I am using partial specializations in a .cpp file to handle special cases while not forcing all the code into a header file. A simplified example of what I'm doing is given below. This works with both gcc and clang, but given the answer to the question above, I'm wondering if I'm just getting lucky that the linker is finding compatible symbols.

I'm starting with a class template defined in foo.hpp:

#pragma once

template <typename T1, typename T2>
class foo
{
public:
  foo (T1 t1, T2 t2) : d_t1(t1), d_t2(t2) {}
  ~foo () = default;

  void print ();
private:
  T1 d_t1;
  T2 d_t2;
};

Note that the print() method is declared, but not defined.

Now in the foo.cpp, I define the print() method.

#include <iostream>

template <typename T1, typename T2>
void
foo<T1,T2>::print()
{
    std::cout << "T1 is: ";
    d_t1.print();
    std::cout << std::endl;
    std::cout << "T2 is: ";
    d_t2.print();
    std::cout << std::endl << std::endl;
}

Note that this implementation assumes that both T1 and T2 will have a method called print(). In my real world code, the types which are used as arguments to this template usually conform to this interface, but sometimes they don't and I deal with that through partial specialization in the .cpp file. This template is not meant for really generic usage, it only needs to work with a small number of types which I know up-front.

So for instance, I might have 3 types such as

class HasPrint
{
public:
  HasPrint () = default;
  ~HasPrint () = default;

  void print ();

};

class AlsoHasPrint
{
public:
  AlsoHasPrint () = default;
  ~AlsoHasPrint () = default;

  void print ();

};

class NoPrint
{
public:
  NoPrint () = default;
  ~NoPrint () = default;

  void noPrint ();

};

Note that 'HasPrint' and 'AlsoHasPrint' classes have a print() method, while the 'NoPrint' class has a noPrint() method. In the bodies of these classes the print/noPrint methods simply print the name of the class.

To handle the case of someone using NoPrint as one of the template arguments, I define a partial specialization in the foo.cpp file:

template <typename T2>
class foo <NoPrint, T2>
{
public:
  foo (NoPrint n1, T2 t2) : d_n1(n1), d_t2(t2) {}
  ~foo () = default;

  void print ()
  {
    std::cout << "NoPrint is: ";
    d_n1.noPrint();
    std::cout << std::endl;
    std::cout << "T2 is: ";
    d_t2.print();
    std::cout << std::endl;
  }
private:
  NoPrint d_n1;
  T2 d_t2;
};

Then to get all the code I need generated for all the permutations that I use, I include (in the foo.cpp file) the following explicit instantiations of foo.

template class foo<HasPrint, HasPrint>;
template class foo<HasPrint, AlsoHasPrint>;
template class foo<AlsoHasPrint, AlsoHasPrint>;
template class foo<NoPrint, HasPrint>;

The result of the last explicit instantiation is that code is generated for an expansion of the template using the NoPrint object and calling the method noPrint().

In a separate test.cpp file, I have the driver for my test program.

#include "foo.hpp"
#include "hasprint.hpp"
#include "alsohasprint.hpp"
#include "noprint.hpp"

int
main (int argc, char** argv)
{
  HasPrint h1;
  HasPrint h2;
  AlsoHasPrint a1;
  NoPrint n1;
  foo<HasPrint, HasPrint> f1 (h1, h2);
  foo<HasPrint, AlsoHasPrint> f2 (h1, a1);
  foo<AlsoHasPrint, AlsoHasPrint> f3 (a1, a1);
  foo<NoPrint, HasPrint> f4 (n1, h1);


  f1.print();
  f2.print();
  f3.print();
  f4.print();
}

This works as expected on both gcc 4.8.3 and clang 3.2. The result is:

T1 is: HasPrint
T2 is: HasPrint

T1 is: HasPrint
T2 is: AlsoHasPrint

T1 is: AlsoHasPrint
T2 is: AlsoHasPrint

NoPrint is: NoPrint
T2 is: HasPrint

This keeps the header for foo.hpp nice and clean at the expense of needing to use explicit instantiation for each set of types for which I use the template, and a partial specialization if one of the argument types does not conform to the interface.

Given that I'm using the type Foo< NoPrint, HasPrint > in the test driver, without informing the compiler that a specialization exists, is this "well-formed" or am I just getting lucky?

Thanks

Aucun commentaire:

Enregistrer un commentaire