5

I want to use partial specialization for a template class so that all children of that template class will use that specialization. Let me explain it with an example :)

template < typename T, unsigned int rows, unsigned int cols>
class BaseMatrix {...};

This class will have children that specify the structure of the matrix, like sparse,dense,diagonal,..

template < typename T, unsigned int rows, unsigned int cols>
class DiagonalMatrix : public BaseMatrix<T,rows,cols>{..}

Then those classes will have children again that specify the storage : stack arrays, vectors, list, queues,..

template < typename T, unsigned int rows, unsigned int cols>
class StackDiagonalMatrix : public DiagonalMatrix<T, rows, cols> {..}

Then there is a class Matrix, which provides all the mathematical functionality. This template class implements operator+, operator-, etc...

   template <typename T, 
             template<typename, unsigned, unsigned> class MatrixContainer,
             unsigned Rows, 
             unsigned Cols>
    class matrix;

For this last class I want to write specializations like this:

template <typename T,unsigned Rows, unsigned Cols>
class matrix<T, BaseMatrix, Rows, Cols> {};

template <typename T,unsigned Rows, unsigned Cols>
class matrix<T, DiagonalMatrix, Rows, Cols>  {};

But when I write a StackDiagonalMatrix which inherits from DiagonalMatrix, it does not find the specialization for DiagonalMatrix -- it does not find a specialization at all actually.

error: aggregate ‘matrix<int, StackDenseMatrix, 3u, 2u> matrix’ has incomplete type and cannot be defined

Now is there a solution for this problem? Can you write a specialization for a parent of several template classes?

Many thanks!

Full Source that is involved :

    template <typename T, unsigned int rows, unsigned int cols>
    class BaseMatrix {
    protected:
        BaseMatrix(){};
        static const unsigned rowSize = rows;
        static const unsigned colSize = cols;
    };

    template <typename T, unsigned int rows, unsigned int cols>
    class DenseMatrix : public BaseMatrix<T, rows, cols> {
    protected:
        DenseMatrix(){};

    };

    template <typename T, unsigned int rows, unsigned int cols>
    class StackDenseMatrix : public DenseMatrix<T, rows, cols> {

    public:
        typedef T                                           value_type;

    private:
        value_type                                          grid[rows][cols];
        StackDenseMatrix();
    };

    template<typename value_type, unsigned int rows, unsigned int cols>
    StackDenseMatrix<value_type, rows,cols>::StackDenseMatrix () {
        for (unsigned int i = 0; i < this->rowSize; i++) {
            for (unsigned int j = 0; j < this->colSize; j++) {
                grid[i][j] = 0;
            }
        }
    }

    template <typename T, template<typename, unsigned, unsigned> class MatrixContainer ,unsigned Rows, unsigned Cols>
    class matrix;

    template <typename T,unsigned Rows, unsigned Cols>
    class matrix<T,BaseMatrix, Rows, Cols> {
         matrix(){};
};

int main () {
    matrix<int, StackDenseMatrix, 3, 2> matrix;
    return 0;
}
greatwolf
  • 20,287
  • 13
  • 71
  • 105
Sparky
  • 717
  • 1
  • 7
  • 17
  • Show the error.. and the code of `matrix`.. and that of others as well. – Nawaz May 27 '11 at 16:10
  • @Nawaz : Normally you now have all the code that is necessary, if it can compile without changing main or design, then my problem is fixed :) – Sparky May 27 '11 at 17:01

2 Answers2

4

Inheritance does not apply to template specializations. When you invoke matrix<int,StackDenseMatrix,3,2> matrix;, it will select the general template for matrix, because the second argument is StackDenseMatrix, not BaseMatrix. Even though those classes are related through inheritance, it doesn't make any difference, they are not of the exact same type, so the compiler won't select the specialization of matrix.

