views:

218

answers:

3

Given: (Code reduced to a sensible minimum)

// MemberTypes

template
<
  typename SPEEDTYPE = float,
  typename SIZETYPE = float,
  typename ACCELERATIONTYPE = float
>
struct ParticleMemberTypes
{
  typedef typename SPEEDTYPE SpeedType;
  typedef typename SIZETYPE SizeType;
  typedef typename ACCELERATIONTYPE AccelerationType;
};

// Properties

template <class T>
class PSpeed
{
public:
  inline const typename T::SpeedType&   GetSpeed() const  { return v; }
  inline void SetSpeed(const typename T::SpeedType& V)  { v = V; }
  const static bool hasSpeed = true;
private:
  typename T::SpeedType v;
};

template <class T> 
class PSize
{
public:
  inline const typename T::SizeType&    GetSize() const  { return v; }
  inline void SetSize(const typename T::SizeType& V)   { v = V; }
  const static bool hasSize = true;
private:
  typename T::SizeType v;
};

template <class T>
class PAcceleration
{
public:
  inline const typename T::AccelerationType& GetAcceleration() const  { return v; }
  inline void SetAcceleration(const typename T::AccelerationType& V)   { v = V; }
  const static bool hasAcceleration = true;
private:
  typename T::AccelerationType v;
};

// Empty base and specializations

(It is necessary that each EmptyBase is a distinct type, to avoid inheriting from the same base class more than once)

template <typename P, typename T> struct EmptyBase {};
template <typename T> struct EmptyBase<PSpeed<T>, T>
{
  const static bool hasSpeed = false;
};
template <typename T> struct EmptyBase<PSize<T>, T>
{
  const static bool hasSize = false;
};
template <typename T> struct EmptyBase<PAcceleration<T>, T>
{
  const static bool hasAcceleration = false;
};

// Base selection template

template <bool ENABLE, typename P, typename T> struct EnableBase;
template <typename P, typename T> struct EnableBase<true, P, T>
{
  typedef P Type;
};
template <typename P, typename T> struct EnableBase<false, P, T>
{
  typedef EmptyBase<P, T> Type;
};

// Particle template class

template
<
  bool USE_SPEED = false,
  bool USE_SIZE = false,
  bool USE_ACCELERATION = false,
  typename T = ParticleMemberTypes<>
>
struct Particle :
  public EnableBase<USE_SPEED, PSpeed<T>, T>::Type,
  public EnableBase<USE_SIZE, PSize<T>, T>::Type,
  public EnableBase<USE_ACCELERATION, PAcceleration<T>, T>::Type
{
};

We can now do:

using namespace std;

Particle<> p1;
Particle<true, true, true, ParticleMemberTypes<Vector3<double> > > p2;

cout << "p1: " << sizeof(p1) << endl;
cout << "p2: " << sizeof(p2) << endl;

Output:

p1: 2
p1: 32

So here are my questions:

  • Is this a reasonable approach to automatically reducing the size of a class?
  • If I only inherit from two Properties the size of Particle is 1, beyond that the size increases by one for each additional EmptyBase, why is that?
  • Are there any patterns, idioms, etc. that would be useful here?

The plan is to write templates to automate the processing of particles based on what properties are present.

I should probably mention that this particle system i'm working on is not "realtime", will be dealing with massive amounts of particles, and that I will be configuring each rendering from C++. Also, this is pretty much my first go at using templates.

EDIT: There are basically two reasons why I've chosen the template approach: one is curiosity - simply to learn about templates and explore their use. The second reason is speed. Seing as I won't need to change anything at run-time, I figured I could use templates to remove the overhead of virtual functions and unused class members, etc.

The intended use is to create a bazillion particles, all of the exact same type, and process and render them, as fast as I can possibly make the code go. :)

The idea is to have a highly configurable system, where I can plug in custom functors to process the particles. Ideally the properties of a particle would be enabled only if they're actually used, but I haven't figured out if and how that's possible.

+1  A: 

One thing, you use USE_SIZE twice in your template definition for Particle.

You might also look into using the flyweight pattern, depending on repetitiveness of the data. http://www.boost.org/doc/libs/1_39_0/libs/flyweight/doc/tutorial/index.html

PiNoYBoY82
Thanks, now corrected
Ernst Hot
+2  A: 

You are aware that the types of templates with different parameters are different? That is, in your code p1 and p2 are of different types and so cannot be stored in the same collection, assigned to each other etc. Of course, your application may not require such equivalence.

anon
Absolutely, I use that fact to create different EmptyBase<...> types, to avoid multiple inheritance from the same class.
Ernst Hot
+3  A: 

Hmm, first, you seem to be rolling a lot of your own stuff. I'd look into Boost::MPL to replace, say Base selection template, and to inherit over a vector with inherit.

Second, you are using "const static bool hasSpeed = true;" a lot; In my programing, I usually prefer a typedefed trait, similar to Boost::type_traits. You can use them to select functions to run at compile time. You could avoid doing that "EmptyBase".

template <typename T>
struct has_size;

template <bool speed, bool accel, typename T>
struct has_size< Particle<true, speed, accel, T> > : public true_type
{ };

template <bool speed, bool accel, typename T>
struct has_size< Particle<false, speed, accel, T> > : public false_type
{ };


// given a particle typedef SomeParticle_t

has_size<SomeParticle_T>::value

Third, a lot of what you are doing here depends on what you want your end usage to be; do you want the particles to be separate, types that can't be converted to each other? If you go this route, pretty much every function will be a template, and you could use boost::enable_if to disable code that doesn't apply to a given particle. It will be potentially very fast (since a lot of work happens at compile time), but you'll have gigantic, hard to read error statements (not very accessible for someone new to templates).

Another, non template route would be inheritance; you would define a set of virtual functions that a particle needs, and then break those out into classes you inherit from. It's not quite clear from your code what you expect to be able to do with the Particle. The errors would be easier to understand, but the code potentially slower (more work shifted to runtime, and would make your objects bigger)

Here's a code sample to illustrate the differences:

// Using templates
template <typename particle>
typename boost::enable_if< has_accel<particle>, typename particle::speed_type >::type
calculate_future_speed(const particle& t)
{ /* do your acceleration calculation */ }

template <typename particle>
typename boost::enable_if< boost::and_< has_speed<particle>,
                                       boost::not_< has_accel<particle> >,
                         typename particle::speed_type >::type
calculate_future_speed(const particle& t)
{ /* no acceleration, so no calculation! just return the speed*/ }

template <typename particle>
typename boost::enable_if< boost::not_< has_speed<particle>,
                         typename particle::speed_type >::type
calculate_future_speed(const particle& t)
{ return 0; /* Has no speed, and no acceleration */ }
Todd Gardner
Good points.I do realize that Boost offers a lot of what I'm trying to do here and that using Boost would probably be the more productive route. On the other hand I think I will learn more from trying to figure out how to do it myself first.About type traits, that's another good point, and it does seem to be the prefered way of doing it, I'll consider that a bit more. (I guess I still need to figure out exactly why it's the prefered way. :)I'll edit the question to clarify why I choose to use templates.
Ernst Hot