Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Could someone help me create a variable container using Boost::MPL?

I have created a physics system that handles any collision object to any collision object like so:

namespace Collision
{
    template <typename T, typename U>
    inline void Check(T& t, U& u)
    {
        if(u.CheckCollision(t.GetCollider()))
        {
            u.HitBy(t);
            t.Hit(u);
        }
    }
}

and there are several other helper objects to make it easy to use, but the gist is that there are dynamic objects that need to be tested against static objects and other dynamic objects, but static objects don't need to be checked.

What I would like is something like this:

void func()
{
    PhysicsWorld world;
    shared_ptr<CSphere> ballPhysics(new CSphere(0,0,ballSprite->Width()));
    BallCommand ballBehavior;
    CBounds bounds(0, 0, 640, 480);
    CBox obstacle(200, 150, 10, 10);

    Collision::Collidable<CBounds> boundC(bounds);
    Collision::Collidable<std::shared_ptr<CSphere>, BallCommand&> ballC(ballPhysics, ballBehavior);
    Collision::Collidable<CBox> obstC(obstacle);

    world.addStatic(boundC);
    world.addDynamic(ballC);
    world.addStatic(obstC);
    ...
    ...
    world.Update();
    ...
    ...
}

I'd love to deduce the containers through the add functions so using the system automatically updates the type lists. I think I get how to generate a typelist with a template function, but not how to then get it where I need it, or at what point in compilation it is complete.

If not that then some system using two typelists that then internally writes the update function to iterate through all the lists pairing them up against each other.

I've read some of the boost MPL book and read Andrei's book several times. But, I seem to get caught up in the how it works stuff and don't really translate that into how do I use it. I wish they had one more section on real world examples in the MPL book.

I've been able to get all of the pieces of a game engine to interact with rendering, physics, collisions (I separate detection from reaction), input, network, sound, etc. All in generic ways. Now I just need to hold all the things in a generic way. After all that generic work, it would be silly to require inheritance just so I can hold something in a container and I don't want to hand code every collection possibility as that is one of the great benefits of generic programming.

I saw Jalf had indicated that s/he used MPL to do something similar, but did not go into details enough for me to figure it out. If anyone knows a practical use example or where I can get more info on using the MPL I'd be grateful.

Thanks again!

Update

boost MPL and boost Fusion both seem to do what I want, but there appears to be very little in the way of good real life examples of either libraries. The documentation for MPL is little more than this template does this and good luck understanding the implications of that. Fusion is a bit better with "Here's an example but it's just the tip of the iceberg!"

A typical boost MPL example is has_xxx. They use XXX and xxx in the example making it difficult to see the difference where XXX(The required text) and Test or CheckType or any more distinguishable user type could be used in place of xxx. Plus there is no mention that none of this is in a namespace. Now I know why Scott meyers compared this to the shower scene in Psycho.

It's a shame really because what little I have gotten to compile and understand does really useful things, but is so hard to figure out I would never spend this much effort if I was on a shipping product.

If anyone knows real world examples or better references, explanations, or tutorial I would be grateful.

Update

Here's more code:

template <typename T, typename V = VictimEffect, typename M = MenaceEffect>
class Collidable
{
    T m_Collider;
    V m_HitBy;
    M m_Hit;

public:
    Collidable(T collide, V victim, M menace) : m_Collider(collide), m_HitBy(victim),         m_Hit(menace) {;}
    Collidable(T collide) : m_Collider(collide) {;}
    Collidable(T collide, V victim) : m_Collider(collide), m_HitBy(victim) {;}

    T& GetCollider()
    {
        return m_Collider;
    }

    template <typename V>
    void HitBy(V& menace)
    {
        m_HitBy.HitBy(menace.GetCollider());
    }

    template <typename V>
    void Hit(V& victim)
    {
        m_Hit.Hit(victim.GetCollider());
    }

    template <typename V>
    bool CheckCollision(V& menace)
    {
        return m_Collider.CheckCollision(menace);
    }
};

Then to use it I do this

    Collidable<Boundary, BallCommand> boundC(boundary, ballBehavior);
    Collidable<CollisionBox> ballC(circle);

Then all I need is to call collide with all my active collidable objects against all my active and passive objects.

I'm not using std::function because the addition of function names makes the code clearer to me. But maybe that's just legacy thinking.

like image 931
Tavison Avatar asked Apr 26 '11 07:04

Tavison


1 Answers

If I understand correctly your problem is:

class manager {
public:
    template<typename T>
    void add(T t);

private:
    /* ??? */ data;
    /* other members? */
};

manager m;
some_type1 s1;
some_type2 s2;
m.add(s1);
m.add(s2);
/* m should hold its copies of s1 and s2 */

where some_type1 and some_type2 are unrelated and you're unwilling to redesign them to use dynamic polymorphism.

I don't think either MPL or Fusion will do what you want with this form. If your problem is what container to use as a member of PhysicsWorld, then no amount of compile-time computations will help: the member type is determined at instantiation time, i.e. the line manager m;.

You could rewrite the manager in a somewhat meta-programing fashion to use it this way:

typedef manager<> m0_type;
typedef typename result_of::add<m0_type, some_type1>::type m1_type;
typedef typename result_of::add<m1_type, some_type2>::type final_type;
/* compile-time computations are over: time to instantiate */
final_type m;
/* final_type::data could be a tuple<some_type1, some_type2> for instance */
m.add(s1); m.add(s2);

This is indeed the sort of things MPL+Fusion can help with. However this still remains quite anchored in the compile-time world: can you imagine writing an template<typename Iter> void insert(Iter first, Iter last) just so you can copy the contents of a container into a manager?

Allow me to assume that your requirements are such that in fact the manager has to be used in a much more runtimey fashion, like in my original formulation of your question. (I don't think that's quite a stretch of the imagination for a PhysicsWorld). There is an alternative, which I think is more appropriate, much less verbose and more maintanable: type-erasure. (The name of the technique may be a bit unfortunate and can be misleading the first time.)

A good example of type-erasure is std::function:

std::function<void()> func;
func = &some_func; /* this just looks like that internally std::function stores a void(*)() */
func = some_type(); /* but here we're storing a some_type! */

Type-erasure is a technique to bridge compile-time with runtime: in both assignments above, the arguments are unrelated types (one of which is non-class so not even remotely runtime polymorphic), but std::function handles both, provided they fulfill the contract that they can be used as f() (where f is an instance of the respective type) and that the expression has type (convertible to) void. The contract here is the compile-time aspect of type-erasure.

I'm not going to demonstrate how to implement type-erasure because there is a great Boostcon 2010 presentation on the subject. (You can watch the presentation and/or get the slides through the link). Or I (or someone else) can do it in the comments.

As a final note, implementation of type-erasure (typically) uses dynamic polymorphism. I mention that because I noticed you considered the use of typelists as a runtime object stored as a manager member. This smells like poor man's reflection, and really, poor man's dynamic polymorphism. So don't do that please. If you meant typelists as in the result of a MPL computation then disregard the node.

like image 128
Luc Danton Avatar answered Oct 05 '22 23:10

Luc Danton