2009-11-16 12 views
7

I actualmente tiene una jerarquía de clases comoC++ implícita de instancias de plantilla

MatrixBase -> DenseMatrix 
      -> (other types of matrices) 
      -> MatrixView -> TransposeView 
         -> DiagonalView 
         -> (other specialized views of matrices) 

MatrixBase es una clase abstracta que obliga a los ejecutores para definir operador() (int, int) y tales cosas; representa 2 matrices dimensionales de números. MatrixView representa una forma (posiblemente mutable) de mirar una matriz, como transponerla o tomar una submatriz. El punto de MatrixView es ser capaz de decir algo como

Scale(Diagonal(A), 2.0) 

donde Diagonal devuelve un objeto DiagonalView que es una especie de adaptador ligero.

Ahora aquí está la (s) pregunta (s). Utilizaré una operación de matriz muy simple como ejemplo. Quiero definir una función como

template <class T> 
void Scale(MatrixBase<T> &A, const T &scale_factor); 

que hace lo más obvio que su nombre sugiere. Quiero poder pasar una matriz que no sea fiel a la bondad o una instancia de una subclase de MatrixView. El prototipo de lo escrito anteriormente no funciona para declaraciones como

Scale(Diagonal(A), 2.0); 

porque el objeto devuelto por DiagonalViewDiagonal es un temporal, y Scale toma una referencia no constante, que no puede aceptar un temporal. ¿Hay alguna manera de hacer funcionar esto? Traté de usar SFINAE, pero no lo entiendo muy bien, y no estoy seguro de si eso resolvería el problema. Para mí es importante que se puedan llamar a estas funciones con plantilla sin proporcionar una lista explícita de argumentos de plantilla (quiero una instanciación implícita). Idealmente, la declaración anterior podría funcionar como está escrita.


Editar: (pregunta de seguimiento)

Como OSE respondió a continuación acerca de las referencias rvalue y temporales, ¿Hay una manera de definir dos versiones de la escala, uno que toma una referencia rvalue no constante para no vistas, y una que toma una vista de valor por paso? El problema es diferenciar estos dos en el momento de la compilación de forma tal que funcione la instanciación implícita.


actualización

he cambiado la jerarquía de clases a

ReadableMatrix 
WritableMatrix : public ReadableMatrix 
WritableMatrixView 
DenseMatrix : public WritableMatrix 
DiagonalView : public WritableMatrixView 

La razón WritableMatrixView es distinta de WritableMatrix es que la vista se debe pasar por alrededor de referencia constante, mientras que la las matrices mismas deben pasarse por non-const ref, por lo que las funciones del miembro de acceso tienen diferente const-ness. Ahora funciones como escala pueden ser definidos como

template <class T> 
void Scale(const WritableMatrixView<T> &A, const T &scale_factor); 
template <class T> 
void Scale(WritableMatrix<T> &A, const T &scale_factor){ 
    Scale(WritableMatrixViewAdapter<T>(A), scale_factor); 
} 

Nota que hay dos versiones, una para una vista const, y una versión no const para matrices reales. Esto significa que para funciones como Mult(A, B, C), necesitaré 8 sobrecargas, pero al menos funciona. Lo que no funciona, sin embargo, es usar estas funciones dentro de otras funciones.Verá, cada clase similar a View contiene un miembro View de lo que está viendo; por ejemplo, en la expresión Diagonal(SubMatrix(A)), la función Diagonal devuelve un objeto de tipo DiagonalView<SubMatrixView<T> >, que necesita conocer el tipo totalmente derivado de A. Ahora, supongamos que dentro de Scale llamo a alguna otra función como esta, que toma una vista base o una referencia matricial. Eso no funcionaría porque la construcción de los necesarios View requiere el tipo derivado del argumento de Scale; información que no tiene. Todavía estoy trabajando para encontrar una solución a esto.


actualización

he utilizado lo que es efectivamente una versión de cosecha de enable_if de Boost para seleccionar entre dos versiones diferentes de una función como Scale. Todo se reduce a etiquetar todas mis clases de matrix y view con etiquetas typedef adicionales que indican si son legibles y escribibles, y view o non-view. Al final, todavía necesito 2^N sobrecargas, pero ahora N es solo el número de argumentos no const. Para obtener el resultado final, consulte here (es poco probable que se vuelva a modernizar de nuevo).

+0

¿Por qué razón Scale() no acepta el parámetro por referencia de referencia? – Naveen

+0

La escala debe realmente escalar su argumento, por lo tanto no puede ser const. –

+1

¿Por qué la escala modifica la matriz temporal? –

Respuesta

1

Una manera fácil de arreglar esto sería usar boost::shared_ptr< MatrixBase<T> > en lugar de una referencia.

0

Puede ser, usted debe usar const. ?

template <class T> 
void Scale(const MatrixBase<T> &A, const T &scale_factor); 
+0

No, no debería ser const, Scale modificará A. –

+0

@Victor: Si la escala modifica A, no debe pasarle el temporal. Sin embargo, si * realmente * desea hacerlo y si su plataforma es Windows, puede compilar el código como una referencia no constante con Visual Studio. Pero no estoy seguro de lo que sucederá si intenta modificar el objeto pasado en ese caso. – Naveen

