1

I'm writing a Matrix template class that can support both row and column major storage. Ideally I'd like to specialise only the methods that are affected by the storage format. However, when I try to specialise a method (as below) I get nothing but error messages.

enum MatrixStorage
{
    ColumnMajor,
    RowMajor
};

template< typename T,
          unsigned rows, 
          unsigned columns, 
          MatrixStorage storage = ColumnMajor >
class Matrix
{
    public:

    T & operator () ( unsigned const & row, unsigned const & column );

};

template< typename T,
          unsigned rows,
          unsigned columns >
T & Matrix< T, rows, columns, ColumnMajor >::
operator () ( unsigned const & row, unsigned const & column )
{
    return elements[ ( row + ( rows * column ) ) % ( rows * columns ) ];
}

template< typename T,
          unsigned rows,
          unsigned columns >
T & Matrix< T, rows, columns, RowMajor >::
operator () ( unsigned const & row, unsigned const & column )
{
    return elements[ ( ( row * columns ) + column ) % ( rows * columns ) ];
}

Error output:

error C3860: template argument list following class template name must list parameters in the order used in template parameter list
error C2976: 'maths::Matrix<T,rows,columns,storage>' : too few template arguments
error C3860: template argument list following class template name must list parameters in the order used in template parameter list

Following the examples given in other questions it looks like the syntax is correct. Still, the only way I can get this to work is by specialising the class itself (as below), but this means duplicating all of the methods that aren't dependent on the storage format.

enum MatrixStorage
{
    ColumnMajor,
    RowMajor
};

template< typename T,
          unsigned rows,
          unsigned columns,
          MatrixStorage storage = ColumnMajor >
class Matrix;

template< typename T,
          unsigned rows,
          unsigned columns >
class Matrix< T, rows, columns, ColumnMajor >
{
    T & operator () ( unsigned const & row, unsigned const & column );
};

template< typename T,
          unsigned rows,
          unsigned columns >
class Matrix< T, rows, columns, RowMajor >
{
    T & operator () ( unsigned const & row, unsigned const & column );
};
Community
  • 1
  • 1
PeddleSpam
  • 418
  • 3
  • 10
  • 1
    If it interests you, I once spent 40 hours or so trying to templatize operations on a matrix class. In my mind, left-multiplication and right-multiplication were so similar that it made no sense to duplicate the code. If you think about it, this is rather similar to your row/column-majority question. What I seemed to discover is that C++'s otherwise excellent template facilities just weren't designed with this kind of use in mind. It didn't work well. In the end, I gave up and stopped using templates for this purpose. I hope that you have better luck. – thb Jun 08 '12 at 01:22
  • Forty hours ago I might have disagreed with you, although I've seen libraries where templates are used this way. I think the main attraction is you save on memory/runtime overhead. As David suggested it would be possible given a few helper classes to delegate functionality to. – PeddleSpam Jun 08 '12 at 02:19

1 Answers1

3

You cannot partially specialize function templates, which include member functions of a template. The alternative would be creating a template helper class that provides the functionality you need, and then forward the call to the class template from the member functions of your template.

Alternatively, as your use case is quite simple, you can write a helper function (template or not, no need for it to be a full class) that will provide you with the index in the array given the coordinates and the mode.

template <MatrixStorage m>
int index_of( int row, int col, int rows, int columns ) {
    return ( row + ( rows * column ) ) % ( rows * columns );
}
template <>
int index_of<RowMajor>( int row, int col, int rows, int columns ) {
    return ( ( row * columns ) + column ) % ( rows * columns );
}

You can make it non template by taking an extra parameter of type MatrixStorage that will be checked at runtime (simple if)

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • I did something similar with an earlier attempt. It works for the function operator example above, but not in other methods like the assignment operator and copy constructor. In those methods I want to iterate over the array differently depending on the storage format. I could make another helper class for that and pass in pointers to the arrays, but it seems a little messy :/ – PeddleSpam Jun 08 '12 at 01:55
  • In many cases inheritance is abused precisely for this (partial specializations). You can move the common code to a base class and then derive from it and perform the specializations. Also note that for assignment you can make do with a single loop with the range `[0, rows*cols)` which will ignore whether the matrix is is in row major or column major.... – David Rodríguez - dribeas Jun 08 '12 at 04:06
  • @DavidRodríguez-dribeas: Yours is a good solution and I like it (and have upvoted it). However, still, I suspect that the OP has a subtle problem. I suspect that he will need to access his matrix object an entire row or column at a time. If so, then fetching individual elements might just be too slow. – thb Jun 08 '12 at 12:47
  • @David Rodríguez - dribeas: Maybe specialising the class itself is the best solution. What methods there are that don't rely on specialised ones are basic, like `getRows()` and `getColumns()`. If I do find a lot of code duplication later I can move it to a base class. – PeddleSpam Jun 08 '12 at 15:26
  • @PeddleSpam: I am not sure of how many different operations you offer, but there will be duplicated checks for sure (even if `operator()` has a different indexing, verification of the row/col being within limits is shared...) the same probably applies to different suboperations. The alternative of a full class specialization is (in my opinion) the worst alternative. If you are going to do that, remove the template argument and provide separate `RMMatrix` and `CMMatrix` templates... – David Rodríguez - dribeas Jun 08 '12 at 15:50
  • @David Rodríguez - dribeas: I see what you mean, using fully specialised helper methods as per your answer would be a better option. The more I think about it though the more it seems this kind of functionality would only be possible with square matrices. Considering the multiplication operator: `Matrix< T, ?, ?, storage > operator * ( Matrix< T, ?, ?, storage > const & matrix );` The template parameters of the returned object are dependent on those of the parameter and the object being called. If you assume both matrices are square then the returned matrix is the same, otherwise it's unknown. – PeddleSpam Jun 08 '12 at 21:21