Skip to main content
GameDev.net gamedev.net
🔒 Locked

Template Specialization

Started by starruler Aug 29, 2008 at 7:22 AM 22 replies 3k views
Original Post
starruler
starruler
Alright, I'm working on my template skills and decided to write a statically sized Vector containing an arbitrary type. Here's the Vector.hh file:

#ifndef VECTOR3_H
#define VECTOR3_H

#include <ostream>
#include <cassert>

/** Class representing a vector of arbitrary size and type.
  * Provides all the functions necessary to operate on Vectors.
  * The * operator is overloaded to provide the dot product. The % operator is 
  * overloaded to provide the cross product.
  * Internally all variables are represented in a statically allocated array.
  */
template<typename T, int N>
class Vector
{
  public:
    /** Default Constructor.
      * Initializes all values to zero.
      */
    Vector()
    {
      for(int i = 0; i < N; i++)
        data = 0;
    }

    // Copy and assignment constructors autogenerated by the compiler.
    
    /// Compound addition operator
    const Vector<T,N>& operator+=(const Vector<T,N> &rhs)
    {
      for(int i = 0; i < N; i++)
        data += rhs.data;
    }

    /// Compound subtraction operator
    const Vector<T,N>& operator-=(const Vector<T,N> &rhs)
    {
      for(int i = 0; i < N; i++)
        data -= rhs.data;
    }
    /// Compound scalar multiplaction operator
    const Vector<T,N>& operator*=(const float scal)
    {
      for(int i = 0; i < N; i++)
        data *= scal;
    }
    /// Compound scalar division operator
    const Vector<T,N>& operator/=(const float scal)
    {
      for(int i = 0; i < N; i++)
        data /= scal;
    }

    /// Addition operator
    inline const Vector<T,N> operator+(const Vector<T,N> &rhs) const
    {
      return Vector<T,N>(*this) += rhs;
    }
    /// Subtraction operator
    inline const Vector<T,N> operator-(const Vector<T,N> &rhs) const
    {
      return Vector<T,N>(*this) -= rhs;
    }
    /// Scalar multiplication operator
    inline const Vector<T,N> operator*(const float scal) const
    {
      return Vector<T,N>(*this) *= scal;
    }
    /// Scalar division operator
    inline const Vector<T,N> operator/(const float scal) const
    {
      return Vector<T,N>(*this) /= scal;
    }

    /// Unary minus operator
    const Vector<T,N> operator-() const
    {
      return Vector<T,N>(*this) *= -1;
    }

    /// Const operator[] for accessing elements in non assignment contexts
    inline const float & operator[](int index) const
    {
      assert(index > -1 && index < N);
      return data[index];
    }
    /// Operator[] for accessing elements in assignment contexts
    inline float & operator[](int index)
    {
      assert(index > -1 && index < N);
      return data[index];
    }
    /// Dot product operator
    const float operator*(const Vector<T,N>& rhs) const
    {
      T result =  0;
      for(int i = 0; i < N; i++)
        result += data * rhs.data;
      return result;
    }
    /** Cross product operator
      * This is not implemented in the template class.
      * This is due to the fact that not all vector spaces allow the cross prod-
      * uct.  The solution to this is to use template specialization to create 
      * methods for spaces that do contain the cross product.
      */
    const Vector<T,N> operator%(const Vector<T,N>& rhs) const
    {
      assert( false && "A non specialization of the cross product has been cal"
                       "led.  This should never happen!" );
    }

    /** The magnitude squared.
      * This function is much faster than magnitude as it avoids a costly call 
      * to sqrt.
      * @return The magnitude^2.
      */
    float magnitude2() const
    {
      // The magnitude^2 is the dot product of a vector with itself.
      return (*this) * (*this);
    }

    /** The magnitude of this vector.
      * @return The magnitude of this vector.
      */
    inline float magnitude() const
    {
      return sqrtf( (*this) * (*this) );
    }

