2009-11-16 6 views
7

Al momento ho una gerarchia di classi comeC++ implicito modello di istanza

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

MatrixBase è una classe astratta che costringe gli implementatori di definire operator() (int, int) e cose del genere; rappresenta 2 matrici dimensionali di numeri. MatrixView rappresenta un modo (possibilmente mutevole) di guardare una matrice, come trasponendola o prendendo una sottomatrice. Il punto di MatrixView è quello di essere in grado di dire qualcosa di simile

Scale(Diagonal(A), 2.0) 

dove Diagonal restituisce un oggetto DiagonalView che è una sorta di adattatore leggero.

Ora ecco la domanda (s). Userò un'operazione di matrice molto semplice come esempio. Voglio definire una funzione come

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

che fa la cosa ovvia che suggerisce il nome. Voglio essere in grado di passare in una matrice non view onestamente buona o in un'istanza di una sottoclasse di MatrixView. Il prototipo, come scritto sopra non funziona per affermazioni come

Scale(Diagonal(A), 2.0); 

perché l'oggetto restituito da DiagonalViewDiagonal è una temporanea e Scale prende un riferimento non-const, che non può accettare una temporanea. C'è un modo per farlo funzionare? Ho provato a usare SFINAE, ma non lo capisco molto bene, e non sono sicuro che ciò risolva il problema. Per me è importante che queste funzioni basate su modelli possano essere chiamate senza fornire un elenco di argomenti template esplicito (voglio istanziazione implicita). Idealmente la dichiarazione di cui sopra potrebbe funzionare come scritto.


Edit: (domanda followup)

Come SBI ha risposto di seguito sui riferimenti rvalue e provvisori, C'è un modo per definire due versioni di Scala, uno che prende un riferimento rvalue non-const per non-view e uno che ha una visualizzazione pass-by-value? Il problema è di distinguere tra questi due al momento della compilazione in modo tale che l'istanziazione implicita funzioni.


Aggiornamento

ho cambiato la gerarchia delle classi per

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

La ragione WritableMatrixView è distinto da WritableMatrix è che la visione deve essere passato in giro per riferimento const, mentre il le matrici stesse devono essere passate da non-const ref, quindi le funzioni dei membri di accesso hanno costanti diverse. Ora funzioni come Scala possono essere definiti come

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 che ci sono due versioni, una per una vista const, e una versione non-const per le matrici reali. Questo significa che per funzioni come Mult(A, B, C), avrò bisogno di 8 sovraccarichi, ma almeno funziona. Ciò che non funziona, tuttavia, sta usando queste funzioni all'interno di altre funzioni.Vedete, ogni classe View-like contiene un membro View di ciò che sta guardando; ad esempio nell'espressione Diagonal(SubMatrix(A)), la funzione Diagonal restituisce un oggetto di tipo DiagonalView<SubMatrixView<T> >, che deve conoscere il tipo completamente derivato di A. Ora, supponiamo che all'interno di Scale io chiamo qualche altra funzione come questa, che richiede una vista di base o un riferimento di matrice. Ciò fallirebbe perché la costruzione del necessario View richiede il tipo derivato dell'argomento di Scala; informazioni che non ha. Sto ancora lavorando per trovare una soluzione a questo.


Aggiornamento

ho usato ciò che è effettivamente una versione home-grown di enable_if di Boost di scegliere tra due diverse versioni di una funzione come Scale. Si riduce all'etichettatura di tutte le mie matrici e visualizza le classi con tag typedef aggiuntivi che indicano se sono leggibili e scrivibili e visualizza o non visualizza. Alla fine, ho ancora bisogno di 2^N overload, ma ora N è solo il numero di argomenti non-const. Per il risultato finale, vedere lo here (è improbabile che venga seriamente rinnovato).

+0

un motivo particolare per cui Scale() non accetta parametri per riferimento const? – Naveen

+0

La scala deve effettivamente ridimensionare il suo argomento, quindi non può essere const. –

+1

Perché la scala modifica la matrice temporanea? –

risposta

1

Un modo semplice per risolvere questo problema è utilizzare boost::shared_ptr< MatrixBase<T> > anziché un riferimento.

0

Potrebbe essere, è necessario utilizzare const. ?

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

No, non dovrebbe essere const, Scala modificherà A. –

+0

@Victor: se la scala modifica A, non è necessario passare temporaneamente ad essa. Tuttavia, se * lo vuoi * realmente e se la tua piattaforma è Windows, puoi compilare il codice come riferimento non const con Visual Studio. Ma non sono sicuro di cosa succederà se si tenta di modificare l'oggetto passato in quel caso. – Naveen

