2011-05-26 10 views
14

Estoy construyendo una biblioteca de matriz y estoy tratando de usar el policy-based design. Entonces mis clases base son clases que proporcionan un método de almacenamiento y algunas funciones de acceso . También tengo una matriz de funciones que proporciona las funciones matemáticas. Esto funciona muy bien, pero hay un problema importante con el operador * debido al tipo de devolución. Lo explicaré con un código.plantillas de C++ 11, determinar el tipo de devolución

clase base que proporciona una pila de almacenamiento:

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; 

entonces tengo mi clase de matriz que proporciona una funcionalidad matemática:

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

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

operator+ siempre funciona, operator* sólo funciona para matriz cuadrada. Así que todavía necesitamos una para todas las matrices. Y eso es si va mal. Ya he probado algunas cosas, pero nada funciona. Busco algo como esto, con la ayuda de C++ 0x (uso de C++ 0x no es un requisito) notará el "???" :)

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

Un ejemplo del problema

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

Aquí se quejará sobre el tipo, ya que no coincide con ninguno de los dos tipos de parámetros. Pero el compilador necesita saber el tipo en tiempo de compilación y no sé cómo proporcionarlo.

Sé que hay otras opciones para el diseño, pero estoy realmente buscando una solución para este escenario ..

Gracias!

+0

@ildjarn, ¡no haga ninja-edit justo delante de mí! – Xeo

+0

@Xeo: D-; (yay límite de caracteres) – ildjarn

+0

@Beremboy: Uhm .. ¿cuál es exactamente tu problema? Creo que debes aclarar. – Xeo

Respuesta

5

Retomando la idea de @hammar, pero con la especialización parcial para permitir la sintaxis normal como muestra la pregunta:

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; 
    // ... 
    } 
}; 

Editar: Como usted dijo en su comentario, también puede utilizar este para crear una matriz que no utiliza un MatrixContainer que tiene fila y tamaño de columna como parámetros de plantilla:

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 
    } 
}; 

Uso:

// 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; 
+0

@ Xeo/@ Hammar: ¡Realmente me gustan las dos soluciones! Ahora, la respuesta más correcta en realidad es la de Xeo porque proporciona el diseño que pedí ... Pero tal vez pase a la idea de Hammar. ¿Qué diseño es el mejor, piensas (Xeo, Hammar)? – Sparky

+0

@Beremboy: Ambos son iguales, solo que tienen una sintaxis diferente. :) A saber, permito la sintaxis "más natural". También puede aceptar una respuesta pero usar otra. Aceptaría la respuesta de @hammars, ya que amplié su idea y solo agregué la especialización parcial, así que el crédito va para él. – Xeo

+0

@Xeo Eso es amable de tu parte :) Pero, ¿ambos diseños son realmente iguales? Creo que su diseño nos permite crear un MatrixContainer sin argumentos de plantilla row en col, ¿o estoy equivocado? Y Hammar siempre espera información de filas y filas en los argumentos de la plantilla, entonces ¿siempre tiene la verificación del tiempo de compilación? – Sparky

3

¿Qué tal si cambia MatrixContainer para ser un parámetro de plantilla de plantilla?

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; 
} 

De esta manera no sólo se les compilar las dimensiones de tiempo de cheques, pero también se puede extender esto para permitir multiplicaciones entre las matrices de diferentes tipos de contenedores.

+0

Creo que mezclar los tipos de contenedores es malo. Además, te estás perdiendo que las filas de la 2da matriz no necesitan ser las columnas en la primera matriz, puede ser completamente diferente. – Xeo

+1

@ Xeo: El producto de una matriz m × p por una matriz p × n es una matriz m × n. Las dimensiones internas (cols de la primera matriz, filas de segundos) deben ser iguales. Son las columnas de la segunda matriz las que pueden ser cualquier cosa. – hammar

+0

Nuestra implementación basada en el montón no requiere el tamaño de fila de columna. Porque no lo necesita y lo mantiene flexible para tal vez cambiar el tamaño? Aunque una matriz en realidad no necesita un cambio de tamaño, supongo ... ¡Quizás necesitemos que cada MatrixContainer tenga su tamaño declarado en tiempo de compilación! Pero, ¿también es posible sin eso? – Sparky

2

El hecho de que trabajé en él antes de encontrar todas las respuestas aquí:

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>(); 
} 

Esta solución puede no adaptarse a su patrón de diseño, sino que utiliza una implementación de la función libre de operator* inferir (y controlar) la argumentos de plantilla, lo que da como resultado un error de tiempo de compilación si no se cumplen las restricciones de la multiplicación de matriz.

0

solo una idea al azar, ¿qué pasa si incluyes en tu clase base una forma de obtener un contenedor del mismo tipo pero de diferente tamaño? algo en la línea de:

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; 
    }; 

    // .... 
} 

y luego se puede hacer

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"); 
    // ... 
    } 

    // .... 
} 

por cierto, no estoy seguro de que tengo la determinación del alcance de MatrixContainer :: cambiar el tamaño de la derecha ...

mi 2c

0

En su publicación que he leído, le gustaría utilizar un diseño basado en políticas. En ese caso, primero necesita definir sus clases de política. Por lo tanto, primero debe decidir qué clases desea que sus usuarios puedan proporcionar por su cuenta. Qué clases tomas, depende de ti. Puede usar para instancias las políticas Almacenamiento y Forma.

Usted puede hacer algo como

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; 
} 

Como se puede ver, también se puede añadir fácilmente dos tipos de matriz diferentes ahora. Solo necesita sobrecargar la función de plantilla general. Una ventaja del uso de políticas es que sus usuarios ahora pueden, por ejemplo, proporcionar fácilmente sus propias instalaciones de almacenamiento.

Una última nota. Como puede usar C++ 0x, también puede hacer algunos accesos directos para sus usuarios. Puede hacer, por ejemplo, cosas como

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>; 
Cuestiones relacionadas