2010-05-03 12 views
14

que estoy escribiendo una biblioteca matemática simple con un tipo de plantilla de vectores:especializaciones plantilla de clase con funcionalidad compartida

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

Ahora quieren alguna funcionalidad adicional específicamente para algunos de ellos. Digamos que quiero las funciones x() y y() en Vector<T, 2> para acceder a determinadas coordenadas. Podría crear una especialización parcial para esto:

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

Pero ahora estoy repitiendo todo lo que ya existía en la plantilla genérica.

También podría usar la herencia. Cambiar el nombre de la plantilla genérica para VectorBase, que podría hacer esto:

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

Sin embargo, ahora el problema es que todos los operadores se definen en VectorBase, por lo que vuelvo VectorBase casos. Estos no se pueden asignar a Vector variables:

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

que podría dar Vector un constructor de conversión implícita para hacer esto posible:

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

Sin embargo, ahora estoy conversión de Vector a VectorBase y viceversa. Aunque los tipos son los mismos en la memoria, y el compilador puede optimizar todo esto, se siente torpe y realmente no me gusta tener una sobrecarga de tiempo de ejecución potencial para lo que es esencialmente un problema de tiempo de compilación.

¿Hay alguna otra manera de solucionar esto?

+0

¿Por qué no simplemente hacer 'x()' y 'y()' funciones gratuitas que toman la especialización apropiada de 'Vector'? P.ej. 'plantilla T x (const Vector & v);' –

+0

Posible, pero 'vx()' tiene más sentido para mí que 'x (v)'. Además, me gustaría agregar algunos constructores especializados, por ejemplo 'Vector (T, T) ', y los constructores no pueden ser funciones libres. – Thomas

+0

No, pero puede tener funciones que devuelvan objetos por valor en el estilo de' std :: make_pair'. –

Respuesta

8

Creo que puede utilizar CRTP para resolver este problema. Este modismo se usa en 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

Acabo de volver a visitar este problema después de trabajar en otra cosa por un tiempo, y esta es de hecho la mejor solución. ¡Un millón de gracias! – Thomas

4

Esto es algo que se me ocurrió cuando jugaba con C++ 0x hace un tiempo. La única característica de C++ 0x utilizada en esto es static_assert, por lo que podría usar Boost para reemplazarla.

Básicamente, podemos usar una función de comprobación de tamaño estático que solo comprueba para asegurarse de que un índice dado sea menor que el tamaño del vector. Utilizamos una aserción estática para generar un error de compilación si el índice está fuera de límites:

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

entonces podemos proporcionar un método get() que devuelve una referencia al elemento situado en un índice dado (obviamente una sobrecarga de const sería útil también):

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

Entonces podemos escribir descriptores de acceso simples de este modo:

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

Si el vector tiene sólo dos elementos, puede utilizar x e y, pero no z. Si el vector tiene tres o más elementos, puede usar los tres.

Terminé haciendo lo mismo para los constructores: creé constructores para vectores de dimensión dos, tres y cuatro y agregué un size_check_eq que les permitió crear instancias solo para vectores de dimensión dos, tres y cuatro, respectivamente. Puedo intentar publicar el código completo cuando llegue a casa esta noche, si alguien está interesado.

Dejé el proyecto a la mitad, por lo que podría haber un gran problema al hacerlo de esta manera que no me encontré ... al menos es una opción a considerar.

0

¿La manera más simple? Uso de las funciones externas:

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

En la programación plantilla usando funciones externas es la forma más sencilla de agregar funcionalidad, simplemente debido a la cuestión de la especialización que acabamos de encontrar.

Por otro lado, aún se podía proporcionar x, y y z para cualquier N o tal vez usar enable_if/disable_if características para restringir el alcance.

0

No sé si puede resolver los problemas de tipeo con el operador de asignación, pero puede hacer la vida un poco más fácil definiendo versiones de plantilla de los distintos operadores, funciones de ayuda para implementarlas y luego usar herencia.

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