0

si stanno limitando il tipo del primo argomento di scala, ma si può lasciare che la figura del compilatore che tipo sarebbe opportuno da solo, in questo modo:

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

Sì, potrei farlo, ma questo porta ad un altro problema. Ho anche VectorBase, Vector, VectorView, ecc. E Scale deve funzionare in modo diverso per i vettori che per le matrici. Forse puoi suggerire come integrare questa ulteriore complicazione. –

+0

Ulteriori problemi: A non può essere passato in base al valore; potrebbe potenzialmente essere una copia enorme. In secondo luogo, se viene passato per riferimento, torniamo al problema in cui le matrici ordinarie vengono passate correttamente, ma le viste sono temporanee nello stack in modo che non funzionino. –

0

Non usare riferimenti, passare per valore.

Lascia che copia elision esegua l'ottimizzazione per te, se necessario.

7

Questo non ha nulla a che fare con i modelli. Il vostro esempio

Scale(Diagonal(A), 2.0); 

potrebbe essere generalizzato a

f(g(v),c); 

In C++ 03, questo richiede il primo parametro per f() sia di essere passati per copia o per const di riferimento. Il motivo è che g() restituisce un valore temporaneo, un valore rvalore. Tuttavia, i valori rval legano solo ai riferimenti const ma non ai riferimenti non const. Questo è indipendente dal fatto che i modelli, SFINAE, TMP o quant'altro siano coinvolti. È solo il modo in cui la lingua (al momento) è.

C'è anche una logica dietro che: Se g() restituisce una temporanea e f() modifica che temporanea, allora nessuno ha la possibilità di "vedere" la modifica temporanea. Quindi la modifica viene eseguita invano e l'intera cosa è molto probabilmente un errore.

Per quanto ho capito, nel tuo caso il risultato di g() è una temporanea che è una vista su qualche altro oggetto (v), quindi la modifica sarebbe modificare v. Ma se questo è il caso, nell'attuale C++, il risultato di g() deve essere const (in modo che possa essere associato a un riferimento const o essere copiato. Poiché const "ha un cattivo odore" per me, rendendo tale vista economica per la copia sarebbe probabilmente la cosa migliore

Tuttavia, c'è di più in questo. C++ 1x introdurrà i cosiddetti riferimenti di valore. Quelli che conosciamo come "riferimenti" saranno quindi divisi in riferimenti di lvalue o riferimenti di valore. Sarai in grado di fare in modo che le funzioni contengano riferimenti di valore e persino sovraccarico basato su "l/valore-valore". Questo è stato pensato per consentire ai progettisti di classe di sovraccaricare il copy ctor e l'assegnazione per i retti lati della mano destra e averli "rubati" i valori della parte destra, in modo che la copia dei rvalues ​​sia più economica. Ma probabilmente potresti usarlo per fare in modo che Scale prenda un valore e lo modifichi.

Sfortunatamente il tuo compilatore molto probabilmente non supporta ancora i riferimenti rvalue.


Edit (domanda followup):

Non è possibile sovraccaricare f(T) con f(T&) per ottenere quello che vuoi. Mentre solo il primo sarà usato per i valori di rvalue, gli lvalues ​​possono legarsi a entrambi gli argomenti ugualmente bene, quindi invocare il valore di f con un lvalue è ambiguo e genera un errore in fase di compilazione.

Tuttavia, che cosa c'è di male ad avere un sovraccarico per 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); 

C'è qualcosa che mi manca?


Un'altra modifica:

avrei bisogno di un ridicolmente gran numero di sovraccarichi, allora, dal momento che attualmente ci sono più di 5 punti di vista, e ci sono diverse decine di funzioni come Scala.

Quindi è necessario raggruppare insieme quei tipi che possono essere gestiti allo stesso modo. Potresti usare alcuni semplici template-meta per fare il raggruppamento. Fuori della parte superiore della mia testa:

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

Questa particolare configurazione/raggruppamento potrebbe non esattamente soddisfare le vostre esigenze, ma è possibile impostare qualcosa di simile in modo che si adattano per voi stessi.

+0

Capisco perfettamente questo punto, ma ho aggiunto un'altra domanda di followup. –

+0

Avrei quindi bisogno di un numero ridicolmente elevato di sovraccarichi, dato che attualmente ci sono più di 5 viste e ci sono diverse dozzine di funzioni come Scale. –