    /** Normalizes this vector.
      * This is done IN PLACE so this vector will be modified.
      * Use the function normal() if you just want the normalized vector and 
      * this vector to remain intact.  This function is faster than normal().
      * @return A const reference to this vector.
      */
    const Vector<T,N> & normalize()
    {
      *this /= magnitude();
      return *this;
    }

    /** Return the normal of this vector.
      * @return The normal of this vector.
      */
    inline const Vector<T,N> normal() const
    {
      return *this / magnitude();
    }


  private:
    T data[N];
};

/// Scalar * vector operator
template<typename T, int N>
const Vector<T,N> operator*(const float &scal, const Vector<T,N> &rhs)
{
  return rhs * scal;
}

/// Insertion operator
template<typename T, int N>
std::ostream& operator<<(std::ostream &os, const Vector<T,N> &vec)
{
  os << '(';
  for(int i = 0; i < N-1; i++)
    os << vec << ' ';
  os << vec[N-1] << ')';
  return os;
}

// Typedefs
typedef Vector<float,3> Vector3F;

#endif



Now what I'd like to do is provide a specific version for the case that N = 3. Then I would implement the cross product and provide a 3-arg constructor for that particular class. I'm struggling with the syntax to do so. I don't want to have to rewrite all the other functions. What I've been trying is:

/// Specializations
template<typename T>
class Vector<T,3>
{
  public:
    Vector()
    {
      data[0] = data[1] = data[2] = 0;
    }

    Vector(T x, T y, T z)
    {
      data[0] = x;
      data[1] = y;
      data[2] = z;
    }

  private:
    T data[3];
};

template<typename T>
const Vector<T,3> Vector<T,3>::operator%(const Vector<T,3> &rhs) const
{
  T i = (data[1] * rhs[2]) - (data[2] * rhs[1]);
  T j = (data[0] * rhs[2]) - (data[2] * rhs[0]);
  T k = (data[0] * rhs[1]) - (data[1] * rhs[0]);;

  return Vector<T,3>(i,j,k);
}



This doesn't work however. The operator[] doesn't seem to carry over. I realize that there might be errors in other places of this code, it is untested. Feel free to point them out.
SiCrane
SiCrane
Just make it a non-member function.
starruler
starruler
That doesn't solve providing a 3-arg constructor problem.
SiCrane
SiCrane
Oops, didn't see that part. You can just add the three arg constructor to the base type and throw in a static assert that N is 3.
starruler
starruler
While that would certainly work I want to learn this part of templates. The static assert part feels like clever hackery to me.
SiCrane
SiCrane
Another option is to create a base class with all the stuff you want your class to contain and then derive both your specialized and non-specialized version from it.
starruler
starruler
That doesn't work because I want statically allocated memory the ability to use multiple types (float, int, double, or even user-defined). That screams templates to me. And I want to work with templates to gain familiarity with them.
SiCrane
SiCrane
The base class would also be a template.
starruler
starruler
Isn't that just template specialization or template some other word here? Having a base template class and then adding functionality to that based on parameters. Exactly what I would do with my subclass it's just a different mechanism.
Antheus
Antheus
Quote:
I don't want to have to rewrite all the other functions.


Template specialization is not inheritance. If you need inheritance, use just that.

When you specialize a template, you end up with a completely unrelated something, which may or may not mimic or resemble generalized version.


But the bigger question here is design. If it looks like a duck....

Vector3 looks like VectorN. They are exactly the same - literally.
  private:    T data[N];};
results (in parsed code) as T data[3] for N = 3. Simply put, your specialization doesn't do anything that generic template doesn't provide already. As such your specialization is redundant.


Abstracting operations. What is the commonality between all vectors? data? Vector? The way I see it, the only thing in common is the very operator[]. So restructure your Vector to have just that (or just use array).

Then, define operations externally:
template < typename T, size_t N >Vector<T,N> opeator???(Vector<T,N> lhs, Vector<T,N> rhs) {  lhs[0] + lhs[1] + rhs[0] + rhs[1];}

For the operators where you cannot do that, you're simply stuck with rewriting the code. You cannot re-use it in case of specializations, since internal storage differs anyway, making re-use moot point.

