views:

214

answers:

4

Hey Folks,

I have a program which is built on "Entities", which hold "Components" (composition FTW).

Components may include many different types including scripts, assets, etc. I would like to build an Entity function called

Entities have a map of strings, and actual Components, such that the Components can be searched for by type name.

I would like to have a function called

<Component T>GetComponent(char* TypeName, <T>);

Which takes in a string and a type name, and which returns the typed component that is requested.

Is it possible to do such a thing with C++ templates? The above clearly does not work, and I'm not sure how to go about it.

Thanks

Edit:

I'm not looking for a factory.

Entity holds instances of different types of components. Currently this is done with

std::vector<Component> componentList;

and an

std::vector<char*> componentNames;

Whose indexes are guaranteed to be the same. Likely I will write a proper map later.

I simply want GetComponent to return a properly typed reference to the already instantied component of type name held by Entity in the ComponentList.

+10  A: 

Does your function create components? Then it is a factory. You could wrap it in that template in order to save clients the (potentially erroneous) casting.

The type of the function template would look like this:

template< typename T >
T* GetComponent(const char*); // presuming it returns a pointer

and it would be called like this:

Foo* foo = GetComponent<Foo>("foo");
sbi
Make it `char const *` and you get an upvote. :)
avakar
Dammit, copied without thinking! Thanks.
sbi
No, its not a factory. Entity holds instances of different types of components. I simply want GetComponent to return a properly typed reference to the component held by Entity.
MJewkes
The answer still works whether it's a factory or not. The point is just that you can have a template parameter not mentioned in the function parameters, but you have to specify the template parameter using `GetComponent<Foo>`, since it can't be deduced.
Steve Jessop
Thanks guys, this is what I need. Sorry for being dense.
MJewkes
A: 

Your getComponent function has two separate tasks

1) retrieve an object from a string identifier

2) cast this object into the provided template argument type

Using templates makes (2) pretty straight forward. But (1) needs work on string objects, so templates won't do the trick on their own. You have got to fill your component container some other way. As for storing and casting, you may be interested in boost::any or boost::variant.

Benoît
+1  A: 

You could use something like:

struct Entity
{
    Component* GetBaseComponent (const char* TypeName)
    {
        // do lookup on TypeName and return corresponding Component.
    }

    template<typename T> T* GetComponent (const char* TypeName)
    {
        return dynamic_cast<T*> (GetBaseComponent (TypeName));
    }
};

and call it with something like:

entity.GetComponent<MyComponent> ("MyComponent");

If you ask for a component and get the type wrong the cast will return a null ptr.

Edit: Just realised this is essentially the same solution as sbi, albeit without calling it a factory.

jon hanson
+1  A: 

Asking the proper question is at least half of the way to getting a good answer. You should really state what you want to achieve rather than the particular problem you are facing. It seems to me as if you have more problems with the language than you actually realize.

The first part is that it seems as if you have a component hierarchy that derives from Component, probably providing a common interface. An entity holds many components internally, that can be of any derived type from Component. If that is the case, you should rework your containers as you are storing Component objects directly, and that will produce slicing in your objects (no matter what derived type you enter into the container, the container will only keep the common Component part of the object).

Working on a couple of vectors and hoping that both of them will be synchronized at all times is feasible but fragile. If the name and the component go together, then you want to store pairs of name/component. If you want to search by name, you should use a map as it will provide O(log N) search directly.

Now, going back to the question. If what you want to achieve is plain syntactic sugar (avoid the caller from explicitly dynamic casting if needed) then you can get it with a template (more later on). But you should really think on your design. Does Component define the real interface into any component? If users need to downcast to particular types before using a Component, either the abstraction is bad (Component does not provide a real interface) or the objects do not really fit together.

If at the end of it you still want to do it, you can hide the dynamic cast from the caller by doing it within a template method (or free function).

class Entity {
   typedef std::map< std::string, boost::shared_ptr<Component> > component_map_t;
public:
   boost::shared_ptr<Component> getComponent( std::string const & name ) {
      component_map_t::iterator it = components_.find(name);
      if ( it == components_.end() ) { // not found, handle error
         // ...
      }
      return it->second;
   }
   template <typename T> // syntactic sugar
   boost::shared_ptr<T> getComponent( std::string const & name ) {
      return boost::dynamic_pointer_cast<T>( getComponent(name) );
   }
private:
   component_map_t components_;
};
template <typename T> // syntactic sugar also available as free function
boost::shared_ptr<T> getComponent( Entity & entity, std::string const & name ) {
   return boost::dynamic_pointer_cast<T>( entity.getComponent(name) );
}
int main() { // usage
   Entity e; // ... work with it add components...
   boost::shared_ptr<Component> c1 = e.getComponent( "one" ); // non-templated method returns Component
   boost::shared_ptr<DerivedComponent> c2 = e.getComponent<DerivedComponent>( "two" );
   boost::shared_ptr<DerivedComponent> c3 = getComponent<DerivedComponent>( e, "two" );
}

You could play with the interface so that instead of boost::shared_ptr you return real references (with what it entails: lifetime must be carefully controlled so that user code does not try to use a dangling reference if the component is removed from the entity).

David Rodríguez - dribeas
In the typical use case for this pattern in game development, there's usually no significant shared interface or hierarchy among the various component types. The entity class merely exists to aggregate components and the caller must (and does) know exactly which concrete component type it wants to work on in each case. The reason the components aren't just regular members of entity is because their presence or absence is determined at run-time from data.
Kylotan