Search code examples
c++c++11templatestemplate-specializationclass-template

Different template class realizations but same member functions


I have a template matrix class with some typical member functions such as inverse, determinant, operator*, etc.. And I want to reuse the code for these member functions in template realizations for both fixed- and dynamic size matrices. Is this possible? and if yes, how?

In the code below, like Eigen, I use "-1" for dynamic dimensions. Yes I know I could use a library for this, but for my application, that is not feasible. The standard functionality is not possible due to the nature of the application (CUDA)

Is it possible to make a template class with different member variable size depending on the template parameters? For instance when Dynamic_Rows = -1 and Dynamic_Cols = -1, then the data is just T **data, but otherwise its T data[Rows][Cols].

As of now, I have one template class for dynamic size matrices ("minimal" example below, note that the code is subject to errors/mistakes because I am relatively fresh in "advanced" class templating).

But I want to have a fixed size data member variable in the case of a fixed size matrix instantiation.

template<class T, int Dynamic_Rows, int Dynamic_Cols>
class CMatrix<T, -1, -1>
{
private:
   size_t n_rows, n_cols;
   T** data;

   void allocate_data();
   void deallocate_data();

public:
   CMatrix(const size_t n_rows, const size_t n_cols);
   CMatrix(const CMatrix& other);
   ~CMatrix();
   CMatrix& operator=(const CMatrix& rhs);
   CMatrix exp() const;
};

with for instance the code for the exp() function below as

template <class T, int Dynamic_Rows, int Dynamic_Cols>
CMatrix<T, -1, -1> CMatrix<T, -1, -1>::exp() const
{
   CMatrix<T, -1, -1> result(n_rows, n_cols);
   for (size_t i = 0; i < n_rows; i++)
   {
      for (size_t j = 0; j < n_cols; j++)
      {
         result.data[i][j] = exp(result.data[i][j]);
      }
   }
   return result;
}

The only thing I can think of now to allow for both dynamic and fixed-size matrices is to basically implement another template of the class as

template<class T, size_t Rows, size_t Cols>
class CMatrix<T, Rows, Cols>
{
private:
   size_t n_rows = Rows, n_cols = Cols;
   T data[Rows][Cols];

public:
   CMatrix() {}
   CMatrix(const CMatrix& other);
   CMatrix& operator=(const CMatrix& rhs);
   CMatrix exp() const;
};

using a variadic template

template<class T, int...> class CMatrix;

but then that would just duplicate the code for most of my member functions!


Solution

  • This is a very generic way to do such specializations using CRTP. It doesn;t require that there is a simple single condition that can be used to select an implementation of a data member, and leave the rest unchanged.

    template <class T, class Impl> class MatrixBase {
    
       // data is NOT defined here
       Impl exp() const
       {
            Impl& self = static_cast<Impl&>(*this);             // <-- this is magic
            Impl result = self;                                 // <-- this is magic
            for (size_t i = 0; i < n_rows; i++)
            {
                for (size_t j = 0; j < n_cols; j++)
                {
                    result.data[i][j] = exp(result.data[i][j]); // <-- data is defined in Impl
                }
            }
            return result;
        }
        // other functions that do not depend on actual type of data
    };
    
    template <class T>
    class DynamicMatrix : public MatrixBase<T, DynamicMatrix<T>> {
        T** data;
        // define constructors etc here
    };
    
    template <class T, int Rows, int Cols>
    class StaticMatrix : public MatrixBase<T, StaticMatrix<T, Rows, Cols>> {
        T[Rows][Cols] data;
        // define constructors etc here
    };
    

    Now that you have both StaticMatrix and DynamicMatrix, you can unify them into a single alias template if you wish.

    template <class T, int Rows, int Cols>
    using CMatrix = std::conditional_t <(Rows >= 0 && Cols >= 0),
                                         StaticMatrix<T, Rows, Cols>,
                                         DynamicMatrix<T>
                                       >;