Search code examples
c++templatescastingpolymorphismderived-class

Multi-type container C++. Casting to derived template class


I am trying to implement a multi-type container in C++ without using std::any, std::variant, boost::any, etc. The add() function adds new objects (int, string, or other Structures) by wrapping them in the template element class and storing as Structure pointers:

using StructPtr = std::shared_ptr<Structure>;

class Structure{
    public:
        Structure() = default;
        Structure(const Structure& oldStruct);
    
        Structure operator = (Structure otherStruct);
        bool operator == (const Structure& otherStruct);
    
        template<class T>
        void add(T obj){
            elements.emplace_back(std::make_shared<Element<T>>(obj));
        }
    
        void print(std::ostream& out);
        
        std::vector<StructPtr> elements;
};
    
template<class T>
class Element : public Structure{
    public:
        Element() = default;
        Element(T _element) : element(_element) { }
        Element( Element& oldElement){
            element = oldElement.element;
        }
    
        T element;    
};

I have a print function that takes in an element and prints it:

template<class T>
void printElement(std::ostream& out, const Element<T>& element){
    printElement(out, element.element); //this functionality has been provided already.
}

I want to go through each element in the vector and pass it to this print function. However, since they are stores as StructPtrs, I do not know how to cast them into this templated Element class (I cannot use dynamic_cast).

This is what I tried:

template<class T>
void printElement(std::ostream& out, const std::vector<StructPtr>& elements){
    for(auto element : elements){
        auto elementDerived = static_cast<Element<T>>(*element);
        printElement(out, elementDerived);
    }
}

void printElement(std::ostream& out, const Structure& s){
    printElement(out, s.elements); 
}

But this gives an error:

no instance of overloaded function "printElement" matches the argument listC/C++(304)
task01.cpp(64, 5): argument types are: (std::__1::ostream, const std::__1::vector<StructPtr, std::__1::allocator<StructPtr>>)

So, my main question is, how do I call:

template<class T>
void printElement(std::ostream& out, const Element<T>& element)

on each element of the vector?


Solution

  • You can't perform a static_cast inside of printElement() since you don't know what to cast to (dynamic_cast would have helped you with that), so the only solution is to make Structure::print() be virtual and have Element override it, eg:

    class Structure{
        public:
            ...        
            virtual void print(std::ostream& out) const {
                printElement(out, elements);
            }
            ...
    };
        
    template<class T>
    class Element : public Structure{
        public:
            ...    
            void print(std::ostream& out) const override {
                printElement(out, element);
            }
            ...
    };
    
    void printElement(std::ostream& out, const std::vector<StructPtr>& elements){
        for(auto element : elements){
            element->print(out);
        }
    }