To solve your problem, I don't think inheritance will do you any good in this case. In generic programming, the more appropriate tools are type-traits, policies and concepts. In this case, you should be able to apply some type-traits to achieve similar goals. One trick I like to use is the default template argument that depends on a previous template argument, and then do a partial specialization. As follows for example:

enum MatrixStorage {
  DenseMatrix,
  SparseMatrix
};

enum MatrixStructure {
  GeneralMatrix,
  SquareMatrix,
  DiagonalMatrix  //, ...
};

template <typename T, unsigned Rows, unsigned Cols>
class StackDenseMatrix {
  public: 
    typedef T value_type;
    static const MatrixStorage Storage = DenseMatrix;
    static const MatrixStructure Structure = GeneralMatrix;
  //..
};

//General template with default arguments:
template <typename T, 
          template <typename, unsigned, unsigned> class MatrixContainer,
          unsigned Rows, unsigned Cols, 
          MatrixStorage Storage = MatrixContainer<T,Rows,Cols>::Storage,
          MatrixStructure Structure = MatrixContainer<T,Rows,Cols>::Structure>
class matrix;

//Specialization with given arguments:
template <typename T, 
          template <typename, unsigned, unsigned> class MatrixContainer,
          unsigned Rows, unsigned Cols>
class matrix<T,MatrixContainer,Rows,Cols,DenseMatrix,GeneralMatrix> {
  //implementation of matrix for when the container is dense and has general structure...
};

int main() {
  matrix<int,StackDenseMatrix,3,2> M; //no extra arguments, and the right specialization will be selected based on the traits of StackDenseMatrix.
  return 0;
};

I have my own matrix library that heavily relies on template meta-programming and generic programming techniques along the lines of the above example to provide special implementation of matrix operations for different type of matrix structures and storage, and it works very well this way. I used to use inheritance for the different matrix types, but I have now switched to relying only on type-traits, concepts, policies and Sfinae switches, that is a much more practical solution.

