Search code examples
c++templatesinheritancepolymorphismvirtual

Assigning a template value to a class template via a pointer to its non-template parent class


I'm trying to make a C++ Template class that can store a template value. However, I need to create pointers to this class before the type of the template value is known. To do this, I created an abstract Base class that the template class inherits from. I create pointers to Base, and when they are allocated, I use basePtr = new Template<TYPE>.

The problem here is in assigning a value to Template. Every way I can think of to do so (I'd LIKE to use an overloaded assignment operator) requires a method with a template datatype as a formal parameter. Because Template objects can only be accessed through Base pointers, I have to make a virtual function in Base. However, virtual methods cannot contain template datatypes, and the virtual method in Base's signature must match that of Template's method.

Here's an example of what I'm going for:

class Base {
    public:
        /* THIS IS ILLEGAL - can't have template virtual method
        template <class V>
        virtual void operator =(const V& newValue) = 0;
        */
};

template <class ValueType>
class Template : public Base {
    private:
        ValueType *value;
    public:
        void operator =(const ValueType& newValue) {
            *value = newValue;
        }
};

int main() {
    Base *theObject;                  // Datatype for template not known 
    theObject = new Template<string>; // At this point, it will be known

    // NOW, THIS DOESN'T WORK - no virtual assignment overload in Base
    *theObject = "Hello, world!";

    return 0;
}

I apologize if I'm going about this the entirely wrong way, and if my method is stupid--this is my first foray into true OOD. Is there a way around this problem that I'm not seeing? I know I could create a long list of pure virtual functions in Base that overload the assignment operator with different input types, like so:

virtual void operator =(const string& newValue) = 0;
virtual void operator =(const int& newValue) = 0;
virtual void operator =(const long& newValue) = 0;
...

But, I want the user to be able to insert custom class objects (or, more likely, pointers to those objects) into Template::value, and I there's no way that can be done with the above methodology.


Solution

  • One way of doing what you are trying to achieve is to use Boost.Any (a well-known header-only library). Below is a demonstration. The code is commented step-by-step, so you should be able to understand it, and here is a live example:

    #include <stdexcept>       // Standard header for exception classes
    #include <string>          // Standard header for std::string
    #include <boost/any.hpp>   // The Boost.Any library header
    #include <iostream>        // This program prints some output
    
    class Base
    {
    public:
        virtual void operator = (boost::any val) = 0;
        //                       ^^^^^^^^^^
        //                       This can hold (almost) "any" value
    
        // You need a virtual destructor if you want to delete objects
        // of subclasses of this class through a pointer to this class!
        virtual ~Base() { }
    };
    
    template <class ValueType>
    class Template : public Base
    {
    private:
        ValueType value;
    //  ^^^^^^^^^
    //  I do not see why using a pointer in this case. Manual memory
    //  management just complicates things. However, if your really
    //  need to do it and your really know what you're doing, go ahead.
    //  Just remember to delete your pointer at destruction and not to
    //  dereference it before it points to an allocated object (in your
    //  original text, both of these things are NOT done correctly).
    public:
        virtual void operator = (boost::any val)
        {
            // Attempt a cast to detect if the value we are trying to
            // assign to this object is of the appropriate type...
            ValueType* pVal = boost::any_cast<ValueType>(&val);
            if (pVal == nullptr)
            {
                // ...it is not! Throw an exception...
                throw std::logic_error("Incompatible type");
            }
    
            // The value is OK: assign it...
            value = *pVal;
        }
    };
    
    int main()
    {
        Base *theObject;
        theObject = new Template<std::string>;
    
        try
        {
            // This assignment will succeed...
            // Wrapping the string literal in a std::string object is necessary
            // because boost::any cannot be initialized from an array (and in C++
            // string literals are arrays of characters).
            *theObject = std::string("Hello, world!");
    
            // This assignment will fail!
            *theObject = 1;
        }
        catch (std::logic_error const& e)
        {
            // Handle the exception...
            std::cout << e.what();
        }
    
        delete theObject; // <=== DON'T FORGET THIS!
    
        return 0;
    }