I’m teaching myself c++ by creating my own data structure class (a matrix, to be exact) and I’ve changed it to a template class of type <T> from only using doubles. The overloaded matrix operators were pretty standard
// A snippet of code from when this matrix wasn't a template class
// Assignment
Matrix& operator=( const Matrix& other );
// Compound assignment
Matrix& operator+=( const Matrix& other ); // matrix addition
Matrix& operator-=( const Matrix& other ); // matrix subtracton
Matrix& operator&=( const Matrix& other ); // elem by elem product
Matrix& operator*=( const Matrix& other ); // matrix product
// Binary, defined in terms of compound
Matrix& operator+( const Matrix& other ) const; // matrix addition
Matrix& operator-( const Matrix& other ) const; // matrix subtracton
Matrix& operator&( const Matrix& other ) const; // elem by elem product
Matrix& operator*( const Matrix& other ) const; // matrix product
// examples of += and +, others similar
Matrix& Matrix::operator+=( const Matrix& rhs )
{
for( unsigned int i = 0; i < getCols()*getRows(); i++ )
{
this->elements.at(i) += rhs.elements.at(i);
}
return *this;
}
Matrix& Matrix::operator+( const Matrix& rhs ) const
{
return Matrix(*this) += rhs;
}
But now that Matrix can have a type, I’m having trouble determining which of the matrix references should be type <T> and what the consequences would be. Should I allow dissimilar types operate on each other (eg., Matrix<foo> a + Matrix<bar> b is valid)? I’m also a little fuzzy on how
One reason I’m interested in dissimilar types is to facilitate the use of complex numbers in the future. I’m a newbie at c++ but am happy to dive in over my head to learn. If you’re familiar with any free online resources that deal with this problem I would find that most helpful.
Edit: no wonder no one thought this made sense all of my angle brackets in the body were treated as tags! I can’t figure out how to escape them, so I’ll inline code them.
I figure that I should illustrate my comment about parameterizing matrix dimensions, since you might not have seen this technique before.
So you’d declare a 2×4 matrix of
floats, initialized to 1.0, as:Note that there is no requirement for the storage to be on the heap (i.e. using
operator new) since the size is fixed. You could allocate this on the stack.You can create another couple matrices of
ints:For copying, dimensions must match though types do not:
Multiplication must have the right dimensions:
Note that, if there’s a botch, it is caught at compile time. This is only possible if the matrix dimensions are fixed at compile time, though. Also note that this bounds checking results in no additional runtime code. It’s the same code that you’d get if you just made the dimensions constant.
Resizing
If you don’t know your matrix dimensions at compile time, but must wait until runtime, this code may not be of much use. You’ll have to write a class that internally stores the dimensions and a pointer to the actual data, and it will need to do everything at runtime. Hint: write your
operator []to treat the matrix as a reshaped 1xN or Nx1 vector, and useoperator ()to perform multiple-index accesses. This is becauseoperator []can only take one parameter, butoperator ()has no such limit. It’s easy to shoot yourself in the foot (force the optimizer to give up, at least) by trying to support aM[x][y]syntax.That said, if there’s some kind of standard matrix resizing that you do to resize one
Matrixinto another, given that all dimensions are known at compile time, then you could write a function to do the resize. For example, this template function will reshape anyMatrixinto a column vector:… well, I think that’s a column vector, anyway. Hope it’s obvious how to fix it if not. Anyway, the optimizer will see that you’re returning
resultand remove the extra copy operations, basically constructing the result right where the caller wants to see it.Compile-Time Dimension Sanity Check
Say we want the compiler to stop if a dimension is
0(normally resulting in an emptyMatrix). There’s a trick I’ve heard called “Compile-Time Assertion” which uses template specialization and is declared as:What this does is let you write code such as:
The basic idea is that, if the condition is
true, the template turns into an emptystructthat nobody uses and gets silently discarded. But if it’sfalse, the compiler can’t find a definition forstruct compiler_assert<false>(just a declaration, which isn’t enough) and errors out.Better is Andrei Alexandrescu’s version (from his book), which lets you use the declared name of the assertion object as an impromptu error message:
What you fill in for
msghas to be a valid identifier (letters, numbers, and underscores only), but that’s no big deal. Then we just replace the default constructor with:And voila, the compiler stops if we mistakenly set one of the dimensions to
0. For how it works, see page 25 of Andrei’s book. Note that in thetruecase, the generated code gets discarded so long as the test has no side effects, so there’s no bloat.