
Jason Hise wrote:
On 11/8/06, Jason Kraftcheck <kraftche@cae.wisc.edu> wrote:
Jason Hise wrote:
I did something very much like this a while back, which was submitted as a potential jumping off point for a boost geometry library. It is in the boost vault under Math - Geometry (http://tinyurl.com/ymjsoz), along with two other submissions. (Mine is geometryJH_01.zip).
I think that my design of the matrix class is a little better because a vector is defined as a Nx1 matrix, rather than the matrix being defined as an array of vectors. This avoids the need for special operators for vector-matrix multiplication, is closer to the underlying mathematical concept, and makes the equivalency of a vector and column matrix explicit.
I suppose this makes sense from an efficiency standpoint. I prefered the matrix as a vector of vectors because it made defining algorithms in terms of elementary row operations cleaner, and because it allowed the more natural syntax of indexing the matrix as though it were a 2D array.
Indexing the matrix as if it were a 2D array can be accomplished other ways. For example: private: Type data[R*C]; public: Type operator[](unsigned i) { return data+i*C; } const Type* operator[](unsigned i) const { return data+i*C; } To index it as if it were a 2D array, the operator[] must return something that also has an operator[]. An array of works as well as a vector. And I fail to see how row operations make the implementation cleaner or easier. Most operators can be implemented with a single loop in both cases. For example: private: Type data[R*C]; public: matrix<R,C,Type>& operator+=( const matrix<R,C,Type>& other ) { for (unsigned i = 0; i < R*C; ++i) data[i] += other.data[i]; return *this; }
There are two usability features that my implementation provides which yours does not. The first is being able to index a vector by name (_x, _y, _z, _w). This is easily implemented using a properly scoped enum. I used leading underscores for my component names as a convention to indicate that they were property names, though admittedly this naming convention could be debated.
vector <float, 3> up; up[_x] = 0; up[_y] = 1; up[_z] = 0;
I've never found such a convention very useful. It is a trivial thing to add if others do.
Another thing that my code allows that yours does not is explicit construction of a vector from the number of parameters that matches the size (at least from 1 through 4), using the SFINAE techniques of boost.enable_if. It can be much nicer for client code to be able to write:
vector <float, 3> up (0, 1, 0);
than to have to write the code segment I used for my previous example.
That is a useful feature. I had considered several ways of implementing it. However, I had doubts about all of them, so I didn't provide any. 1) varargs o matrix( Type ii, ... ); o no type checking on arguments 2) multiple constructors with different numbers of args o what you have implemented o seems conceptually wrong to have a four-arg constructor for a 2-element vector, or visa versa. o doesn't work for larger matrices 3) one constructor with unrolled assignment o most of the same limitations as 2) o one function o safe (will never write off the end of the array) o matrix( Type a, Type b , Type c = 0, Type d = 0, Type e = 0, Type f = 0, Type g = 0, Type h = 0, Type i = 0, Type j = 0, Type k = 0, Type l = 0, Type m = 0, Type n = 0, Type o = 0, Type p = 0 ) { switch (R*C) { case 16: data[15] = p; case 15: data[14] = o; ... case 1: data[ 0] = a; } o excessively large argument list for e.g. a 2x1 matrix - jason