I've been doing lots of vector math stuff and wrote my own template for it.
My requirements are lots of vector maths (addition, subtraction, scale, cross prod, and dot prod) also I need to be able to pass my vector as a float[] so that openGL can make use of it.
I've been using this quite happily for sometime, today a lecturer saw it and groaned. There was two things he particularly hated (one of which I understand), my use of inheritance because it didn't seem to follow an is a
style. And my casting (T*)this
, of course he didn't have much in the way of a solution.
First:
The inheritance, I need to be able to make use of vec2 to vec4's so I designed my vectors like this.
template<typename T>
Vector2D
{
public:
getX(), getY(), setX(), setY() ....
};
template<typename T>
Vector3D : public Vector2D<T>
{
public:
getZ(), setZ() ...
}
template<typename T>
Vector4D : public Vector3D<T>
{
public:
getW(), setW() ...
}
Why is this bad? and tbh I can't see how to improve it. I need(want) to be able to define type and have getters and setters of some sort. If I rearranged it like
template<typename T, int _size>
VectorT
I'd loose my .getX()
, .setX()
stuff and have to replace it with something like .at()
or []
. tbh I prefer the readability of .getX()
, although it would make operator definitions easier.
Second:
I can see why this is bad, but to make it so I can pass these vectors into openGL's method that expect a float array I've overloaded the splat operator
// Defined in Vector2D<T>
operator*() { return (T*)this; }
As I understand it, there is no guarantee that the compiler will put the member variables x,y,z,w at the beginning of the class, and if not careful I might end up passing the v-table instead. However I must admit I've had no problems so far.
The only way I can see around this is to maintain an array that gets returned. Which I suppose would be easier if I changed they way I deal with vectors in the first place.
You may want to consider using GLM instead. It does all that you've described (though I found the documentation lacking) including integration with OpenGL.
You can listen to your teacher, and use partial specialization (warning : not tested) :
template<typename T, int size>
class Vector;
template< typename T >
class Vector< T, 2 >
{
public :
Vector() : data() {}
T GetX() const { return data[0]; };
T GetY() const { return data[1]; };
void SetX( const T v ) const { data[0]=v; };
void SetY( const T v ) const { data[1]=v; };
private :
T data[2];
};
template< typename T >
class Vector< T, 3 >
{
public :
Vector() : data() {}
T GetX() const { return data[0]; };
T GetY() const { return data[1]; };
T GetZ() const { return data[2]; };
void SetX( const T v ) const { data[0]=v; };
void SetY( const T v ) const { data[1]=v; };
void SetZ( const T v ) const { data[2]=v; };
private :
T data[3];
};
How about this:
template<class T, int _dim>
class Vector
{
T v[_dim];
operator*(){return v;}
friend T inner_product(Vector<T, _dim> const &v1, Vector<T, _dim> const &v2);
};
template<class T, int _dim>
T inner_product(Vector<T, _dim> const &v1, Vector<T, _dim> const &v2)
{
T p = 0.;
for(int i; i < _dim; i++)
p += v1.v[i] * v2.v[i];
return p;
}
template<class T>
class Vector2 : Vector<T, 2>
{
float getX() const {return v[0];}
float getS() const {return v[0];}
float getY() const {return v[1];}
float getT() const {return v[1];}
}
template<class T>
class Vector3 : Vector<T, 3>, Vector2<T>
{
float getZ() const {return v[2];}
float getR() const {return v[2];}
}
template<class T>
class Vector4 : Vector<T, 4>, Vector3<T>
{
float getW() const {return v[3];}
float getQ() const {return v[3];}
}
Note that making inner_product
a friend, not being part of the class, allows you to use it for all derived types!
Like you said, you are abusing the "is-a" nature of inheritance. The problem might arise if you wrote a function like this
float dotProduct(vector2D a, vector2D b);
You could pass in a 3D vector and get a scalar result when really the dot product of a 2d vector and a 3d vector is undefined and this is actually a bug which could lead to weird behavior. Granted this isn't a huge deal, but you are throwing away some of the type checking, and if you are going to deal with the pain of static typing you might as well gets its benefits when bugs like that arise.
Maintaining the array is definitely a superior solution, you don't want to rely on undefined behavior, because you never know when it's going to completely screw you over.
I'd probably do something like this:
template<typename T>
class VectorT{
protected:
T* m_data;
int m_size;
public:
VectorT(unsigned int size)
: m_size(size)
{
m_data=new T[size];
}
virtual ~VectorT()
{
delete[] m_data;
}
T* operator*() { return m_data; }
T& operator[](int ii) { return m_data[ii]; }
}
template<typename T>
class Vector3 : public VectorT<T>
{
public:
Vector3() : VectorT(3) {}
T getX() { return m_data[0]; }
T getY() { return m_data[1]; }
T getZ() { return m_data[2]; }
Vector3 crossP(const Vector3& vv) { ... }
}
Here is a complete OpenGL styled Math library ( open source ) for c++
http://glm.g-truc.net/