I have some classes which can be checked. The code which implements this declares a function template in a header file and specializes it in different source files:
// check.h
template <class T>
bool check(const T& object);
// class1.h
struct Class1 {int mass;};
// check_class1.cpp
#include "class1.h"
#include "check.h"
template <>
bool check(const Class1& object) {return object.mass < 100;}
// class2.h
struct Class2 {int price;};
// check_class2.cpp
#include "class2.h"
#include "check.h"
template <>
bool check(const Class2& object) {return object.price < 1000;}
// class3.h
struct Class3 {int x;};
... // 10 more classes which I can check
This code is used like this:
#include "class1.h"
#include "class2.h"
#include "class3.h"
#include "check.h"
int main()
{
Class1 object1{50};
Class2 object2{500};
Class3 object3{8};
check(object1); // OK
check(object2); // OK
check(object3); // a link error appears here
}
This works pretty well. When I add another class Class3
which I can check, I don't need to touch the header file, because it defines a very wide interface. If I forgot to implement the check
function for Class3
, the linker will remind me with an error message.
My question is: is this behavior guaranteed, or does my code work by luck? I am using Visual Studio.
If I want to specialize my function template, shouldn't I declare all my specializations in the header file?
I'd add those declarations to be on the safe side (well, assuming I don't overload instead for whatever reason). I don't think the law is too clear on that. For one, we have
[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. An
implicit instantiation is never generated for an explicit
specialization that is declared but not defined.
Which, if I read correctly, means that if an explicit specialization is added to main.cpp
, then it must appear before main
. Because that is where an implicit instantiation may occur. The paragraph doesn't make your code flat out ill-formed NDR, because the usage and the explicit specialization appear in different TU. But it does raise concerns.
On the other hand, there is this paragraph:
[temp]
7 A function template, member function of a class template,
variable template, or static data member of a class template shall be
defined in every translation unit in which it is implicitly
instantiated unless the corresponding specialization is explicitly
instantiated in some translation unit; no diagnostic is required.
This one allows us to explicitly instantiate in separate unseen TU's. But it doesn't provide an allowance for explicit specializations. Whether or not that's intentional or an omission I cannot say.
The reason it works is likely due to how the whole thing is implemented. When the function declaration is implicitly instantiated it produces a symbol that just so happens to match the one produced by the explicit specialization. Matching symbols means a happy linker, so everything builds and runs.
But from a language-lawyer perspective, I think we can call the behavior here undefined by omission. It's undefined simply because the standard doesn't address it. So going back to my opening statement, I'd add them to be on the safe side, because at least then the placement is addressed by the standard.
You have to declare each explicit specialization before their use. But you can do that in the headers declaring the types for which it is specialized.
// class2.h
struct Class2 {int price;};
template <class T>
bool check(const T& object);
template <>
bool check(const Class2& object)
(I still don't understand why using overloads is not an option).