开发者

C++11 templates, determining return type

I am building a matrix library and I am trying to use the policy-based design. So my base classes are classes that provide a storage method and some access functions. I also have a function matrix which provides the mathematical functions. This works great, but there is a major problem with the operator* because of the return type. I will explain it with some code.

Base class that provides a stack storage开发者_如何学运维 :

template < typename T, unsigned int rows, unsigned int cols>
class denseStackMatrix {
public:
    typedef T value_type;

private:
    value_type grid[rows][cols];
    const unsigned int rowSize;
    const unsigned int colSize;

Then I have my matrix class which provides mathematical functionality :

template <typename MatrixContainer >
class matrix : public MatrixContainer {
public:
    typedef MatrixContainer Mcontainer;

    matrix<Mcontainer>& operator +(const matrix<Mcontainer>&);
    matrix<Mcontainer>& operator *(const matrix<Mcontainer>&);

operator+ always works, operator* only works for square matrix. So we still need one for all matrices. And that's were it goes wrong. I have already tried few things, but nothings works. I look for something like this, with the help of c++0x (usage of c++0x is not a requirement) you shall notice the "???" :)

friend auto operator * (const matrix<T1>& matrix1, const matrix<T2>& matrix2)
-> decltype(matrix<???>);

An example of the problem

matrix<denseStackMatrix<int,3,2> > matrix1;
matrix<denseStackMatrix<int,2,4> > matrix2;
matrix<denseStackMatrix<int,3,4> > matrix3 = matrix1 * matrix2;

Here it will complain about the type, because it does not match any of the two parameter types. But the compiler needs to know the type at compile-time and I do not know how to provide it.

I know there are other options for the design, but I am really looking for a solution for this scenario..

Thank you !


Picking up on the idea of @hammar, but with partial specialization to allow the normal syntax like the question shows:

template<class MatrixContainer>
class matrix;

template<
  template<class,int,int> class MatrixContainer,
  class T, int rows, int cols
>
class matrix< MatrixContainer<T,rows,cols> >{
  typedef MatrixContainer<T,rows,cols> Mcontainer;
  typedef matrix<Mcontainer> this_type;
  static int const MyRows = rows;
  static int const MyCols = cols;

public:
  template<int OtherCols>
  matrix<MatrixContainer<T,MyRows,OtherColls> > operator*(matrix<MatrixContainer<T,MyCols,OtherCols> > const& other){
    typedef matrix<MatrixContainer<T,MyCols,OtherCols> > other_type;
    typedef matrix<MatrixContainer<T,MyRows,OtherCols> > result_type;
    // ...
  }
};

Edit: As you said in your comment, you can also use this to create a matrix that doesn't use a MatrixContainer which has row and column size as template parameters:

template<
  template<class> class MatrixContainer,
  class T
>
class matrix< MatrixContainer<T> >{
  typedef MatrixContainer<T> Mcontainer;
  typedef matrix<Mcontainer> this_type;

public:
  // normal matrix multiplication, return type is not a problem
  this_type operator*(this_type const& other){
    // ensure correct row and column sizes, e.g. with assert
  }

  // multiply dynamic matrix with stack-based one:
  template<
    template<class,int,int> class OtherContainer,
    int Rows, int Cols
  >
  this_type operator*(matrix<OtherContainer<T,Rows,Cols> > const& other){
    // ensure correct row and column sizes, e.g. with assert
  }
};

Usage:

// stack-based example
matrix<DenseStackMatrix<int,3,2> > m1;
matrix<DenseStackMatrix<int,2,4> > m2;
matrix<DenseStackMatrix<int,3,4> > m3 = m1 * m2;

// heap-based example
matrix<DenseHeapMatrix<int> > m1(3,2);
matrix<DenseHeapMatrix<int> > m2(2,4);
matrix<DenseHeapMatrix<int> > m3 = m1 * m2;


How about changing MatrixContainer to be a template template parameter?

template <class T, int Rows, int Cols>
class DenseStackMatrix {
public:
    typedef T value_type;

private:
    value_type grid[Rows][Cols];
};

template <class T, int Rows, int Cols, template<class, int, int> class MatrixContainer>
class Matrix : public MatrixContainer<T, Rows, Cols> {
public:
    template <int ResultCols>
    Matrix<T, Rows, ResultCols, MatrixContainer> & operator*(const Matrix<T, Cols, ResultCols, MatrixContainer> &);
};

int main() {
    Matrix<int, 3, 2, DenseStackMatrix> matrix1;
    Matrix<int, 2, 4, DenseStackMatrix> matrix2;
    Matrix<int, 3, 4, DenseStackMatrix> matrix3 = matrix1 * matrix2;
}

This way you not only get compile time dimensions checking, but you can also extend this to allow multiplications between matrices of different container types.


Just because I worked on it before finding all the answers here:

template <typename T, unsigned int M, unsigned int N>
struct Matrix
{
};

template <typename T, unsigned int M, unsigned int MN, unsigned int N>
Matrix<T, M, N> operator*(Matrix<T, M, MN> const & lhs, Matrix<T, MN, N> const & rhs)
{
    return Matrix<T, M, N>();
}

int main()
{
    Matrix<int, 3, 4> prod = Matrix<int, 3, 2>() * Matrix<int, 2, 4>();

    // Fails to compile as desired
    // g++ gives:
    //matrix.cpp: In function 'int main()':
    //matrix.cpp:20: error: no match for 'operator*' in 'Matrix<int, 3u, 2u>() * Matrix<int, 3u, 4u>()'
    Matrix<int, 3, 4> prod1 = Matrix<int, 3, 2>() * Matrix<int, 3, 4>();
}

This solution may not fit your design pattern, but uses a free function implementation of operator* to infer (and check) the template arguments, resulting in a compile-time error if the constraints of matrix multiply are not met.


just a random idea, what if you include in you base class a way to get a container of the same type but of different size ? something on the lines of:

template<typename T, unsigned int Rows, unsigned int Cols>
class denseStackMatrix {
public:
  static const int rows = Rows;
  static const int cols = Cols;

  template<unsigned int R, unsigned int C>
  struct resize {
    typedef denseStackMatrix<T, R, C> type;
  };

  // ....
}

and then you can do

template <typename MatrixContainer >
class matrix : public MatrixContainer {
  using MatrixContainer::resize;

public:

  template<typename RHSMcontainer>
  matrix<typename resize<rows, RHSMcontainer::cols>::type>
  operator *(const matrix<RHSMcontainer>&)
  {
    static_assert(cols == RHSMcontainer::rows, "incompatible sizes");
    // ...
  }

  // ....
}

btw, I'm not sure I got the scoping of MatrixContainer::resize right ...

my 2c


In your post I read you would like to use policy-based design. In that case you first need to define your policy classes. Thus, you first need to decide which classes you want that your users can provide by their own. Which classes you take, is up to you. You can for use for instances the policies Storage and Shape.

You can make something like

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

template <typename T, typename Shape = Dense, typename Storage = Stack, 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;
}

As you can see, you can also easily add two different Matrix types now. You just need to overload the general template function. An advantage of using policies is that your users can now for instance easily provide their own storage facilities.

One last note. As you can use C++0x you can also make some shortcuts for your users. You can do for instance things like

template<typename T, unsigned Row, unsigned Col>
using DenseStackMatrix = Matrix<T, Dense, Stack, Row, Col>;

template<typename T>
using DenseHeapMatrix = Matrix<T, Dense, Heap, 0, 0>;
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