0

I have created a maths library that operates via templates, it allows the user to specify the size and type of the array within a class which is then used to create a maths vector of any dimension up to four. As soon as I went to create a colour class, it struck me how similar the vector and colour class are. Is there anyway in which I could reduce code reuse and use some form of inheritance or specialisation to separate:

  1. Specific functionality (ie vector3 does not have a setXYZW() function, instead only a setXYZ()) to the dimension of which it can only be used in.
  2. Colour class and vector class can both (in terms of array data member) be of size ranging from 1 to 4 and the both share the same operators, but differ in their use in some circumstances such as a multiply vector differs from a multiply colour.

My knowledge of templates is not that good, so I would very much appreciate if anyone can show me the best solution to such a situation?

template < std::size_t N = 3, typename T = float >
class Vector
{
  typedef T Degree, Radian;
private:
  T m_vecEntry[N];
public:
  // arithmetic operations
  Vector operator + (const Vector & _vector) const;
  Vector operator - (const Vector & _vector) const;
  Vector operator * (const Vector & _vector) const;
  Vector operator * (float _val) const;
};

template < std::size_t N = 4, typename T = float >
class Colour
{
private:
  T m_colEntry[N];
public:
  // arithmetic operations
  Colour operator + (const Colour& _colour) const;
  Colour operator - (const Colour& _colour) const;
  Colour operator * (const Colour& _colour) const;
  Colour operator * (float _val) const;
};
sbi
  • 219,715
  • 46
  • 258
  • 445
Sent1nel
  • 509
  • 1
  • 7
  • 21
  • 2
    Sidenote: I'd implement binary arithmetic operators which are symmetrical (like `+`) as (inline) non-members, with their implementation based on their non-symmetrical pendants (like `+=`), which I'd implement as members. – sbi Feb 13 '10 at 16:10
  • Does it make sense to have `Colour` templated on the type of the storage and the size? A vector can have any number of elements, but a colour definition is probably limited. – David Rodríguez - dribeas Feb 13 '10 at 19:49
  • In most cases a Colour can be RGB or RGBA. – Sent1nel Feb 13 '10 at 20:08

3 Answers3

2

Your classes have a fair amount of duplicated code, it is advisable that you do something about it. A possible solution follows.

First, you take the common functionality to a base class:

template <class Derived, std::size_t N, typename T>
class VectorBase
{
protected:
  VectorBase() {} // Prevent instantiation of base

  Derived operator + (const Derived & _vector) const {
      std::cout << "Base addition\n";
      return Derived();
  }
  Derived operator * (T _val) const {
      std::cout << "Base scalar multiplication\n";
      return Derived();
  }

  T m_components[N];
};

Then you derive from it your Vector and Colour classes. In each derived class you use using Base::operation; to state explicitly that the corresponsing operation from the base class makes sense in the derived class.

For operations that don't make sense in the derived class you provide an alternative definition or not provide it at all (it will not be accessible since you didn't write using).

You can also add operations that were not in the base class, like Vector::norm:

template < std::size_t N = 3, typename T = float >
class Vector : VectorBase<Vector<N, T>, N, T>
{
  typedef VectorBase<Vector<N, T>, N, T> Base;
  typedef T Degree, Radian;

public:
  using Base::operator+; // Default implementation is valid
  using Base::operator*; // Default implementation is valid
  T norm() const {   // Not present in base class
      return T();
  }  
};

template < std::size_t N = 4, typename T = float >
class Colour : VectorBase<Colour<N, T>, N, T>
{
  typedef VectorBase<Colour<N, T>, N, T> Base; 
public:
  using Base::operator+; // Default implementation is valid

  Colour operator * (T _val) const {  // Redefines version in base class
      std::cout << "Colour scalar multiplication\n";
      return Colour();
  }
};

The only trick in this code is that I've used the CRTP to make base class operations work with derived types.

Here is a little test program:

int main()
{
  Vector<> va, vb;
  va + vb;
  va.norm();
  va * 3.0;

  Colour<> ca, cb;
  ca + cb;
  ca * 3.0f;
}

It prints:

Base addition
Base scalar multiplication
Base addition
Colour scalar multiplication
Manuel
  • 12,749
  • 1
  • 27
  • 35
  • +1 private inheritance is the idiomatic way of expressing *implemented in terms of*. Whether you factor the functionality to a common base class or you just derive one from the other, using private inheritance you keep the two classes unrelated from external code´s point of view (even if tightly coupled, inheritance is the second most coupling relationship, right after friendship) – David Rodríguez - dribeas Feb 13 '10 at 19:43
  • @David - Thanks, that explanation was certainly missing from my answer – Manuel Feb 13 '10 at 19:47
  • Thank you Manuel, can that method which you have shown be used with template specialistaion ? An example would be say if I had static const members of the Vector class of which differed in value for specific vector dimensions. ie static const Vector<3,T> UNIT_X (1, 0, 0); <- a Vector of 3 dimensions. static const Vector<4,T> UNIT_X(1, 0, 0, 0); <- vector of 4 dimensions. In my current class setup this causes a problem of redeclartion of the name UNIT_X... and its not possible for use in template specialistaion due to the requiredment of the constructor. – Sent1nel Feb 13 '10 at 20:33
  • @Setn1nel - it's possible but you'll have to provide a full specialization. See this thread: http://stackoverflow.com/questions/1501357/template-specialization-of-particular-members – Manuel Feb 14 '10 at 08:12
1

Ultimately the compiler will only create the parts of the template specialization it determines you are going to use. So it in affect will do the optimization for you e.g. not creating an unused method and so on.

So you may just want to consider making normal pre-processor macros to wrap the slight tweaks per template.

But obviously if you want to make some specializations you can do that too, but you'll still end up duplicating lines of code as it were ;)

metismo
  • 457
  • 2
  • 6
1

A lot of graphic engines keep the two separate for precisely the reasons you mentioned. While the structure of the data is equal both require different semantics for operations on the data. This also has the added benefit of more meaningful function names (setX vs setRed) but means code duplication.

Your implementation is just a wrapper around fixed size arrays. You could move the data and shared functionality in a (possible abstract) base class and provide the specific functionality in child classes.

Another way would be to treat your vector and colour classes as decorators to an array wrapper. Write or use a wrapper around arrays and combine it with vector/color functionality through composition.

pmr
  • 58,701
  • 10
  • 113
  • 156