Limegarden.net Personal site of Wouter Lindenhof

10Jan/100

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 class tVector2
{
public:
T X, Y;
tVector2(T x, T y) : X(x), Y(y) {}
// Assigrnent operator
tVector2 operator = (tVector2 v) { X = v.X, Y=v.Y; return *this; }
};
// Multiplication operator
template tVector2 operator * (tVector2 v, T r) { return tVector2(v.X*r, v.Y*r); }
[/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(1,1);
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 operator *(tVector2,T)' : template parameter 'T' is ambiguous
error C2676: binary '*' : 'tVector2' does not define this operator or a conversion to a type acceptable to the predefined operator[/ccWN_text]

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 operator * (tVector2 v, float r);
tVector2 operator * (tVector2 v, double r);
[/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[/cci_cpp] multiplied by a double. Since the compiler can't figure out whether template argument T is a float or a double, he throws the error that is ambiguous.
The second error comes forth because the compiler still tries to solve. However it cannot convert [cci_cpp]tVector2[/cci_cpp] to a [cci_cpp]tVector2[/cci_cpp] and he can't demote the second argument "2.0" to a float because their second form exists and denies him from demoting just like the following examples forbids only using the first form.

[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[/cci_cpp] does not change, but it is rather easy. We simply add another operator which look as followed.

[cc_cpp]
template
tVector2 operator * (tVector2 v, R r) {
return tVector2(v.X*r, v.Y*r);
}[/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 operator * (tVector2 v, int r);
tVector2 operator * (tVector2 v, float r);
tVector2 operator * (tVector2 v, double r);
[/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 Vec2(1,1);
tVector2 Vec2d(1,1);
Vec2d = Vec2d * Vec2; // tVector2=tVector2*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 operator *(tVector2,R)' : could not deduce template argument for 'tVector2' from 'double'
error C2677: binary '*' : no global operator found which takes type 'tVector2' (or there is no acceptable conversion)[/ccWN_text]

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 operator * (tVector2 v, tVector2 r){
return tVector2(v.X*r, v.Y*r);
}[/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[/cci_cpp] and although there are times where this kind of multiplication is correct, the result would be a tVector2 which is wrong. In fact the above code could even be made working with a few simple steps. It would not give the result we are looking for, but it would work. The wrong steps would be, adding a new constructor which has as input two vectors and allowing numbers be multiplied by vectors (like in the first solution, but then the two function arguments switched around). However I will provide you with the correct solution.

SECOND SOLUTION: USE MULTIPLE TEMPLATE ARGUMENTS WITH TYPES

Similar to the first problem we add the following function:

[cc_cpp]template
tVector2 operator * (tVector2 v, tVector2 r) {
return tVector2(v.X*r.X, v.Y*r.Y);
}[/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 Vec2(1,1);
tVector2 Vec2d(1,1);
Vec2d = Vec2 * Vec2d; // tVector2 = tVector2 * tVector2
return 0;
}[/cc_cpp]

As you can see the result of the multiplication is now [cci_cpp]tVector2[/cci_cpp] while it previously was a [cci_cpp]tVector2[/cci_cpp]. This multiplication works, but the problem is the assignment operation. We try to store a [cci_cpp]tVector2[/cci_cpp] in a [cci_cpp]tVector2[/cci_cpp] and that is not allowed. If you look back at the original class you will see that the assignment operator requires that the function argument is of the same type as the class to which it belongs. And if there is one thing I have learned from the previous problem then it is that is not allowed.
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 class tVector2
{
public:
T X, Y;
tVector2(T x, T y) : X(x), Y(y) {}
// Assigment operator (didn't solve the third problem)
tVector2 operator = (tVector2 v) { X = v.X, Y=v.Y; return *this; }

// Assigment operator which allows other types (solved third problem)
template tVector2 operator = (tVector2 v){
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)