Traits as layers of indirection

25 07 2010

“We can solve any problem by introducing an extra layer of indirection” Quoted by Butler Lampson and attributed to David Wheeler.

This qoute is true for nearly, nearly all problems I suspect; unless of course we have code with so many layers of indirections that it’s just, um… impossible to maintain. I should probably emphasis here that I do mean indirection rather than abstraction which are two different concepts.

My idea of traits is that of meta-information encoded within a class and used to describe the nature [properties] of the instanced attributes. And just because that sentence is filled with too many synonymous words I’m going to show some code, which would hopefully clarify my definitions of; Properties, Attributes and Traits.
struct Vector3
{
  /*traits*/
  typedef float value_type;
  enum { size=3 };
  /*functions*/
  type_value& x();
  type_value& y();
  type_value& z();
  Vector3(value_type x, value_type y, value_type z);
  /*data*/
  value_type m[size];
};

Simple; but for what purpose? Isn’t all that information available for us by just looking at the header file, surely more typing is bad for the soul?

A little bit of typing here can save us more typing later, but I’ll get back to that. For now let’s also make a simple function which returns the sum of two vectors.
Vector3 operator+(const Vector3& lhs, const Vector3& rhs)
{
  Vector3(lhs.x()+rhs.x(), lhs.y()+rhs.y(), lhs.z()+rhs.z());
}

Easy, straightforward and readable [also bit of a waste of registers, but I’m not going to optimized anything unless I have to].

Easy, straightforward and readable. And not extensible at all. That is to say, I now want to add a Vector2, Vector4 and just for the sheer join of it, Vector6. That also means I would now have to write 3 other functions to sum those classes. Code bloat anyone?

It’s obvious that the sum function has to have a loop which counts the elements and adds them, but how are we to push all of those classes into the same function and still expect that to work?

Duh, obviously, inheritence. If all vectors inherit from the same class [let’s call it VectorBase] then we can treat them all as the base class and run on it, right?

Wrong. let’s introduce Matrix2, Matrix3, Matrix4, Quaternion, Color, and some other classes which are different but are still summed the same way and if you think that one giant base class is the solution than we have a different understanding of OOP.

Inheritence is a powerful tool but I rather not use it unless I have to. Templates to the rescue [templates are of course another powerful tool and as with all power, please wield with care!].
template<typename T>
T operator+(const T& lhs, const T& rhs)
{
  T result;
  for (int i=0; i!=T::size; ++i) { result.m[i] = lhs.m[i] + rhs.m[i]; }
  return result;
}

This piece of code is almost, but not quite, what I had in mind; the reason for that being the direct access into the T [type] in order to extract the size trait. It might look like a good idea, but consider what happens when I would want to treat an object as a Vector/Matrix/Whatever using this function but the object already has a size functionality which has nothing to do with the usage of this trait, or worse, what if it doesn’t have the size trait at all…?

Here I introduce my next layer of indirection; the trait class.
template<typename T>
struct SizeTrait
{
  enum { size=T::size };
};

And for our new operator+
template<typename T>
T operator+(const T& lhs, const T& rhs)
{
  T result;
  for (int i=0; i!=SizeTrait::size; ++i) { result.m[i] = lhs.m[i] + rhs.m[i]; }
  return result;
}

Is there a difference, it seems like nothing has changed other than adding more complexity. Not true, although the SizeTrait class still pulls into the T [type] to extract its size trait we can now specialize the trait in case another class is needed in the algorithm but doesn’t have a size trait. Like this;
template<typename T>
struct SizeTrait
{
  enum { size=Matrix4::rows*Matrix4::cols };
};

And voila, instant size. This of course is a very simple example but the method scales well to more complex objects [check out the STL’s trait classes].

Greensleeves was my heart of gold

Advertisements

Actions

Information

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s




%d bloggers like this: