Post date: Oct 27, 2008 5:16:17 PM
Templates are weird for a variety of reasons. I think the greatest among those reasons is that they fail the Compile Test. Which I'm defining as the (unreasonable) assumption that if it compiles it should work (or at least run). But that's not the case for templates because they're not actually being compiled until they've been instantiated, which is the big reason that that templated code compiles slower. So if you have templated code in a library that compiles just fine, you may find that is it horribly broken when you try to use it. Fortunately there are a couple of things you can do. One is to try compiling with gcc, which I've found catches a lot more potential template problems than Visual Studio. Another, better way is to use Explicit Template Instantiation, which has the additional benefit of speeding up compilation time. I'll describe how it works using my templated vec3 class as an example
template<class T>
class vec3
{
union
{
struct{ T x,y,z; };
struct{ T s,t,p; };
struct{ T r,g,b; };
T vec_array[3];
};
...
//Bunch O' Functions
...
}
And since I don't generally go writing vec3<float> or vec3<double> all the time I've typedef'd most of the common types.
typedef vec3<int> vec3i;
typedef vec3<float> vec3f;
typedef vec3<double> vec3d;
typedef vec3<char> vec3b;
typedef vec3<unsigned char> vec3ub;
Actually I've typedef'd a bunch of stuff I don't actually ever seem to use, but whatever. The point is that those typedefs represent a lot of uncompiled and untested code, and there won't be any problems until I actually try to use that code
vec3i happy_days(0, 1, 1);
happy_days.do_something(); //Function that was declared but I never got around to implementing.
This is just one example of how this can bite us on the ass. Fortunately, Explicit Template Instantiation allows us to specify specific types for which we want the template to be compiled for all functions, and best of all, its really easy, just stick a few lines of code in an implementation file (header files won't work, at least that's what I've heard), like so
#include "vec3.h"
template class vec3<int>;
template class vec3<float>;
template class vec3<double>;
So here I've specified that I want my vec3 class to be compiled with types int, float, and double. And it will immediately discover that do_something() was never implemented and throw an error. In this case I will also get errors like
Error: 'sqrt' : ambiguous call to overloaded function
This is in the function length()
template<class T>
inline const T vec3<T>::length() const
{
return(sqrt(x*x + y*y + z*z));
}
This is because the implementations of sqrt are sqrt(double _X), sqrt(float _X), and sqrt(long double _X), so the compiler doesn't really know what to do with the int. I could cast it to a float or a double, but I don't want to muck up the float or double implementations, I could do some template specialization stuff. But the truth is, I don't really care about it, the vec3 class was written primarily with floats and doubles in mind, so I just comment out the vec3<int> line.
#include "vec3.h"
//template class vec3<int>; //sqrt can't handle int "ambiguous call to overloaded function"
template class vec3<float>;
template class vec3<double>;
Now, I can still use vec3<int>, and I won't even have a problem with it unless I try to call length() with a vec3<int>, and like I said I don't really care about that type enough to warrant any further safeguards. And for the bonus, every function has already been compiled for the types/classes vec3<float> and vec3<double>, in the library, meaning they won't have to compile later when I use them in my applications. Anyway, I just figured this stuff out, and thought it was pretty cool/useful.