5

I'm getting consistent behavior from both gcc 4.8.3 and clang 3.2, but do not understand why it is happening. Despite the fact that I have an explicit instantiation for a class template, the code is not being generated and I get an undefined symbol when I am using a fully specialized instance of the template.

I have a simple class template definition in a file 'temp.hpp'

#pragma once

template <typename T1>
class C 
{
public:
  C (T1 c) : d_c(c) {};
  ~C () = default;

  void print ();
private:
  T1 d_c;
};

Note that the method 'print()' is declared, but not defined here. I want the definition in the .cpp file and it will be specialized for different types.

So in the temp.cpp file I have the default definition of the print() method

#include "temp.hpp"
#include <iostream>

template<typename T1>
void
C<T1>::print ()
{
  std::cout << "Printing: " << d_c << std::endl;
}

followed by a specialization of the class for the type 'float':

template <>
class C <float>
{
public:
  C (float f) : d_f(f) {};
  ~C () = default;

  void print ()
  {
    std::cout << "float: " << d_f << std::endl;
  }

private:
  float d_f;
};

and since the definitions are in the .cpp file I must explicitly instantiate all the specializations that I will be using. So I have:

template class C<int>;
template class C<float>;

The driver for my test looks like this in test.cpp:

#include "temp.hpp"

int main()
{
  int i = 1;
  C<int> c_int(i);

  float f = 1.2;
  C<float> c_float(f);

  c_int.print();
  c_float.print();
}

Upon compiling and linking this I get error:

test.cpp: undefined reference to `C<float>::print()'

The object code for the C< int > is properly generated. I can see it using nm:

nm -C temp.o
...
0000000000000000 W C<int>::print()
0000000000000000 W C<int>::C(int)
0000000000000000 W C<int>::C(int)
...

As I mentioned earlier, this is consistent with gcc and clang so I'm assuming there is some language rule I don't understand here.

Note that if I add a usage of the print() method in file temp.cpp, then the code is generated, but that is silly and in my real code would be impossible. For this simple test case it would look like:

void foo () 
{
  C<float> s(1.3);
  s.print();
}

In the real code which motivated this little test my template has 3 template arguments which combine to expand into about 30 permutations of the code. There are one or two of those for which I need a specialization which does something different, but the other 28 I can leave alone.

Any pointers on where I've gone wrong or a language reference for why the explicit instantiation of should not generate code are greatly appreciated. I've spent 1/2 a day reading all the other stackoverflow posts on explicit instantiation and believe I am using it correctly.

Barry
  • 286,269
  • 29
  • 621
  • 977
Jay
  • 75
  • 5
  • So you have the specialization for `C` in `temp.cpp`, but you want to use that specialization in `test.cpp`? That's a problem. The specialized type `C` is a different and completely unrelated type from what you would get if you substituted `float` in for `T1` in the original template. In `test.cpp`, it's looking for `C::print()` where `T1 == float`, and that doesn't exist in your program. If you move the specialization into the header `temp.hpp`, then it should work; the specialization needs to be visible to the compiler when it's processing `test.cpp`. – Jason R Jul 31 '15 at 16:51
  • Thanks for the explanation Jason, In the real code the amount of code in specialization is sufficiently complex that it would draw in a bunch of other header files and I would end up in header Hades for compilation purposes. I have found an alternate solution where I can keep the expansions which generate illegal code further up the expansion hierarchy. – Jay Jul 31 '15 at 17:32

1 Answers1

6

From [temp.expl.spec]:

If a template, a member template or a member of a class template is explicitly specialized then that specialization shall be declared before the first use of that specialization that would cause an implicit instantiation to take place, in every translation unit in which such a use occurs; no diagnostic is required. If the program does not provide a definition for an explicit specialization and either the specialization is used in a way that would cause an implicit instantiation to take place or the member is a virtual member function, the program is ill-formed, no diagnostic required.

We're explicitly specializing C in temp.cpp, but in test.cpp, it is not declared before it is used. Thus, your code is ill-formed, no diagnostic required. You'll have to simply move the declaration of C<float> into temp.hpp

Always be careful with explicit specializations. The standard takes them very seriously:

The placement of explicit specialization declarations for function templates, class templates, [...], can affect whether a program is well-formed according to the relative positioning of the explicit specialization declarations and their points of instantiation in the translation unit as specified above and below. When writing a specialization, be careful about its location; or to make it compile will be such a trial as to kindle its self-immolation.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • 2
    You didn't quote the following paragraph. I'm disappointed :) – T.C. Jul 31 '15 at 17:42
  • Thanks for the responses, I would hate for my program to self-immolate, my boss would get pretty mad ;) I'm guessing that my follow-up question is succeeded because of a partial specialization instead of an explicit specialization? This subtly has escaped me before. – Jay Jul 31 '15 at 18:21
  • The haiku is much more apparent if you add linebreaks in the right places (although bold was a good first step). – Ben Voigt Jul 31 '15 at 18:22
  • @BenVoigt s/haiku/limerick. – Barry Jul 31 '15 at 19:40
  • @Jay I rolled back your followup - one question per question please. You can always ask a new question and link this one. – Barry Jul 31 '15 at 19:41
  • @Barry: ahh, true that – Ben Voigt Jul 31 '15 at 19:42