I have a class meant to implement a matrix, here:
template<typename Comparable>
class Matrix {
private:
std::size_t num_cols_;
std::size_t num_rows_;
Comparable **array_;
public:
Matrix();
~Matrix(); // Destructor
Matrix(const Matrix<Comparable> & rhs);// Copy constructor
Matrix(Matrix<Comparable> && rhs); // Move constructor
Matrix<Comparable> & operator= (const Matrix<Comparable> & rhs);// Copy assignment
Matrix<Comparable> & operator= (Matrix<Comparable> && rhs); // Move assignment
template<typename buh> friend std::ostream &operator<< (std::ostream &os, Matrix<buh> &rhs);
void ReadMatrix();
};
(Vectors aren't an option for this particular problem.)
The array_ member in particular holds the matrix itself, and is populated using the following code:
array_ = new Comparable*[num_rows_];
for (int i = 0; i < num_rows_; ++i) {
array_[i] = new Comparable[num_cols_];
};
for(int i = 0;i < num_rows_; ++i) {
std::cout << "Enter items for row " << i << "." << std::endl;
for(int j = 0;j < num_cols_; ++j) {
std::cin >> array_[i][j];
}
}
I can fill the array with values and access them, and my copy constructor and move assignment operator are functional, but the move assignment operator throws out a strange bug. here's the definition.
template<typename Comparable>
Matrix<Comparable>& Matrix<Comparable>::operator= (Matrix<Comparable> && rhs) {
delete[] array_;
array_ = new Comparable*[rhs.num_rows_];
for(int i = 0;i < rhs.num_rows_;++i) {
std::swap(array_[i],rhs.array_[i]);
rhs.array_[i] = nullptr;
}
rhs.num_cols_ = 0;
rhs.num_rows_ = 0;
rhs.array_ = nullptr;
return *this;
}
Take the statement a = std::move(b);. If b is of a different size than a, the matrix data is deformed by the move. If b has more columns than a, the extra columns will be cut off; if b has fewer rows than a, the excess rows will remain in a; if a has more columns or rows than b, the excess columns will display memory address where there should be nothing at all. Is this a simple bug? Is there a problem with way I create the arrays? Any insight into what's causing this is appreciated.
"Move assign" doesn't mean "carefully modify the passed in object to become some sort of 'empty' value", it means "it's fine to modify the passed in object".
Move assignment here should have a very simple implementation: just swap.
template<typename Comparable>
Matrix<Comparable>& Matrix<Comparable>::operator= (Matrix<Comparable> && rhs) {
using std::swap;
swap(array_, rhs.array_);
swap(num_cols_, rhs.num_cols_);
swap(num_rows_, rhs.num_rows_);
return *this;
}
Not sure why you are using new Comparable* in your move assignment operator. The idea of a move assignment is to move the resources, not make new ones.
Your code could look like:
delete[] array_;
array_ = rhs.array_;
rhs.array_ = nullptr;
num_cols_ = rhs.num_cols_;
num_rows_ = rhs.num_rows_;
return *this;
However, consider using the copy-and-swap idiom. It's not always the most efficient option but it is a good starting point if you're not a guru.
Note: If you really want to use a pointer to pointer to implement your matrix, use vector<vector<Comparable>> instead. All the work is done for you; your code is just reinventing the wheel.
Usually it is simpler and more effective to represent a matrix with one contiguous allocation, instead of a separate allocation for each row, so you may want to give that idea some thought.
Related
I am building a template matrix class for use in my future c++ code. I have a few questions regarding value passing for overloaded operators, exceptions vs asserts, and general class design.
Am I passing the values correctly? Is it efficient? What can I do otherwise to make it better?
This library is built with future application design in mind (terminal or gui), where a user could define their own matrices and run calculations. Would using exceptions instead of asserts be better in this case?
I have looked up the rule of 5 for c++, where it states that:
Because the presence of a user-defined destructor, copy- constructor, or copy-assignment operator prevents implicit definition of the move constructor and the move assignment operator, any class for which move semantics are desirable, has to declare all five special member functions.
Can I get away with not implementing this rule by just not having any of those three?** What would be the standard way to make this class more functional?
I have subtraction, multiplication, and division (scalar) defined in my program with the same/similar structure as the provided addition operator definitions, so not all of that code is necessary here.
Any hard advice or criticism on the overall design is accepted!
#ifndef MACMATRICES_H
#define MACMATRICES_H
#include <vector>
#include <iomanip>
#include <iostream>
#include <exception>
#include "../../DMF-Terminal.h"
namespace DMF
{
template <typename T>
class matrix
{
public:
// Constructors
matrix();
matrix(int p_rows, int p_columns);
// Operators
std::vector<T>& operator[] (size_t i) { return m[i]; }
matrix<T> operator+(const matrix<T>& rhs);
matrix<T> operator+(const T& rhs);
matrix<T>& operator+=(const matrix<T>& rhs);
matrix<T>& operator+=(const T& rhs);
// Class Methods
void print() const;
matrix<T> inverse();
T determinant();
// Observers
bool isSquare() const;
int rowSize() const { return m_rows; }
int colSize() const { return m_cols; }
private:
int m_rows, m_cols;
std::vector< std::vector<T> > m;
};
/* Constructors -----------------------------------------------------------------------------------*/
template <typename T>
matrix<T>::matrix(){}
template <typename T>
matrix<T>::matrix(int p_rows, int p_cols) :
m(p_rows, std::vector<T>(p_cols)), m_rows(p_rows), m_cols(p_cols) {}
/* Addition ---------------------------------------------------------------------------------------*/
template <typename T>
matrix<T> matrix<T>::operator+(const matrix<T>& rhs)
{
try
{
if((this->rowSize() == rhs.rowSize()) && (this->colSize() == rhs.colSize()))
{
matrix<T> sum (this->rowSize(), this->colSize());
for(int i = 0; i < this->rowSize() ; ++i)
{
for(int j = 0; j < this->colSize(); ++j)
sum.m[i][j] = this->m[i][j] + rhs.m[i][j];
}
return sum;
}
else throw std::runtime_error("Cannot add matrices, invalid row/column sizes.");
}
catch (std::exception &e)
{
std::cout << "Error: " << e.what(); DMF::wait();
}
}
template <typename T>
matrix<T> matrix<T>::operator+(const T& rhs)
{
matrix<T> sum (this->rowSize(), this->colSize());
for(int i = 0; i < this->rowSize() ; ++i)
{
for(int j = 0; j < this->colSize(); ++j)
sum.m[i][j] = this->m[i][j] + rhs;
}
return sum;
}
template <typename T>
matrix<T>& matrix<T>::operator+=(const matrix<T>& rhs)
{
try
{
if((this->rowSize() == rhs.rowSize()) && (this->colSize() == rhs.colSize()))
{
for(int i = 0; i < this->rowSize() ; ++i)
{
for(int j = 0; j < this->colSize(); ++j)
this->m[i][j] += rhs.m[i][j];
}
return *this;
}
else throw std::runtime_error("Cannot add matrices, invalid row/column sizes.");
}
catch (std::exception &e)
{
std::cout << "Error: " << e.what(); DMF::wait();
}
}
template <typename T>
matrix<T>& matrix<T>::operator+=(const T& rhs)
{
matrix<T> sum (this->rowSize(), this->colSize());
for(int i = 0; i < this->rowSize() ; ++i)
{
for(int j = 0; j < this->colSize(); ++j)
this->m[i][j] += rhs;
}
return *this;
}
}
#endif /* MACMATRICES_H */
As of right now, this code works within a mini terminal program. I also have matrix * matrix and matrix *= matrix operators overloaded and it seems to be working correctly, with the result matrix size being correct.
Am I passing the values correctly? Is it efficient? What can I do otherwise to make it better?
You pass your matrix by (const) reference, so you avoid copy, so it is fine.
You have some "typo" as variable used as matrix<T> sum in operator+=.
You duplicate some information, std::vector knows its size.
linearise std::vector<std::vector<T>> into std::vector<T>> would be more cache friendly, but requires then a proxy class (with another operator[]) to handle operator[], or you might use instead an accessor as operator()(int, int) or operator[](std::pair<int, int>).
This library is built with future application design in mind (terminal or gui), where a user could define their own matrices and run calculations. Would using exceptions instead of asserts be better in this case?
Exceptions are to communicate the error to the user, but currently, you catch it directly to ignore the error with some log... So instead of throwing, you could directly log the error currently.
There are several to fix that issue:
Have matrix size in template argument and use type system to check those error at compilation. require to know size at compilation though.
If you consider that user might be able to ignore/recover from the error, then let's propagate exception. You might probably have dedicated exceptions.
If you consider that user might not be able to ignore/recover from the error, then assert/terminate/UB are possible way.
I have looked up the rule of 5 for c++, where it states that: "Because the presence of a user-defined destructor, copy- constructor, or copy-assignment operator prevents implicit definition of the move constructor and the move assignment operator, any class for which move semantics are desirable, has to declare all five special member functions." Can I get away with not implementing this rule by just not having any of those three? What would be the standard way to make this class more functional?
The rule of 3 has variants:
rule of 5 to also include move constructor and move assignation.
rule of 0, where all member are already RAII friendly and so default implementation is fine.
using std::vector allows to use rule of 0 :-)
So your are fine.
I have overloaded assignment operator for the class with a 2D array, but in order to do memory management and resizing correct I have to delete previous matrix first, then construct a new one, and only then I can start assigning.
Matrix& Matrix::operator = (const Matrix& m1){
for (int i = 0; i < m_rows; ++i)
delete[] m_matrix[i];
delete[] m_matrix;
m_matrix = new double*[m1.rows()];
for (int i = 0; i < m1.rows(); ++i)
m_matrix[i] = new double[m1.cols()]();
for (int k = 0; k < m1.rows(); ++k)
for (int j = 0; j < m1.cols(); ++j)
m_matrix[k][j] = m1.m_matrix[k][j];
m_rows = m1.rows();
m_cols = m1.cols();
return *this;
}
In fact, this part is destructor of my class:
for (int i = 0; i < m_rows; ++i)
delete[] m_matrix[i];
delete[] m_matrix;
And this part is similar to a constructor:
m_matrix = new double*[m1.rows()];
for (int i = 0; i < m_rows; ++i)
m_matrix[i] = new double[m1.cols()]();
What annoys me is that I have to copy constructors' and destructors' code in the assignment function (and some other functions too!) to make it work properly. Is there a better way to write it?
The ideal improvement would be Matrix& Matrix::operator=(const Matrix&) = default;.
If you switch to using std::vector for matrix storage you won't need to implement the copy/move constructors/assignments and destructor at all.
If what you are doing is a programming exercise, create your own dynamic array and use that in the implementation of your matrix.
I cannot recommend enough watching Better Code: Runtime Polymorphism by Sean Parent, he makes an effective demonstration of why you should strive to write classes that do not require non-default implementations of copy/move constructors/assignments and destructor.
Example:
template<class T>
class Matrix
{
std::vector<T> storage_;
unsigned cols_ = 0;
public:
Matrix(unsigned rows, unsigned cols)
: storage_(rows * cols)
, cols_(cols)
{}
// Because of the user-defined constructor above
// the default constructor must be provided.
// The default implementation is sufficient.
Matrix() = default;
unsigned columns() const { return cols_; }
unsigned rows() const { return storage_.size() / cols_; }
// Using operator() for indexing because [] can only take one argument.
T& operator()(unsigned row, unsigned col) { return storage_[row * cols_ + col]; }
T const& operator()(unsigned row, unsigned col) const { return storage_[row * cols_ + col]; }
// Canonical swap member function.
void swap(Matrix& b) {
using std::swap;
swap(storage_, b.storage_);
swap(cols_, b.cols_);
}
// Canonical swap function. Friend name injection.
friend void swap(Matrix& a, Matrix& b) { a.swap(b); }
// This is what the compiler does for you,
// not necessary to declare these at all.
Matrix(Matrix const&) = default;
Matrix(Matrix&&) = default;
Matrix& operator=(Matrix const&) = default;
Matrix& operator=(Matrix&&) = default;
~Matrix() = default;
};
The canonical implementation of the assignment operator leverages existing functionality (copy/move ctor, dtor, andswap(); note that using a non-specialized std::swap() would be bad). It looks like this:
T& T::operator= (T val) {
val.swap(*this);
return *this;
}
It nicely avoids reimplementing otherwise existing logic. It also deals gracefully with self-assignment which is a problem in your original code (it will do work but self-assignment is generally rather uncommon; optimizing it with a check against self-assignment typically pessimizes code).
The argument is passed by value to take advantage of copy elision.
The primary caveats with this approach are below. In general I prefer the canonical implementation as it is generally more correct and the outlined issue are often not really that relevant (e.g., when the object was just created anyway the transferred memory is actually "hot").
It does not attempt to reuse already allocated and possibly "hot" memory. Instead it always uses new memory.
If the amount of held data is huge, there are copies temporarily held which may exceed system limits. Reusing existing memory and/or release memory first would both address this issue.
I am new to C++11 so i still strugle with its concepts.
Here's my problem :
I have a matrix class :
class matrix
{
private:
double** data;
size_t number_lines;
size_t number_columns;
size_t capacity_lines;
size_t capacity_columns;
public:
....
}
and i've provided a copy constructor, a move constructor...
I've overloaded the multiplication operator *(double x) to multiply matrix elements by the scalar x and return the multiplied matrix.
Here's the code :
matrix matrix::operator*(double lambda)
{
double** aux_data = new double*[number_lines];
for (size_t i = 0; i < number_lines; i++)
{
aux_data[i] = new double[number_columns];
for (size_t j = 0; j < number_columns; j++)
aux_data[i][j] = lambda*data[i][j];
}
return matrix(aux_data, number_lines, number_columns);
}
the return of the function is an rvalue reference so it invokes the move constructor. Here's the code of the move constructor :
matrix::matrix(const matrix&& moved_copy)
{
if (this != &moved_copy)
{
number_columns = moved_copy.number_columns;
number_lines = moved_copy.number_lines;
data = moved_copy.data;
}
}
The problem with this move constructor is that it performs a shallow copy and not a deep copy (like every move constructor i guess, otherwise what's the point of this move constructor) so the member data points to the object pointed by moved_copy.data, but this object is local to the operator *=() function so when the operator goes out of scope the object is gone and i have a dangling pointer. So my question is : should i perform a deep copy in the move constructor or is there way of solving this problem without doing so ?
Thank you.
No, you shouldn't make a deep copy in a move constructor. The whole point of a move constructor is to take ownership of some resource that's expensive to copy.
In this case, ownership of your data pointer can be transferred from an existing matrix to the newly constructed matrix object. But the idea is to transfer ownership, to the new object, not to share ownership with the new object. In this case that just means setting moved_copy.data to nullptr, that way it won't delete your data when it's destroyed.
matrix::matrix(matrix&& moved_copy)
{
number_columns = moved_copy.number_columns;
number_lines = moved_copy.number_lines;
data = moved_copy.data;
moved_copy.data = nullptr;
}
Notice that I also removed your if guard: there's no way to construct an object from itself, so that's not really needed for a move constructor (it can be useful for a move assignment operator though).
I also removed the const from moved_copy. Move constructors need to modify the state of the moved-from object to take ownership of its resources, so const cant' be used.
Edit: It is actually possible to construct an object from itself, but it's not something that you really need to guard against.
The problem with this move constructor is that the data member has the same value on both objects after the move, such that when the first object is deleted, the second has a pointer to deleted memory.
Change the move constructor to:
matrix::matrix(matrix&& moved_copy)
{
if (this != &moved_copy)
{
number_columns = moved_copy.number_columns;
number_lines = moved_copy.number_lines;
data = moved_copy.data;
moved_copy.number_columns = 0;
moved_copy.number_lines = 0;
moved_copy.data = nullptr;
}
}
The check if (this != &moved_copy) could be omitted, because an object is normally not constructed by moving from itself.
No you should not perform a deep copy in the move constructor. Otherwise you don't gain anything and the notion of a move constructor is broken:
matrix::matrix(matrix&& moved_copy)
: data(moved_copy.data),
number_rows(moved_copy.number_rows),
number_columns(moved_copy.number_columns),
capacity_rows(moved_copy.capacity_rows),
capacity_columns(moved_copy.capacity_columns) {
moved_copy.data = nullptr;
}
Furthermore, avoid to define binary operators as member functions because you're breaking the mathematical property of commutativity. That is, although:
matrix M;
...
matrix K = m * 2.0;
will work. The following won't:
matrix M;
...
matrix K = 2.0 * m;
Prefer to define binary operators as free functions.
matrix operator*(matrix const &m, double lambda) {
matrix out(m.aux_data, m.number_rows, m.number_columns);
...
return out;
}
matrix operator*(double lambda, matrix const &m) {
return m * lambda;
}
I am new to C++11.
So you won't mind me suggesting that you implement your matrix in terms of std::vector, as then all your move concerns are solved for you:
Here's the beginning of one implementation:
#include <vector>
#include <cstddef>
#include <iostream>
class matrix
{
private:
std::vector<double> storage_;
std::size_t row_capacity_;
std::size_t rows_;
std::size_t cols_;
std::size_t get_location(std::size_t row, std::size_t col) const
{
return row * row_capacity_ + col;
}
public:
matrix(std::size_t rows, std::size_t cols, std::size_t row_capacity, std::size_t col_capacity)
: storage_(row_capacity * col_capacity)
, row_capacity_(row_capacity)
, rows_(rows)
, cols_(cols) {}
matrix(std::size_t rows, std::size_t cols)
: matrix(rows, cols, rows, cols) {}
//
// note that all move/copy operations are automatically generated.
// "The rule of none"
//
std::size_t get_rows() const { return rows_; }
std::size_t get_cols() const { return cols_; }
std::size_t get_capacity_rows() const { return row_capacity_; }
std::size_t get_capacity_cols() const { return storage_.capacity() / row_capacity_; }
double& at(std::size_t row, std::size_t col)
{
return storage_[get_location(row, col)];
}
double const& at(std::size_t row, std::size_t col) const
{
return storage_[get_location(row, col)];
}
};
int main()
{
auto m = matrix(3, 3);
m.at(1, 1) = 2;
std::cout << m.at(1, 1) << std::endl;
std::cout << m.get_cols() << std::endl;
std::cout << m.get_rows() << std::endl;
std::cout << m.get_capacity_cols() << std::endl;
std::cout << m.get_capacity_rows() << std::endl;
}
I have implemented a Matrix class with a move assignment as
template <typename OutType>
class Matrix
{
public:
int Rows_; // number of Rows
int Columns_; // number of Columns
OutType *data_; // row Major order allocation
// STUFF
Matrix<OutType> & operator=(Matrix<float>&& other) {
swap(other);
return *this;
}
void swap(Matrix<float>& other) {
int t_Rows_ = Rows_; Rows_ = other.Rows_; other.Rows_ = t_Rows_;
int t_Columns_ = Columns_; Columns_ = other.Columns_; other.Columns_ = t_Columns_;
float* t_ptr = data_;
data_ = other.data_;
other.data_ = t_ptr; }
}
in order to implement the B=f(A); syntax, as suggested in
C++: Implementing B=f(A), with B and A arrays and B already defined
As possible function, I'm considering the FFT, implemented as
Matrix<float> FFT(const Matrix<float> &in)
{
Matrix<float> out(in.GetRows(),in.GetColumns());
// STUFF
return out;
}
Is there any room for further efficiency improvements? Is there any further trick to improve, for example, the move assignment or the swap function?
EDIT: NEW SOLUTION FOLLOWING KONRAD RUDOLPH'S COMMENT
Matrix & operator=(Matrix&& other) {
std::swap(Rows_, other.Rows_);
std::swap(Columns_, other.Columns_);
std::swap(data_, other.data_);
std::cout << "move assigned \n";
return *this;
}
I recommend implementing move-assignment and move-construction for your class:
Matrix( Matrix<OutType> &&that ) noexcept
: Rows_(that.Rows_)
, Cols_(that.Cols_)
, data_(that.data_)
{
that.Rows_ = that.Cols_ = 0;
that.data_ = nullptr;
}
Matrix<OutType> &operator=( Matrix<OutType> &&that ) noexcept {
using std::swap;
swap( Rows_, that.Rows_ );
swap( Cols_, that.Cols_ );
swap( data_, that.data_ );
return *this;
}
If you implement move operations (construction and assignment) like this, std::swap should work great for your code, and you don't need to provide your own. If you do want to provide your own implementation of swap, I recommend providing it as a two-argument friend function so that it can be found through Argument Dependent Look-up. I also recommend calling swap (and all other functions) without namespace qualifications, as shown above, so that ADL is not suppressed (unless, for some reason, you really need to specify exactly which function is called, and an overload customized for the specific type would be wrong). ADL is especially valuable when dealing with templated code. If you call std::swap with the std:: qualifier, you significantly reduce the opportunity for user-defined types to provide a more efficient swap implementation.
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.
template<class T, size_t NRows, size_t NCols>
class Matrix
{public:
Matrix() {} // `data` gets its default constructor, which for simple types
// like `float` means uninitialized, just like C.
Matrix(const T& initialValue)
{ // extra braces omitted for brevity.
for(size_t i = 0; i < NRows; ++i)
for(size_t j = 0; j < NCols; ++j)
data[i][j] = initialValue;
}
template<class U>
Matrix(const Matrix<U, NRows, NCols>& original)
{
for(size_t i = 0; i < NRows; ++i)
for(size_t j = 0; j < NCols; ++j)
data[i][j] = T(original.data[i][j]);
}
private:
T data[NRows][NCols];
public:
// Matrix copy -- ONLY valid if dimensions match, else compile error.
template<class U>
const Matrix<T, NRows, NCols>& (const Matrix<U, NRows, NCols>& original)
{
for(size_t i = 0; i < NRows; ++i)
for(size_t j = 0; j < NCols; ++j)
data[i][j] = T(original.data[i][j]);
return *this;
}
// Feel the magic: Matrix multiply only compiles if all dimensions
// are correct.
template<class U, size_t NOutCols>
Matrix<T, NRows, NOutCols> Matrix::operator*(
const Matrix<T, NCols, NOutCols>& rhs ) const
{
Matrix<T, NRows, NOutCols> result;
for(size_t i = 0; i < NRows; ++i)
for(size_t j = 0; j < NOutCols; ++j)
{
T x = data[i][0] * T(original.data[0][j]);
for(size_t k = 1; k < NCols; ++k)
x += data[i][k] * T(original.data[k][j]);
result[i][j] = x;
}
return result;
}
};
So you'd declare a 2x4 matrix of floats, initialized to 1.0, as:
Matrix<float, 2, 4> testArray(1.0);
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:
Matrix<int, 2, 4> testArrayIntA(2);
Matrix<int, 4, 2> testArrayIntB(100);
For copying, dimensions must match though types do not:
Matrix<float, 2, 4> testArray2(testArrayIntA); // works
Matrix<float, 2, 4> testArray3(testArrayIntB); // compile error
// No implementation for mismatched dimensions.
testArray = testArrayIntA; // works
testArray = testArrayIntB; // compile error, same reason
Multiplication must have the right dimensions:
Matrix<float, 2, 2> testArrayMult(testArray * testArrayIntB); // works
Matrix<float, 4, 4> testArrayMult2(testArray * testArrayIntB); // compile error
Matrix<float, 4, 4> testArrayMult2(testArrayIntB * testArray); // works
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 use operator () to perform multiple-index accesses. This is because operator [] can only take one parameter, but operator () 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 a M[x][y] syntax.
That said, if there's some kind of standard matrix resizing that you do to resize one Matrix into 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 any Matrix into a column vector:
template<class T, size_t NRows, size_t NCols>
Matrix<T, NRows * NCols, 1> column_vector(const Matrix<T, NRows, NCols>& original)
{ Matrix<T, NRows * NCols, 1> result;
for(size_t i = 0; i < NRows; ++i)
for(size_t j = 0; j < NCols; ++j)
result.data[i * NCols + j][0] = original.data[i][j];
// Or use the following if you want to be sure things are really optimized.
/*for(size_t i = 0; i < NRows * NCols; ++i)
static_cast<T*>(result.data)[i] = static_cast<T*>(original.data)[i];
*/
// (It could be reinterpret_cast instead of static_cast. I haven't tested
// this. Note that the optimizer may be smart enough to generate the same
// code for both versions. Test yours to be sure; if they generate the
// same code, prefer the more legible earlier version.)
return result;
}
... 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 result and 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 empty Matrix). There's a trick I've heard called "Compile-Time Assertion" which uses template specialization and is declared as:
template<bool Test> struct compiler_assert;
template<> struct compiler_assert<true> {};
What this does is let you write code such as:
private:
static const compiler_assert<(NRows > 0)> test_row_count;
static const compiler_assert<(NCols > 0)> test_col_count;
The basic idea is that, if the condition is true, the template turns into an empty struct that nobody uses and gets silently discarded. But if it's false, the compiler can't find a definition for struct 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:
template<bool> struct CompileTimeChecker
{ CompileTimeChecker(...); };
template<> struct CompileTimeChecker<false> {};
#define STATIC_CHECK(expr, msg) { class ERROR_##msg {}; \
(void)sizeof(CompileTimeChecker<(expr)>(ERROR_##msg())); }
What you fill in for msg has to be a valid identifier (letters, numbers, and underscores only), but that's no big deal. Then we just replace the default constructor with:
Matrix()
{ // `data` gets its default constructor, which for simple types
// like `float` means uninitialized, just like C.
STATIC_CHECK(NRows > 0, NRows_Is_Zero);
STATIC_CHECK(NCols > 0, NCols_Is_Zero);
}
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 the true case, the generated code gets discarded so long as the test has no side effects, so there's no bloat.
I'm not sure I understand what you're asking.
But I would point out that your operator declarations are incorrect and/or incomplete.
Firstly, the assignment operator should return the same type as its parameter; viz:
const Matrix& operator=(const Matrix& src);
Secondly, binary operators return a new object, so you can't return a reference. All binary operators should be declared thus:
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
Actually, it is considered better style to declare and implement binary operators as global friend functions instead:
class Matrix { ... };
inline Matrix operator+(const Matrix& lhs,const Matrix& rhs)
{ return Matrix(lhs)+=rhs; }
Hope this helps.
Now I understand what you're asking.
Presumably your implementation of various operators will in this case consist of operations on the composite types. So really the question of whether Matrix op Matrix is meaningful depends on whether string op int is meaningful (and whether such a thing might be useful). You would also need to determine what the return type might be.
Assuming the return type is the same as the LHS operand, the declarations would look something like:
template <typename T>
class Matrix
{
template <typename U>
Matrix<T>& operator+=(const Matrix<U>& rhs);
};
template <typename T,typename U>
Matrix<T> operator+(const Matrix<T>& lhs,const Matrix<U>& rhs)
{ return Matrix<T>(lhs)+=rhs; }
Matrix<double> x = ...;
Matrix<int> y = ...;
cout << x + y << endl; // prints a Matrix<double>?
OK, that's doable, but the problem gets tricky quickly.
Matrix<double> x = ...
Matrix<complex<float>> y = ...
cout << x + y << endl; // Matrix<complex<double>>?
You will most likely be happiest if you require that your binary operators use like-type operands and force your application builders to explicitly type-cast their values. For the latter case:
cout << ((Matrix<complex<double>>) x) + ((Matrix<complex<double>>) y) << endl;
You can provide a member template constructor (or a type conversion operator) to support the conversions.
template <typename T>
class Matrix {
...
public:
template <typename U>
Matrix(const Matrix<U>& that) {
// initialize this by performing U->T conversions for each element in that
}
...
};
The alternative, having your binary operator template deduce the correct Matrix return type based on the element types of the two operands, requires some moderately complex template meta-programming, probably not what you're looking to get into.
First of all, the copy assignment operator should not have const Matrix& as its return type; your interface is correct.
Grant's suggestion of how binary operators are implemented is the generally accepted way of doing these things.
It's a nice exercise, but one quickly sees why doing linear algebra in C++ is a bad idea to begin with. Operations like A+B and A*B are valid only if the dimensions of the matrices match up.
You don't have to add much at all, because inside a template, the class name itself refers to the current template parameter. So the following are equivalent:
template <typename T> struct Foo
{
Foo<T> bar(const Foo<T> &);
Foo bar2(const Foo *); // same
};
So all your operations just go through without change. What you should add is a constructor that converts one matrix type into another:
temlate <typename T> class Matrix
{
template <typename U> Matrix(const Matrix<U> &); // construct from another matrix
/*...*/
};
Using that conversion constructor, you can mix matrices in your operators, as Matrix<T>::operator+(Matrix<U>) will use the conversion to create an argument of type Matrix<T>, and then you use your already implemented operator.
In C++11 you can add static_assert(std::is_convertible<U, T>::value, "Boo"); to your converting constructor to give you a useful compile-time diagnostic if you call it with an incompatible type.