Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Partial specialization for a parent of multiple classes

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;
}
like image 372
Sparky Avatar asked Oct 11 '22 22:10

Sparky


1 Answers

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.

like image 183
Mikael Persson Avatar answered Oct 14 '22 01:10

Mikael Persson