c++structcasting

C++ cast struct dynamically based on subtype


So I am currently attempting to create my own enitity component architecture and I am having some issues.

I am storing my components as a struct, eg

struct BaseComponent
{
  bool isValid;
}

struct ComponentA : BaseComponent
{
  int someValForA;
}

struct ComponentB : BaseComponent
{
  int someValForB
}

ComponentB* compB = new ComponentB()
compB->someValForB = 10;
BaseComponent* baseComp = compB

ComponentB* comp = (ComponentB*) baseComp

I would like my system to be able to store the structs of varing inheritance. so I would need to use a vector of pointers. The problem is, how do I cast them back to their origional derived struct dynamically without knowing their origional subtype? Can I determine their derived type through code without an enum since I want to implement this in a library.

I will also take answer that also give alternate approaches to implementing this system, keeping in mind that I want to develop it. Please be specfic and give a code example if possible to help.

Thank-you for reading :)

PS. This is a repost of another question I uploaded today. It was closed as a duplicate question, yet the duplicate didn't even come close to answering my question. I request that you understand the exactness of my question by talking to me through comments instead of preventing anyone else from helping. Thanks.


Solution

  • Rather than throwing out type information, then checking whether every component is the one you're looking after, I suggest you use a container that maps a type to its component (or tells you it doesn't have it).

    using TypeId = unsigned int;
    
    namespace detail_typeId {
        TypeId idCounter = 0u;
    }
    
    template <class T>
    TypeId const idFor = detail_typeId::idCounter++;
    

    This trick uses the side-effect in the initialization of the specializations of idFor<T> to provide a unique identifier value for each type, that can be used as a key. You could also use std::type_index, but that forces you to have polymorphic classes as components. This approach also has the advantage of producing contiguous integer identifiers, which span the range [0, idCounter - 1].

    struct Component {};
    

    A base class for your components.

    struct Entity {
    
        template <class T, class... Args>
        void addComponent(Args &&... args) {
            if(!comps.emplace(
                idFor<T>, std::make_unique<T>(std::forward<Args>(args)...)
            ).second)
                throw std::runtime_error("Component already present.");
        }
    
        template <class T>
        T *getComponent() {
            auto found = comps.find(idFor<T>);
            return found == end(comps)
                ? nullptr
                : static_cast<T*>(found->second.get());
        }
    
        std::map<TypeId, std::unique_ptr<Component>> comps;
    };
    

    Here we see the actual storage of the components, as well as two convenience functions to access them. The map allows us to retrieve any component based on its type.

    Example usage with three user-defined components :

    struct CompA : Component { int attribA; };
    struct CompB : Component { int attribB; };
    struct CompC : Component { int attribC; };
    
    int main() {
        Entity e;
        e.addComponent<CompA>();
        e.addComponent<CompB>();
    
        if(CompA *c = e.getComponent<CompA>()) {
            std::cout << "Retrieved component A\n";
            c->attribA = 42;
        }
    
        if(CompB *c = e.getComponent<CompB>()) {
            std::cout << "Retrieved component B\n";
            c->attribB = 42;
        }
    
        if(CompC *c = e.getComponent<CompC>()) {
            std::cout << "Retrieved component C\n";
            c->attribC = 42;
        } else {
            std::cout << "Didn't retrieve component C\n";
        }
    }
    

    Output:

    Retrieved component A
    Retrieved component B
    Didn't retrieve component C

    Live on Coliru