Undesired template specification
Updated 10 January 2010: The download was not working, this is now fixed
Yesterday I decided to improve my math library and to make it more flexible. One of the things that I wanted to do was convert it to a template, so that I could easily make vectors of different types. However when it was finished and I started testing it I came across a lot of issues which my old math library didn't have. The reason that my old library didn't have them was because all of the structures used floats and when used with any other variable type would be automatically be casted, promoted or demoted to match the type float. However templates are more strict on that area since templates need to specialize. I know this is confusing so let me explain by example.
I will show three examples of the above problem and I will explain how to solve any of them. I hope that throughout the progress you will also understand why the problems happen.
But first let me define the class that I use. This piece of code won't change until I cover the solution of the third problem.
The Vector2 class:
[cc_cpp]
template
{
public:
T X, Y;
tVector2(T x, T y) : X(x), Y(y) {}
// Assigrnent operator
tVector2
};
// Multiplication operator
template
[/cc_cpp]
FIRST PROBLEM: ONLY A SINGLE TEMPLATE ARGUMENT
The first problem shows itself rather fast.
[cc_cpp]
int testMain1() // Think of this as a normal main
{
tVector2
Vec2 = Vec2 * 2.0f; // Does compile
Vec2 = Vec2 * 2.0; // Does not compile
return 0;
}
[/cc_cpp]
Visual Studio comes with two errors:
[ccWN_text]error C2782: 'tVector2
error C2676: binary '*' : 'tVector2
If we think about the reason for this problem is rather easy. If we would think as the compiler we would have the following two forms of multiplication:
[cc_cpp]
tVector2
tVector2
[/cc_cpp]
The first multiplication in the example does compile as it makes use of the first form, the second multiplication in the example however raises two errors. As you can see neither form can be applied on our multiplication, which has the form [cci_cpp]tVector2
The second error comes forth because the compiler still tries to solve. However it cannot convert [cci_cpp]tVector2
[cc_cpp]
void TestFunction(float v);
void TestFunction(double v);
int main()
{
TestFunction(2.0f); // Use first form
TestFunction(2.0 ); // Use second form
return 0;
}
[/cc_cpp]
FIRST SOLUTION: USE MULTIPLE TEMPLATE ARGUMENTS
The title of the first solution might feel contradicting as [cci_cpp]tVector2
[cc_cpp]
template
tVector2
return tVector2
}[/cc_cpp]
The biggest change is that because of using two template arguments, which are used for the first and second argument, is that compiler no longer have to match the types. So the compiler can generate the following:
[cc_cpp]
tVector2
tVector2
tVector2
[/cc_cpp]
And the everything works. You can even remove the old multiplication method if you want.
SECOND PROBLEM: MULTIPLYING DIFFERENT TYPES
This problem is exactly the same as the first one, but I cover it none the less as it took me a few seconds more to realize this than I wanted.
[cc_cpp]
int testMain2() // Think of it as a seperate program
{
// Second problem: Multiplying different types
tVector2
tVector2
Vec2d = Vec2d * Vec2; // tVector2
return 0;
}[/cc_cpp]
When we compile this visual studio comes two times with two different errors (total is four):
[ccWN_text]error C2784: 'tVector2
error C2677: binary '*' : no global operator found which takes type 'tVector2
The errors all take place in the solution of the previous problem as it tries to solve that function in to the following form:
[cc_cpp]tVector2
return tVector2
}[/cc_cpp]
I have added on purpose also the body of the form because that is what you should concentrate on. As you can see it tries to multiply [cci_cpp]v.X[/cci_cpp] with [cci_cpp]r[/cci_cpp]. While [cci_cpp]v.X[/cci_cpp]is a float, which is correct. [cci_cpp]r[/cci_cpp] is of the type [cci_cpp]tVector2
SECOND SOLUTION: USE MULTIPLE TEMPLATE ARGUMENTS WITH TYPES
Similar to the first problem we add the following function:
[cc_cpp]template
tVector2
return tVector2
}[/cc_cpp]
There is however one down side to this solution and that is that type returned is the same as the first template argument, even if the second template argument would be bigger. For example the first template argument is a float and the second is of the type double. In that case we would lose precision.
THIRD PROBLEM: CONVERTING FROM ONE TYPE TO ANOTHER TYPE
The test case is similar to that of the second problem, but with a little change.
[cc_cpp]int testMain3() // Think of it as a seperate program
{
// Third problem: Storing different types
tVector2
tVector2
Vec2d = Vec2 * Vec2d; // tVector2
return 0;
}[/cc_cpp]
As you can see the result of the multiplication is now [cci_cpp]tVector2
However unlike the solutions of the first two problems, this operator needs to be a member function. So we should store a tVector of a unknown type in a tVector of a specialized type (which is actually also unknown, but the compiler specializes this so it is known to him, but not to us).
THIRD SOLUTION: TEMPLATE FUNCTION INSIDE A TEMPLATE CLASS
To be honest, I didn't think this was at all possible but since I had solved the previous two problems I was intended to find a solution for this one as well. In retrospect the solution was just as simple as the previous one, but then again everything seems easy in retrospect.
The new class looks as followed:
[cc_cpp]template
{
public:
T X, Y;
tVector2(T x, T y) : X(x), Y(y) {}
// Assigment operator (didn't solve the third problem)
tVector2
// Assigment operator which allows other types (solved third problem)
template
X = v.X, Y=v.Y; return *this; }
};[/cc_cpp]
As you can see a new function has been added, which is a template function. Quite a simple and elegant solution once you accept the fact that you are allowed to create template functions inside a class as well.
FINAL WORDS
Although the title of the article is "Undesired template specification" it was only how I felt when I encountered all these problems. Afterwards I think it was not undesired at all, because this is how the language should work. If it wouldn't work like this we would barely have control what some of the functions would and then it would really become undesired.
I hope that the all of the above was easy enough to follow (assuming you are familiar with templates) and helped you understand in what went wrong and how to solve it.
As usual I have added the full source code at the bottom of the article. All the functions are in it, however in a production environment I would recommend you remove the old versions of some of the functions since the new functions also cover the functionality of the old ones.
Source code: templatetest_0.zip (2 kb)