The templatized geometry library is fully independent of scalar type. Even special-purpose user-defined data types can be used to construct geometric objects, as long as they follow the syntax and semantics of an algebraic field. The exact requirements for a scalar field data type are listed in the scalar field type requirements page.
template <class ScalarParam,int dimensionParam>
class Vector
{
/* ... */
};
A generic function to calculate the dot product of two such vectors would have to be templatized by the same parameters, and have to look as follows:
template <class ScalarParam,int dimensionParam>
ScalarParam dot(const Vector<ScalarParam,dimensionParam>& v1,
const Vector<ScalarParam,dimensionParam>& v2)
{
ScalarParam result(0);
for(int i=0;i<dimensionParam;++i)
result+=v1[i]*v2[i];
return result;
}
Things start getting out of hand if several templatized data types have to interact. Assume a point type templatized on the same parameters as the vector type above, and a function that calculates the squared Euclidean distance between two points:
template <class ScalarParam,int dimensionParam>
ScalarParam dist2(const Point<ScalarParam,dimensionParam>& p1,
const Point<ScalarParam,dimensionParam>& p2)
{
Vector<ScalarParam,dimensionParam> dist=p2-p1;
return dot(dist,dist);
}
The problem here is that only a specific vector type is "compatible" with the point type the function is called with; and if for some reason the explicit types used lose compatibility (due to code changes, partial updates, API changes etc.) the code might still work - due to implicit conversions provided by templatization - but might lose performance.An approach to minimize the "viral" property of template parameters - a function that uses several templatized types with several template parameters each must be templatized by all parameters - is to use type definitions as shortcuts inside each templatized data type. For example, the actual vector data type used in the templatized geometry library looks like the following:
template <class ScalarParam,int dimensionParam>
class Vector
{
public:
typedef ScalarParam Scalar;
static const int dimension=dimensionParam;
/* ... */
};
The typedef and static const int seem redundant, but with them using templatized types becomes much easier. The dot product function can now be written as:
template <class VectorParam>
VectorParam::Scalar dot(const VectorParam& v1,const VectorParam& v2)
{
VectorParam::Scalar result(0);
for(int i=0;i<VectorParam::dimension;++i)
result+=v1[i]*v2[i];
return result;
}
Granted, this code snippet does not look much less complicated than the first one, but now the function is only templatized by a single parameter. The benefits become larger as functions become more complicated. Consider the following version of the squared distance function:
template <class PointParam>
PointParam::Scalar dist2(const PointParam& p1,const PointParam& p2)
{
PointParam::Vector dist=p2-p1;
return dot(dist,dist);
}
The real point data type used in the templatized geometry library contains a type definition for the vector type it is compatible with; thus, a user does not have to know which exact type to use. This style of specifying generic types is a lot safer, especially when larger projects use several different versions of points, vectors etc. in different parts of the code. The templatized geometry library uses embedded type definitions extensively, to minimize the number of parameters any function using it has to be templatized by, and to ensure maximum compatibility if intermediate results of calculations are of a type a user does not have to know or care about.Another benefit of this style of templatization is on a more semantic level. The templatized geometry library only contains vectors that are of the form Fn, where F is a field, and n is an integer dimension. The parameters for the first versions of the dot and dist2 functions reflected this. The second versions, however, are templatized by vector type. This means, if someone implemented a vector type that is not Fn, any functions not requiring the dimension parameter would still work. The second dot function is still too special, and should therefore be (and is) part of the vector type itself, but the second dist2 function is truly generic - it could as well be used to calculate the distance between two vectors in any Hilbert space.