Fun with Templates
Updated on Tue, 2015-10-06 12:20. Originally created by jwebb on 2015-10-06 11:19.
#include "TVector3.h"
#include "StarClassLibrary/StThreeVectorD.hh"
#include <iostream>
using namespace std;
/**
Demonstration of how to do:
TVector3 a( 1, 2, 3 );
double b[] = {4, 5, 6};
double result = dot( a, b );
But what use is this really?
Vectors. Tracks. Hits. Any many others...
Every collaboration implements their own classes to describe these.
*/
// This is my little vector class. It's pretty simple. Not much functionality.
struct Vector3_t {
double x;
double y;
double z;
};
//
// These are traits. They describe how we get information from an arbitrary class... in this
// case, one of the 4 ways we represent vectors in this example.
//
struct vector_with_idx_operator { }; // supports x = V[0], y = V[1], z = V[2]
struct vector_with_get_methods { }; // supports x = getX(), y = getY(), z = getZ()
struct vector_with_named_members { }; // supports x = V.x, y = V.y, z = V.z
struct vector_with_named_methods { }; // supports x = V.x(), y = V.y(), z = V.z()
//
// Since we are dealing with member variables and member functions, we need to handle the
// different styles which developers define member variables and member functions:
//
// i.e. v.getX() vs v.GetX(), or v.x vs v.X.
//
struct anycase { };
struct lowerCamelCaseMethods : public anycase { };
struct upperCamelCaseMethods : public anycase { };
struct lowerCamelCaseMembers : public anycase { };
struct upperCamelCaseMembers : public anycase { };
//
// These are tags. We build them up using multiple inheritance to describe the various
// traits that a vector class has. So for example, below we specify that StThreeVectorD
// accesses its values through member functions, and that we use lowerCamelCase (i.e. getX())
// to get the values.
//
struct simple_array_tag : public vector_with_idx_operator, public anycase { };
struct tvector3_tag : public vector_with_idx_operator, public upperCamelCaseMethods { };
struct stthreeectord_tag : public vector_with_named_methods, public lowerCamelCaseMethods { };
struct vector3_tag : public vector_with_named_members, public lowerCamelCaseMembers { };
// Fail if an unknown type is passed
template<typename V> struct VectorTraits { typedef void vector_type; };
//
// A VectorTraits class allows us to specify the tags associated with any given type passed in.
// If a user wants to implement a new vector class, then this will often be the only additional
// code needed: (1) a tag with the appropraite traits defined, and (2) an entry in the vector
// traits table below.
//
template<> struct VectorTraits<double[3]> { typedef simple_array_tag vector_type; };
template<> struct VectorTraits<double* > { typedef simple_array_tag vector_type; };
template<> struct VectorTraits<Vector3_t> { typedef vector3_tag vector_type; };
template<> struct VectorTraits<Vector3_t*> { typedef simple_array_tag vector_type; };
template<> struct VectorTraits<TVector3> { typedef tvector3_tag vector_type; };
template<> struct VectorTraits<StThreeVectorD> { typedef stthreeectord_tag vector_type; };
//
// These are our dispatch functions. They take three arguements: a reference to the vector object
// which contains the data, and two tags specifying the access method and the preferred case of
// the member or method.
//
template <typename V> double GetX( const V &v, vector_with_idx_operator, anycase ) { return v[0]; }
template <typename V> double GetX( const V &v, vector_with_named_members, lowerCamelCaseMembers ) { return v.x; }
template <typename V> double GetX( const V &v, vector_with_named_members, upperCamelCaseMembers ) { return v.X; }
template <typename V> double GetX( const V &v, vector_with_named_methods, lowerCamelCaseMethods ) { return v.x(); }
template <typename V> double GetX( const V &v, vector_with_named_methods, upperCamelCaseMethods ) { return v.X(); }
template <typename V> double GetX( const V &v, vector_with_get_methods, lowerCamelCaseMethods ) { return v.getX(); }
template <typename V> double GetX( const V &v, vector_with_get_methods, upperCamelCaseMethods ) { return v.GetX(); }
template <typename V> double GetY( const V &v, vector_with_idx_operator, anycase ) { return v[1]; }
template <typename V> double GetY( const V &v, vector_with_named_members, lowerCamelCaseMembers ) { return v.y; }
template <typename V> double GetY( const V &v, vector_with_named_members, upperCamelCaseMembers ) { return v.Y; }
template <typename V> double GetY( const V &v, vector_with_named_methods, lowerCamelCaseMethods ) { return v.y(); }
template <typename V> double GetY( const V &v, vector_with_named_methods, upperCamelCaseMethods ) { return v.Y(); }
template <typename V> double GetY( const V &v, vector_with_get_methods, lowerCamelCaseMethods ) { return v.getY(); }
template <typename V> double GetY( const V &v, vector_with_get_methods, upperCamelCaseMethods ) { return v.GetY(); }
template <typename V> double GetZ( const V &v, vector_with_idx_operator, anycase ) { return v[2]; }
template <typename V> double GetZ( const V &v, vector_with_named_members, lowerCamelCaseMembers ) { return v.z; }
template <typename V> double GetZ( const V &v, vector_with_named_members, upperCamelCaseMembers ) { return v.Z; }
template <typename V> double GetZ( const V &v, vector_with_named_methods, lowerCamelCaseMethods ) { return v.z(); }
template <typename V> double GetZ( const V &v, vector_with_named_methods, upperCamelCaseMethods ) { return v.Z(); }
template <typename V> double GetZ( const V &v, vector_with_get_methods, lowerCamelCaseMethods ) { return v.getZ(); }
template <typename V> double GetZ( const V &v, vector_with_get_methods, upperCamelCaseMethods ) { return v.GetZ(); }
//
// And here are our access functions. It takes in one arguement, the reference to the object being used, and
// looks up the related tag in the VectorTraits class. Once it has the tag, it passes the object and the
// tag to the dispatch functions above.
//
template<typename V> double GetX( const V& v )
{
typename VectorTraits<V>::vector_type x;
return GetX( v, x, x );
};
template<typename V> double GetY( const V& v )
{
typename VectorTraits<V>::vector_type x;
return GetY( v, x, x );
};
template<typename V> double GetZ( const V& v )
{
typename VectorTraits<V>::vector_type x;
return GetZ( v, x, x );
};
//
// Finally we implement the dot product function. It is simply a template
// function which takes two vectors of potentially different types. Accesses
// their elements. Computes the dot product and returns it.
//
template<typename A, typename B> double dot( const A& a, const B& b )
{
return GetX( a ) * GetX( b ) + GetY( a ) * GetY( b ) + GetZ( a ) * GetZ ( b );
};
//
// So. Why not just implement these directly using function overloading? Simply
// put, if we have 4 different vector classes which we want to handle, we would
// need to implement 16 different versions of the dot product. Add one more, we
// need to implement 25. It's N^2 to be able to handle an arbitrary number of
// classes.
//
void tagDispatchExample()
{
cout << "TVector3 a(1, 2, 3) --" << endl;
TVector3 a( 1, 2, 3 );
cout << "x = " << GetX( a ) << endl;
cout << "y = " << GetY( a ) << endl;
cout << "z = " << GetZ( a ) << endl;
cout << "a*a = " << dot( a, a ) << endl;
cout << "StThreeVectorD b(4,5,6) --" << endl;
StThreeVectorD b(4,5,6);
cout << "x = " << GetX( b ) << endl;
cout << "y = " << GetY( b ) << endl;
cout << "z = " << GetZ( b ) << endl;
cout << "b*b = " << dot( b, b ) << endl;
cout << "Vector3_t c(7,8,9) --" << endl;
Vector3_t c = {7,8,9};
cout << "x = " << GetX( c ) << endl;
cout << "y = " << GetY( c ) << endl;
cout << "z = " << GetZ( c ) << endl;
cout << "c*c = " << dot( c, c ) << endl;
cout << "double d[3] = {-1,-2,-3} --" << endl;
double d[] = {-1,-2,-3};
cout << "x = " << GetX(d) << endl;
cout << "y = " << GetY(d) << endl;
cout << "z = " << GetZ(d) << endl;
cout << "d*d = " << dot(d,d) << endl;
cout << endl << endl;
cout << "a*b = " << dot(a,b) << endl;
cout << "c*b = " << dot(c,b) << endl;
cout << "c*a = " << dot(c,a) << endl;
cout << "a*d = " << dot(a,d) << endl;
};
»
- jwebb's blog
- Login or register to post comments