Mikael Persson
  • 18,174
  • 6
  • 36
  • 52
  • We are using your solution, accept for that we dropped the enum MatrixStorage. It works nice when working with one MatrixContainer but there is a problem if you use different MatrixContainers. I can not add a StackDenseMatrix and a HeapDenseMatrix with following declaration : Matrix operator+ (const Matrix How can I fix that ? – Sparky May 28 '11 at 02:11
  • First of all, all such operators should be friend functions, not member functions, that is important for overload resolution. If you want to add a StackDenseMatrix with a HeapDenseMatrix, you need to make the operator templated on both storage types (as in `template matrix operator*(const matrix&, const matrix&);`. I have an entire matrix library like this, PM me if interested in it. – Mikael Persson May 28 '11 at 02:32
  • ok, with friends then. Thanks for the offer about your matrix lib, but I will try to do it myself first :) – Sparky May 28 '11 at 08:13
  • I would rather do it myself, but it keeps complaining. So it would be nice to have an example as you suggesting. If you can mail it to william_hamperton@hotmail.com that would probably help me a lot. Thank you very much. – Sparky May 28 '11 at 08:26
0

To solve your problem, you can use policy-based design. You can make for instance policy classes of Storage and Shape.

class Diagonal  {
public:
    // the default storage facility of a Diagonal matrix
    typedef Stack default_storage;
};

template <typename T, typename Shape = DefaultShape, typename Storage = DefaultStorage, unsigned Row, unsigned Col>
class Matrix : public Storage, Shape {   // policy classes Storage and Shape
public:
    template <typename T1, typename Shape1, typename Storage1, unsigned Row1, unsigned Col1>
    friend Matrix<T1,Shape1,Storage1,Row1,Col1>& operator += (Matrix<T1,Shape1,Storage1,Row1,Col1> matrix1,Matrix<T,Shape,Storage,Row,Col> matrix2);

    template <typename T1, typename Shape1, typename Storage1, unsigned Row1, unsigned Col1>
    friend Matrix<T1,Diagonal,Storage1,Row1,Col1>& operator += (Matrix<T1,Diagonal,Storage1,Row1,Col1> matrix1,Matrix<T,Diagonal,Storage,Row,Col> matrix2);

    template <typename T1, typename Shape1, typename Storage1, unsigned Row1, unsigned Col1>
    friend Matrix<T,Shape,Storage,Row,Col>& operator + (Matrix<T,Shape,Storage,Row,Col> matrix1, Matrix<T,Shape,Storage,Row,Col> matrix2);

    template <typename T1, typename Shape1, typename Storage1, unsigned Row1, unsigned Col1>
    friend Matrix<T,Diagonal,Storage,Row,Col>& operator + (Matrix<T,Diagonal,Storage,Row,Col> matrix1, Matrix<T,Diagonal,Storage,Row,Col> matrix2);

// general template function
template <typename T, typename Shape, typename Storage, unsigned Row, unsigned Col>
Matrix<T,Shape,Storage,Row,Col>& operator + (Matrix<T,Shape,Storage,Row,Col> matrix1, Matrix<T,Shape,Storage,Row,Col> matrix2) {
    Matrix<T,Shape,Storage,Row,Col>* result = new Matrix<T,Shape,Storage,Row,Col>();
    for (unsigned i = 0; i < matrix1.getRowSize(); i++) {           // getRowSize is a member function of policy class Storage
        for (unsigned j = 0; j < matrix1.getRowSize(); j++) {
            (*result)(i,j) = matrix1.getValue(i,j) + matrix2.getValue(i,j);
        }
    }
    return *result;
}

// overloaded template function
template <typename T, typename Shape, typename Storage, unsigned Row, unsigned Col>
Matrix<T,Diagonal,Storage,Row,Col>& operator + (Matrix<T,Diagonal,Storage,Row,Col> matrix1, Matrix<T,Diagonal,Storage,Row,Col> matrix2) {
    Matrix<T,Shape,Storage,Row,Col>* result = new Matrix<T,Shape,Storage,Row,Col>();
    for (unsigned i = 0; i < matrix1.getRowSize(); i++) {
        (*result)(i,i) = matrix1.getValue(i,i) + matrix2.getValue(i,i);
    }
    return *result;
}

// general template function
template <typename T, typename Shape, typename Storage, unsigned Row, unsigned Col>
Matrix<T,Shape,Storage,Row,Col>& operator += (Matrix<T,Shape,Storage,Row,Col> matrix1,Matrix<T,Shape,Storage,Row,Col> matrix2)  {
    Matrix<T,Shape,Storage,Row,Col>* result = new Matrix<T,Shape,Storage,Row,Col>(matrix1); // copy constructor
    for (unsigned i = 0; i < matrix1.getRowSize(); i++) {
        for (unsigned j = 0; j < matrix1.getRowSize(); j++) {
            (*result)(i,j) = matrix1.getValue(i,j) + matrix2.getValue(i,j);
        }
    }
    return *result;
}

// overloaded template function
template <typename T, typename Shape, typename Storage, unsigned Row, unsigned Col>
Matrix<T,Diagonal,Storage,Row,Col>& operator += (Matrix<T,Diagonal,Storage,Row,Col> matrix1,Matrix<T,Diagonal,Storage,Row,Col> matrix2) {
    Matrix<T,Shape,Storage,Row,Col>* result = new Matrix<T,Shape,Storage,Row,Col>(matrix1); // copy constructor
    for (unsigned i = 0; i < matrix1.getRowSize(); i++) {
        (*result)(i,i) = matrix1.getValue(i,i) + matrix2.getValue(i,i);
    }
    return *result;
}

The major advantage of using policy-based design here is that your users can easily provide their own storage facilities and shape operations. All you need to do is give them a clear interface so that they know how they can make their own storage facilities and shape operations.

Glenn
  • 125
  • 1
  • 5