Bugs:
/// Const operator[] for accessing elements in non assignment contextsinline const float & operator[](int index) const.../// Operator[] for accessing elements in assignment contextsinline float & operator[](int index)    
I'll let you figure out why. Inline is also redundant, since it's implied by the way functions are defined.
SiCrane
SiCrane
Quote:
Original post by starruler
Isn't that just template specialization or template some other word here? Having a base template class and then adding functionality to that based on parameters. Exactly what I would do with my subclass it's just a different mechanism.


Specialization isn't adding functionality. It's replacing functionality.
starruler
starruler
http://www.cprogramming.com/tutorial/template_specialization.html

The examples there look a lot like "inheritance" for templates. Could just be reading them incorrectly.

Moving the cross product outside of the class is a good idea and I'll definitely be doing that. That way a compile time error is thrown for a vector in a space that doesn't contain the cross.

There has to be some way to have a 3-arg constructor for the N=3 variant without rewrite a massive amount of code.

About the bugs. I don't understand. I wrote that so that the class could be used with the const modifier and still have access to the data elements. For the const definition I removed the reference as that was unnecessary it now reads

const float operator[](int index) const


The inline modifiers are there to prevent an extra function call when all the function is doing is returning a value.
Antheus
Antheus
Just another example I thought of where specialization of this type may be valid approach.

Let's say you want space efficient storage of bools.
template < size_t N, bool UseArray = true >struct BoolVector {  bool operator[](size_t index) const {    return (bits[index/8] >> index) & 1;  }private:  bool bits[N/8+1];};template < size_t N >struct BoolVector<N, false> {  bool operator[](size_t index) const {    return (bits >> index) & 1;  }private:  int bits;};template < size_t N >struct Vector<bool, N> : public BoolVector< N, N<9 > {};


Here, the common class changes storage based on needs, and selects template as appropriate. The common thing again is the subscript operator, internal storage is encapsulated in BoolVector.

Quote:
I removed the reference as that was unnecessary it now reads

const float operator[](int index) const


Declare Vector.
SiCrane
SiCrane
Quote:
Original post by starruler
There has to be some way to have a 3-arg constructor for the N=3 variant without rewrite a massive amount of code.

There is. I already mentioned two of them.
starruler
starruler
I guess I should add to the documentation that this class is to be used with numerical types. As the to my knowledge std::string doesn't implement the * operator. Although I fail to see how that would break that operator. Is returning a reference to std::string a bad thing?

About your example. When I was searching around on the net I came across an example that included that. But they didn't use sub classes or inheritance or anything of that nature. It was all pure template stuff.

http://www.cplusplus.com/doc/tutorial/templates.html Check that link and scroll to template specialization. They actually add functionality to a template without using inheritance.
Antheus
Antheus
Quote:
Original post by starruler
I guess I should add to the documentation that this class is to be used with numerical types. As the to my knowledge std::string doesn't implement the * operator. Although I fail to see how that would break that operator. Is returning a reference to std::string a bad thing?


You're returning a float in a class generalized by T. Return value must be T, or the class is useless even for numerical types.

starruler
starruler
Quote:

You're returning a float in a class generalized by T. Return value must be T, or the class is useless even for numerical types.


Oh, that's a pretty silly mistake. Thanks for pointing it out. Did you look over those links at all?
rip-off
rip-off
Looking at this link, and change main to this:
int main () {  mycontainer<int> myint (7);  mycontainer<char> mychar ('j');  cout << myint.increase() << endl;  cout << mychar.increase() << endl;  cout << mychar.uppercase() << endl;  return 0;}


Quote:
Original Source: GCC
'class mycontainer' has no member named 'increase'


Conclusion: specialisation is replacement, not addition.
starruler
starruler
That's frustrating code provided in a tutorial doesn't compile. Sorry about the constant pushing on the topic. I'm going to take the Vector class and subclass it into a Vector3 class that contains the constructor and cross product operator. Thanks for your patience and help!
rip-off
rip-off
It does compile. But I changed it to include a call to mychar.increase(), to illustrate the point.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.