user6556145
user6556145

Reputation:

Use of templates

I am learning C++ by programing a game and I am stuck with something: I have a code which is starting to be repetitive (I will add a number of other components) and I would like to improve it using templates (I guess), which I don't really know how to use.

Here's the code:

#include <memory>

#include "PositionComponent.h"
#include "VelocityComponent.h"

class ComponentManager
{
public:
    ComponentManager();

    void addComponent(Entity id, PositionComponent component);
    void addComponent(Entity id, VelocityComponent component);

private:
    std::map<Entity,std::shared_ptr<PositionComponent> > m_positionComponents;
    std::map<Entity,std::shared_ptr<VelocityComponent> > m_velocityComponents;
};

and

#include "ComponentManager.h"

ComponentManager::ComponentManager()
{

}

void ComponentManager::addComponent(Entity id, PositionComponent component)
{
    if (m_positionComponents.count(id) > 0)
    {
        Tools::log("couldn't add component");
    }
    // else if entity doesn't exist..
    else
    {
        m_positionComponents[id] = std::make_shared<PositionComponent>(component);
    }
}

void ComponentManager::addComponent(Entity id, VelocityComponent component)
{
    if (m_velocityComponents.count(id) > 0)
    {
        Tools::log("couldn't add component");
    }
    // else if entity doesn't exist..
    else
    {
        m_velocityComponents[id] = std::make_shared<VelocityComponent>(component);
    }
}

How to make that beautiful with templates?

Thanks!

Upvotes: 0

Views: 141

Answers (3)

SirGuy
SirGuy

Reputation: 10780

I think using a template here is fine, and other options definitely exist. Here's the template solution I would try:

template <class T>
void ComponentManager::addComponent(Entity id, T component)
{
    using MapType = std::map<Entity,std::shared_ptr<T> > &;
    auto allMaps = std::tie(m_positionComponents, m_velocityComponents);
    auto & tMap = std::get<MapType>(allMaps);
    if (tMap.count(id) > 0)
    {
        Tools::log("couldn't add component");
    }
    // else if entity doesn't exist..
    else
    {
        tMap[id] = std::make_shared<T>(component);
    }
}

This isn't tested at all, so I don't promise it will compile as is.

The overload of std::get used here requires c++-14.

Upvotes: 1

R Sahu
R Sahu

Reputation: 206717

How to make that beautiful with templates?

Solution 1

Add a private member function template to ComponentManager.

template <typename Component>
  void addComponent(Entity id,
                    Component component,
                    std::map<Entity,std::shared_ptr<Component> >& components)
  {
     if (components.count(id) > 0)
     {
        Tools::log("couldn't add component");
     }
     // else if entity doesn't exist..
     else
     {
        components[id] = std::make_shared<Component>(component);
     }
  }

and use it as:

void ComponentManager::addComponent(Entity id, PositionComponent component)
{
   this->addComponent(id, component, m_positionComponents);
}

void ComponentManager::addComponent(Entity id, VelocityComponent component)
{
   this->addComponent(id, component, m_velocityComponents);
}

The drawback of this approach is that for every type of Component you want to support, you'll need to add another public member function.

Solution 2

You can have only one public member function template to add components to the appropriate collection. Then you'll need to add code to get the right collection given the component type. One way to that would be to use a tag dispatch mechanism.

class ComponentManager
{
   public:
      ComponentManager();

      template <typename Component>
         void addComponent(Entity id, Component component)
         {
            std::map<Entity,std::shared_ptr<Component> >& components = getCollection(tag<Component>());
            if (components.count(id) > 0)
            {
               Tools::log("couldn't add component");
            }
            // else if entity doesn't exist..
            else
            {
               components[id] = std::make_shared<Component>(component);
            }
         }

   private:

      template <typename Component> struct tag {};

      std::map<Entity,std::shared_ptr<PositionComponent> >& getCollection(tag<PositionComponent>)
      {
         return m_positionComponents;
      }

      std::map<Entity,std::shared_ptr<PositionComponent> >& getCollection(tag<VelocityComponent>)
      {
         return m_velocityComponents;
      }

      std::map<Entity,std::shared_ptr<PositionComponent> > m_positionComponents;
      std::map<Entity,std::shared_ptr<VelocityComponent> > m_velocityComponents;
};

With this approach, you will still need to add another member function to the class for each type of Component you wish to support but they will be in the private section of the class, not public.

Upvotes: 0

Jarod42
Jarod42

Reputation: 218268

You may create a template class that handles one generic type

template <typename T>
class ComponentManagerT
{
public:
    void addComponent(Entity id, T component)
    {
        if (m_components.count(id) > 0)
        {
            Tools::log("couldn't add component");
        }
        // else if entity doesn't exist..
        else
        {
            m_components[id] = std::make_shared<T>(component);
        }
    }

    // ...
private:
    std::map<Entity, std::shared_ptr<T>> m_components;
};

And a class to regroup all those managers

struct ComponentManager
{
    ComponentManagerT<PositionComponent> m_positionComponentManager;
    ComponentManagerT<VelocityComponent> m_velocityComponentManager;
};

Upvotes: 0

Related Questions