1

I am experimenting with specialized destructors. This code is perfectly valid and compiles just fine:

#include <iostream>

using namespace std;

template <typename T>
class Cat
{
public:
    ~Cat();
};

template <typename T>
Cat<T>::~Cat()
{
    std::cout << "T" << std::endl;
}

template <>
Cat<int>::~Cat()
{
    std::cout << "int" << std::endl;
}


int main()
{
    Cat<int> c1;
    Cat<float> c2;
    return 0;
}

However if I put the class and the destructors in a separate file "Cat.h" and do #include "Cat.h" in Main.cpp, I'm getting a linker error: LNK2005 "public: __thiscall Cat<int>::~Cat<int>(void)" (??1?$Cat@H@@QAE@XZ) already defined in Cat.obj. Why?

Valentin
  • 1,108
  • 8
  • 18
  • 3
    You can't specialize just one member function of a class template. You have to specialize the whole class. – navyblue Oct 17 '17 at 21:25
  • This is NOT a duplicate. I'm putting my code in a header, not in a cpp. – Valentin Oct 17 '17 at 21:25
  • @navyblue 1. Destructor is the only function I defined. Or do you mean I also have to specialize what's generated by default? 2. Why does it compile in the first case then? – Valentin Oct 17 '17 at 21:27
  • No `using namespace std;` please. There should be no difference between including a file and pasting its content by hand, especially not multiple definitions. Are you sure there isn't another .cpp file somewhere? – Quentin Oct 17 '17 at 21:28
  • I don't have chekced the standard yet, but marking the destructors "inline" will make it link in Visual Studio. Haven't tried GCC yet. – Jodocus Oct 17 '17 at 21:29
  • When `Cat` is instantiated, the generic form is used to define `Cat`. Since you already have that defined somewhere else, and they don't match, this is an ODR violation at best. Furthermore, you aren't allowed to try and be tricky and do things like declare but not define a class template function (then define it conditionally later), because all instantiations for a given T need to be of the same template. In short, like navyblue says you need to specialize the entire class (or refactor to some subset and specialize that). – GManNickG Oct 17 '17 at 21:29
  • In your "however" scenario, what exactly is in `cat.cpp` ? – M.M Oct 17 '17 at 21:38
  • @navyblue It seems to work—as expected—in gcc, clang, and MSVC without any warnings. I thought this wasn’t valid too, but since none of them catch it I’m not so sure; do you have a source for that? – Daniel H Oct 17 '17 at 21:38
  • @navyblue By "You can't", you mean "I didn't know you could" – M.M Oct 17 '17 at 21:39
  • @GManNickG My understanding is that it's fine so long as the specializations are all visible at the point of instantiation. It would be a problem if the full specialization were only in one translation unit, but the template was instantiated in both – M.M Oct 17 '17 at 21:49
  • @M.M: Hm, I no longer remember any of the references. :) I went through this same path some time ago when I wanted to write a generic PIMPL utility; I needed to specialize a function per-pimpl class (because each destructor is unique) and also needed it to be defined only after the pimpl'd class destructor was defined. It turns out the simplest thing was to use a macro to do this, but IIRC I found: you cannot hide the template definition and then specialize "the same" template under the hood, but you can hide the definition and then repeat the same definition multiple times elsewhere [1/2] – GManNickG Oct 17 '17 at 21:53
  • because that's just the same as an include at that point. Of course we all know it "works" to declare but not define a single template, then per-instantiation give it a unique, possibly-heterogeneous definition as needed, but it's technically UB. See my comments from past-me at https://github.com/NicholasGorski/Scratch/blob/master/pimpl_example/detail/pimpl.hpp#L23 [2/2] – GManNickG Oct 17 '17 at 21:55
  • 1
    @M.M My bad. It's ok to sepcialize just selected members of a class template. – navyblue Oct 17 '17 at 22:01

2 Answers2

3

It sounds like you have the following:

template <>
Cat<int>::~Cat()
{
    std::cout << "int" << std::endl;
}

in a header file that is included in two translation units. An explicit specialization of a function template is a non-inline function (C++14 [temp.expl.spec]/12); so this is an ODR violation.

It's the same error you'd get if you implemented a non-template member function (or a free function) in the same place: both translation units end up with a copy of the function, which is not allowed even if the two copies are identical.

To fix this, put the keyword inline before template<>.


There was some discussion in comments about the legality of providing an explicit specialization for a member function of a class template. I believe this is correct, with restrictions according to C++14 [temp.expl.spec]/6:

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.

Also, section 7:

The placement of explicit specialization declarations for function templates, class templates, variable templates, member functions of class templates, static data members of class templates, member classes of class templates, member enumerations of class templates, member class templates of class templates, member function templates of class templates, static data member templates of class templates, member functions of member templates of class templates, member functions of member templates of non-template classes, static data member templates of non-template classes, member function templates of member classes of class templates, etc., and the placement of partial specialization declarations of class templates, variable templates, member class templates of non-template classes, static data member templates of non-template classes, member class templates of class templates, etc., 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.

In your program it is in the perfect location: immediately after the class definition. The risky sitautions occur when the class might have been instantiated before a declaration at least of the specialization was encountered.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • You also need to declare the specialization right? Without that I think it's also technically UB. – GManNickG Oct 17 '17 at 22:29
  • @GManNickG i don't think so -- definition of function is also a declaration. The rule is that an explicit specialization must be declared before any instantiation of the thing it's specializing; but that declaration can also be a definition – M.M Oct 18 '17 at 01:25
1

Chances are your Cat.cpp and main.cpp translation units both include the same Cat.h header file. Put your entire template class in a header file, remove the Cat.cpp translation unit and compile without it.
Live example
More details in this SO post:
Why can templates only be implemented in the header file?.

Ron
  • 14,674
  • 4
  • 34
  • 47