2010-05-03 11 views
14

Sto scrivendo una semplice libreria di matematica con un tipo di modello di vettore:specializzazioni modello di classe con funzionalità condivise

template<typename T, size_t N> 
class Vector { 
    public: 
     Vector<T, N> &operator+=(Vector<T, N> const &other); 
     // ... more operators, functions ... 
}; 

Ora voglio un po 'di ulteriore funzionalità specificamente per alcuni di questi. Diciamo che voglio le funzioni x() e y() su Vector<T, 2> per accedere a determinate coordinate. Potrei creare una specializzazione parziale per questo:

template<typename T> 
class Vector<T, 3> { 
    public: 
     Vector<T, 3> &operator+=(Vector<T, 3> const &other); 
     // ... and again all the operators and functions ... 
     T x() const; 
     T y() const; 
}; 

Ma ora sto ripetendo tutto ciò che esisteva già nel modello generico.

Potrei anche utilizzare l'ereditarietà. Rinominare il modello generico per VectorBase, avrei potuto fare questo:

template<typename T, size_t N> 
class Vector : public VectorBase<T, N> { 
}; 

template<typename T> 
class Vector<T, 3> : public VectorBase<T, 3> { 
    public: 
     T x() const; 
     T y() const; 
}; 

Tuttavia, ora il problema è che tutti gli operatori sono definiti in VectorBase, in modo da tornare VectorBase istanze. Questi non possono essere assegnati a Vector variabili:

Vector<float, 3> v; 
Vector<float, 3> w; 
w = 5 * v; // error: no conversion from VectorBase<float, 3> to Vector<float, 3> 

ho potuto dare Vector un costruttore di conversione implicita per rendere questo possibile:

template<typename T, size_t N> 
class Vector : public VectorBase<T, N> { 
    public: 
     Vector(VectorBase<T, N> const &other); 
}; 

Tuttavia, ora sto convertendo Vector-VectorBase e viceversa. Anche se i tipi sono gli stessi in memoria, e il compilatore potrebbe ottimizzare tutto questo, è goffo e non mi piace avere un potenziale sovraccarico di runtime per quello che è essenzialmente un problema in fase di compilazione.

C'è qualche altro modo per risolvere questo?

+0

Perché non solo eseguire 'x()' e 'y()' funzioni libere che prendono la specializzazione appropriata di 'Vector'? Per esempio. 'template T x (const Vector & v);' –

+0

Possibile, ma 'vx()' ha più senso per me di 'x (v)'. Inoltre, mi piacerebbe aggiungere alcuni costruttori specializzati, ad esempio 'Vector (T, T) ', ei costruttori non possono essere funzioni libere – Thomas

+0

No, ma puoi avere funzioni che restituiscono gli oggetti in base al valore nello stile di' std :: make_pair'. –

risposta

8

Penso che si possa utilizzare CRTP per risolvere questo problema. Questo idioma è utilizzato in boost::operator.

template<typename ChildT, typename T, int N> 
class VectorBase 
{  
public: 
    /* use static_cast if necessary as we know that 'ChildT' is a 'VectorBase' */ 
    friend ChildT operator*(double lhs, ChildT const &rhs) { /* */ } 
    friend ChildT operator*(ChildT const &lhs, double rhs) { /* */ } 
}; 

template<typename T, size_t N> 
class Vector : public VectorBase<Vector<T,N>, T, N> 
{ 
}; 

template<typename T> 
class Vector<T, 3> : public VectorBase<Vector<T, 3>, T, 3> 
{ 
public: 
    T x() const {} 
    T y() const {} 
}; 

void test() 
{ 
    Vector<float, 3> v; 
    Vector<float, 3> w; 
    w = 5 * v; 
    w = v * 5; 
    v.x(); 

    Vector<float, 5> y; 
    Vector<float, 5> z; 
    y = 5 * z; 
    y = z * 5; 
    //z.x(); // Error !! 
} 
+0

Ho appena rivisitato questo problema dopo aver lavorato su qualcos'altro per un po ', e questa è davvero la soluzione più bella. Grazie mille! – Thomas

4