0

están limitando el tipo del primer argumento de la escala, pero puede dejar que la cifra compilador qué tipo sería adecuado por sí solo, así:

template <class M,class T> 
void Scale(M A, const T &scale_factor); 
+0

Sí, podría hacer eso, pero eso lleva a otro problema. También tengo VectorBase, Vector, VectorView, etc. Y Scale debe funcionar de forma diferente para los vectores que para las matrices. Quizás pueda sugerir cómo incorporar esta complicación adicional. –

+0

Problemas adicionales: A no se puede pasar por valor; podría ser una gran copia. En segundo lugar, si se transfiere por referencia, volvemos al problema en el que las matrices ordinarias se pasan in fine, pero las vistas son temporales en la pila, por lo que no funcionaría. –

0

No utilice referencias, pasar por valor

Deje que la elisión de copia le haga la optimización, si es necesario.

7

Esto no tiene nada que ver con las plantillas. Su ejemplo

Scale(Diagonal(A), 2.0); 

podría generalizarse a

f(g(v),c); 

En C++ 03, esto requiere que el primer parámetro a f() a cualquiera que pasar por copia o por const referencia. El motivo es que g() devuelve un valor temporal, un valor r. Sin embargo, los valores r solo se unen a las referencias const, pero no a las referencias no const. Esto es independiente de si las plantillas, SFINAE, TMP o whatnot están involucradas. Es solo la forma en que el lenguaje (actualmente) es.

También hay un razonamiento detrás de eso: si g() devuelve un temporal, y f() modifica ese temporal, entonces nadie tiene la posibilidad de "ver" el temporal modificado. Por lo tanto, la modificación se hace en vano y es probable que todo sea un error.

Por lo que he entendido, en su caso, el resultado de g() es un temporal que es una vista en algún otro objeto (v), por lo que la modificación se modificaría v. Pero si ese es el caso, en C++ actual, el resultado de g() debe ser const (por lo que se puede vincular a una referencia const o debe copiarse. Dado que const "me huele mal", hacer que esa vista sea barata de copiar probablemente sea lo mejor.

Sin embargo, hay más en esto. C++ 1x presentará lo que se llama referencias rvalue. Lo que conocemos como "referencias" se dividirá en referencias de referencia lval o referencias rvalue. Podrá hacer que las funciones tomen referencias de valores e incluso sobrecargas en función de "l/rvalue-ness". Esto se pensó para permitir a los diseñadores de clases sobrecargar el copiador y la asignación para el lado derecho de validación y hacer que "roben" los valores del lado derecho, de modo que la copia de los valores sea más económica. Pero probablemente podría usarlo para tener Scale tomar un valor r y modificar eso.

Desafortunadamente, es muy probable que su compilador aún no admita las referencias rvalue.


Editar (pregunta de seguimiento):

No puede sobrecargar f(T) con f(T&) para lograr lo que quiere. Mientras que solo el primero se usará para valores r, lvalues ​​puede enlazarse a cualquiera de los argumentos igualmente bien, por lo que invocar ese f con un lvalue es ambiguo y da como resultado un error en tiempo de compilación.

Sin embargo, lo que es malo en tener una sobrecarga de DiagonalView:

template <class T> 
void Scale(MatrixBase<T> &matrix, const T &scale_factor); 

template <class T> 
void Scale(DiagonalView<T> view, const T &scale_factor); 

¿Hay algo que me falta?


Otra edición:

que necesitaría un ridículamente gran número de sobrecargas entonces, puesto que en la actualidad hay más de 5 puntos de vista, y hay varias docenas de funciones como escala.

Luego, debe agrupar los tipos que se pueden manejar de la misma manera. Podrías usar algunas cosas simples de plantilla y meta para hacer la agrupación. De la parte superior de mi cabeza:

template<bool B> 
struct boolean { enum { result = B }; }; 

template< typename T > 
class some_matrix { 
    public: 
    typedef boolean<false> is_view; 
    // ... 
}; 

template< typename T > 
class some_view { 
    public: 
    typedef boolean<true> is_view; 
    // ... 
}; 

namespace detail { 
    template< template<typename> class Matrix, typename T > 
    void Scale(Matrix<T>& matrix, const T& scale_factor, boolean<true>) 
    { 
    /* scaling a matrix*/ 
    } 
    template< template<typename> class Matrix, typename T > 
    void Scale(View<T>& matrix, const T& scale_factor, boolean<true>) 
    { 
    /* scaling a view */ 
    } 
} 

template< template<typename> class Matrix, typename T > 
inline void Scale(Matrix<T>& matrix, const T& scale_factor) 
{ 
    detail::Scale(matrix, scale_factor, typename Matrix<T>::is_view()); 
} 

Este particular configuración/agrupación podría no ajustarse exactamente a sus necesidades, pero se puede configurar algo como esto de manera que se adapten por sí mismo.

+0

Entiendo este punto ahora completamente, pero he agregado otra pregunta de seguimiento. –

+0

Necesitaría una cantidad ridículamente grande de sobrecargas, dado que actualmente hay más de 5 vistas, y hay varias docenas de funciones, como Scale. –

Cuestiones relacionadas