No, I’m not talking about hospital procedures. I’m talking about visitors, as in the visitor design pattern.
So, the GMTL is coming along nicely. I’m doing specialized functions now, meaning that I’ve completed the generic algorithms for arbitrary size matrices, vectors, and square matrices. Specialized means that they only work for certain size matrices, such as from_euler which produces a 3×3 matrix.
Which brings me to this post’s point. See, I really, really like functional programming. One of the main points of the GMTL is to create a matrix library with a purely functional notation. No operations are to be performed on the matrices themselves, so m.setIdentity or m.fromEulerAngles are not allowed. Also, no temporaries are to be created intermittently.
So, how do you write
m = from_euler( x, y, z );
without using a temporary? An expression template.
First, we define an operation class that will contain an expression or computation. We’ll call it op_matrix.
template <typename Operation>
struct op_matrix {
Operation _op;
op_matrix( Operation& op ) : _op( op )
{
}
template
void apply( Matrix& m )
{
_op.apply( m );
}
};
The class is a template so that we can put in custom operations, but make them all members of a family of classes we use later on.
Next, we define an assignment operator in our matrix class that takes a parameter of type op_matrix.
template <typename Operation>
matrix& operator=( op_matrix<Operation>& op )
{
op.apply( *this );
return *this;
}
As you can see, the assignment operator simply calls apply on op_matrix, which in turn calls apply on the Operation, using the matrix as the parameter. Hence, the operation is visited by the matrix in the same manner as using the Visitor pattern.
As an example, let’s take a rotation matrix around the x axis for an operation.
template <typename Element>
struct op_from_euler_x {
Element _x;
op_from_euler_x( Element x )
: _x( x )
{
}
#if defined(_DEBUG)
~op_from_euler_x( void )
{
}
#endif
void apply( Matrix3& m )
{
Element cx = cosf(_x);
Element sx = sinf(_x);
m = 1.0f, 0.0f, 0.0f,
0.0f, cx, -sx,
0.0f, sx, cx;
}
};
To make this clean, we wrap it in a function that contains the operation we are performing.
op_matrix< op_from_euler_x<float> > rot_x( float x )
{
return op_matrix<op_from_euler_x<float> >( op_from_euler_x<float>( (float) x ));
}
The downside to such usage is the inflexibility of expressions using this technique. The only real possible statement using this is
Matrix m;
m = my_operation();
There are undoubtedly ways to extend this to make full expressions possible, but for now it works and gives us the functional notation we crave with the efficiency of a passed parameter.