Ecco qualcosa che mi è venuto in mente quando si gioca con le funzioni di C++ 0x qualche tempo fa. L'unica caratteristica C++ 0x utilizzata in questo è static_assert, quindi è possibile utilizzare Boost per sostituirlo.

Fondamentalmente, è possibile utilizzare una funzione di controllo delle dimensioni statiche che verifica solo che un dato indice sia inferiore alla dimensione del vettore. Usiamo un affermare statico per generare un errore di compilatore se l'indice è fuori limite:

template <std::size_t Index> 
void size_check_lt() const 
{ 
    static_assert(Index < N, "the index is not within the range of the vector"); 
} 

Poi possiamo fornire un metodo get() che restituisce un riferimento all'elemento ad un determinato indice (ovviamente un sovraccarico const sarebbe utile anche):

template <std::size_t Index> 
T& get() 
{ 
    size_check_lt<Index>(); return data_[Index]; 
} 

quindi possiamo scrivere accessors semplici in questo modo:

T& x() { return get<0>(); } 
T& y() { return get<1>(); } 
T& z() { return get<2>(); } 

Se il vettore ha solo due elementi, è possibile utilizzare xey ma non z. Se il vettore ha tre o più elementi, puoi usarli tutti e tre.

Ho finito per fare la stessa cosa per i costruttori - Ho creato i costruttori per i vettori di dimensione due, tre e quattro e ho aggiunto un size_check_eq che consentiva loro di essere istanziati solo per i vettori di dimensione due, tre e quattro, rispettivamente. Posso provare a postare il codice completo quando torno a casa stasera, se qualcuno è interessato.

Ho abbandonato il progetto a metà, quindi potrebbe esserci un grosso problema nel farlo in questo modo in cui non mi sono imbattuto in ... almeno è un'opzione da considerare.

0

Il modo più semplice? Utilizzando funzioni esterne:

template <class T> 
T& x(Vector<T,2>& vector) { return vector.at<0>(); } 

template <class T> 
T const& x(Vector<T,2> const& vector) { return vector.at<0>(); } 

Nella programmazione modello utilizzando funzioni esterne è il modo più semplice per aggiungere funzionalità, semplicemente a causa del problema di specializzazione che hai appena incontrato.

D'altra parte, si potrebbe ancora fornire x, y e z per qualsiasi N o forse usare enable_if/disable_if caratteristiche per limitare il campo di applicazione.

0

Non so se è possibile aggirare i problemi di digitazione con l'operatore di assegnazione, ma è possibile semplificare la vita definendo le versioni dei modelli dei vari operatori, le funzioni di supporto per implementarle e quindi utilizzare l'ereditarietà.

template <typename T, std::size_t N> 
class fixed_array { 
public: 
    virtual ~fixed_array() {} 
    template <std::size_t K> 
    fixed_array& operator+=(fixed_array<T,K> const& other) { 
     for (std::size_t i=0; i<N; ++i) 
      this->contents[i] += other[i]; 
     return *this; 
    } 
    template <std::size_t K> 
    fixed_array& operator=(fixed_array<T,K> const& other) { 
     assign_from(other); 
     return *this; 
    } 
    T& operator[](std::size_t idx) { 
     if (idx >= N) 
      throw std::runtime_error("invalid index in fixed_array[]"); 
     return contents[idx]; 
    } 
protected: 
    template <std::size_t K> 
    void assign_from(fixed_array<T,K> const& other) { 
     for (std::size_t i=0; i<N; ++i) 
      this->contents[i] = other[i]; 
    } 
private: 
    T contents[N]; 
}; 

template <typename T> 
class fixed_2d_array: public fixed_array<T,2> { 
public: 
    T x_coord() const { return (*this)[0]; } 
    T y_coord() const { return (*this)[1]; } 
    template <std::size_t K> 
    fixed_2d_array& operator=(fixed_array<T,K> const& other) { 
     assign_from(other); 
     return *this; 
    } 
}; 

int 
main() { 
    fixed_array<int,5> ary1; 
    fixed_2d_array<int> ary2; 
    ary2 = ary1; 
    ary1 = ary2; 
    ary2 += ary1; 
    ary1 += ary2; 
    return 0; 
} 
Problemi correlati