Search code examples
c++templatesinheritance

How to protect a templated derived class after being cast from a base class having the incorrect template type being used?


I'm trying to write a container that handles a vector of ObjArray's which is a templated class. ObjArray inherits from a base class and the container holds a list of base pointers which I cast to the right type when manipulating. How do I make this as failsafe as possible for the caller?

A code snippet below shows what I'm trying to do with comments about what could go wrong.

#include <iostream>
#include <vector>
#include <memory>

struct CustomA{
    int a;
};

struct CustomB{
    float b;
};

class IObjArray{
    public:
    virtual void RemoveObj(int obj_id) = 0;
    virtual ~IObjArray() = default;
};

template<typename T>
class ObjArray : public IObjArray{
    public:
    void RemoveObj(int obj_id) override {
        std::cout << "Removing object of id" << obj_id << "\n";
        // Mechanism for removing object from vector below...
    }
    void AddT(T obj){
        vec_of_T_.push_back(obj);
    }
private:
    std::vector<T> vec_of_T_;
};

class ObjManager{
    public:
    template<typename obj>
    size_t RegisterObj(){
        list_.emplace_back(std::make_unique<ObjArray<obj>>());
        return list_.size() - 1;
    }
    
    template<typename T>
    void AddObj(size_t obj_array_id, T obj){
        static_cast<ObjArray<T>*>(list_[obj_array_id].get())->AddT(obj);
    }
    private:
    std::vector<std::unique_ptr<IObjArray>> list_;
};

int main()
{
    ObjManager om;
    auto id_a = om.RegisterObj<CustomA>();
    auto id_b = om.RegisterObj<CustomB>();
    CustomA a{0};
    om.AddObj(id_a, a);
    om.AddObj(id_b, a); // This compiles but is bad. Anyway to stop this happening? Registering a CustomA type in a CustomB ObjArray.
    // om.AddObj<CustomB>(id_b, a); // This doesn't compile because of type mismatch
    return 0;

Demo

I could use some form of typeid(T) to add to a map when objects are registered and then whenever AddObj is called it could typeid(T) as a key into the map and pull the correct ObjArray. I'd rather use an ID based system thought to index into the array and to keep this as fast as possible. I also can't add the methods AddObj to the IObjArray interface because they're templated.

Is it possible to fix these issues or is it just important to make the caller aware?

Thanks!


Solution

  • If id_a and id_b can be of different type you can wrap the size_t index into a class template that is tagged with the type of the element:

    template <typename T>
    struct Index {
        size_t value = 0;
        using type = T;
    };
    
    class ObjManager{
        public:
        template<typename obj>
        Index<obj> RegisterObj(){
            list_.emplace_back(std::make_unique<ObjArray<obj>>());
            return {list_.size() - 1};
            //     ^ wrap the index
        }
        
        template<typename T>
        void AddObj(Index<T> obj_array_id, T obj){
            static_cast<ObjArray<T>*>(list_[obj_array_id.value].get())->AddT(obj);
            //                                           ^ unwrap it
        }
        private:
        std::vector<std::unique_ptr<IObjArray>> list_;
    };
    

    Then here:

    om.AddObj(id_a, a);
    om.AddObj(id_b, a);    // error, as it should 
    

    first line compiles while the second does not, because the Index type of id_b does not match that of b.

    Live Demo