Gultekin Yegin
Gultekin Yegin

Reputation: 74

How to access member funtions of a derived Template class if we have a base* class

I have a template class derived from a base class.

Also in main, I have a vector container which contains various types of the derived class. Without knowing the type of the Derived class, how can I access its member functions in main?

Template specializition could be a good solution to this problem. But, how can I do that?

class Base{ // The base class of a template class
public:
    Base(){}
    virtual ~Base(){}
};

template <typename T>
class Derived : public Base{ // The template class,
                             // derived from the base class.
    T data;
public:
     Derived(){}
    ~Derived(){}

       T  get()       { return data; }
     void set( T val) { data = val;  }
};

int main() {

    vector <Base*> myObject;
    // I have different types of Derived class
    // in a vector conteiner.

    Derived<int>  *D1=new Derived<int>;
    Derived<float>*D2=new Derived<float>;
    Derived<char> *D3=new Derived<char>;

    myObject.push_back(D1);
    myObject.push_back(D2);
    myObject.push_back(D3);

    for (int i=0;i<myObject.size();i++){
         // myObject[i]->set(4);  Error! : myObject is a vector of <Base*>
         // myObject[i]->get();   Error! : not a vector of <Derived*>
    }

  return 0;
}

Upvotes: 1

Views: 193

Answers (3)

Mr D
Mr D

Reputation: 106

The exact behavior you want is not possible by standard means of c++. This design is not polymorphic and you don't have any type information inside your objects. Also, it seems that you don't have common interface for your objects either (in c++ context) since all functions operate on different types.

This seems to be an implementation of a Variant type and you want to store different values (int, string, float, whatever) inside one container.

The simplest way you can fix this is to add a type filed to your base class and assign it with specific value for each type inside derived classes. However, you should manually check it and cast to desired actual time every time you want to access your actual values.

enum ValueType
{
    Bool, Int, Float
};

class Base
{
protected:
    ValueType _type;

public:
    ValueType type() const { return _type; }
};

// usage example
Base *val = vector[something];
switch (val->type()) {
    case Int: { int intvalue = Derived<int>(val)->get(); }
}

The other way is to make common interface for get/set by having them return/accept some common type which can represent value of any type, like string and use inheritance and virtual functions as usual without templates. Drawback for this implementation would be cost of string handling and storage.

class Base
{
public:
    virtual string get() const = 0;
    virtual void set(const string &newval) = 0;
};

class IntValue : public Base
{
public:
    /* imagine we have that magic IntToStr function */
    string get() const override { return IntToStr(_value); }

private:
    int _value;
};

I believe most efficient way would be to have all possible getters and setters in base class and override them into specific derived classes. With this you get unified interface and can put additional handy value conversions (like reading int as char or bool or string).

class Base
{
public:
    virtual bool getBool() const = 0;
    virtual int getInt() const = 0;
    // etc for other types getters and setters
};

class IntValue
{
public:
    bool getBool() const override { return _value != 0; }
    int getInt() const override { return _value; }
    // for incompatible types - throw error or return default value
};

There's other way to make this as fast as possible, but also as ugly as possible is to have templated get/set versions and their implementation (without virtual) specialized for each type and each possible variant type. With this you must keep internal type variable which will hold actual value type. In that case you don't need inheritance at all.

Upvotes: 1

Silvio Mayolo
Silvio Mayolo

Reputation: 70267

The trouble is in the way you implement your types. When you declare a std::vector<Base*>, C++ prepares to store objects that are capable of Base functionality and nothing more. When you put the three objects into the vector, C++ forgets what their underlying types are. So when you call myObject[i]->set(4), the system can't possibly know which set to call.

Normally, the solution would be to make virtual methods in the base class. As you've mentioned, however, you can't do that because you have a template type in get and set. The next idea would be to implement a templated base class and have Derived<T> subclass Base<T>. This would solve the problem but it would create a new one: You can't have a vector of heterogeneous objects.

So I think fundamentally, you need to think about what you're trying to do. Here are some questions to ask yourself.

  1. Do you know up-front how many objects and which ones are going to be stored? If you know the number and types of the objects at compile time, consider an std::tuple of Derived objects.

  2. Since you set all of the objects to the integer 4, do you know for a fact that all of the object types will be convertible to/from integers? If so, you can write get_int and set_int as virtual functions in the base class and call those, which can delegate to get and set in the child class.

  3. If you don't know how many objects you'll need up front but you do know that they all come from a limited set of objects, consider an std::vector of std::variant (or boost::variant, if you don't have access to C++17).

  4. If you really need all of the generality, you can circumvent the type system using std::any (or, again, boost::any if you don't have C++17). But really think about whether or not you can do better. The above solutions are going to give you better type safety and make your code less prone to errors.

Upvotes: 0

Curious
Curious

Reputation: 21510

You will not be able to do what you want with a non template base class. What you are trying to do is erase the type of the template derived class by storing a pointer to a non template base class. This is called type erasure - https://en.wikipedia.org/wiki/Type_erasure

You can dynamic_cast and then switch on the type of the derived class. For example

for (int i=0;i<myObject.size();i++){

     if (auto* ptr = dynamic_cast<Derived<int>*>(myObject[i])) {
         ptr->set(4);
         ptr->get();
     } else if (auto* ptr = dynamic_cast<Derived<double>*>(myObject[i])) {
         ptr->set(1.0);
         ptr->get();
     }
}

How std::any solves this interface issue might be of interest to you. Take a look here http://en.cppreference.com/w/cpp/utility/any/any_cast

The alternative is to not invest in type erasure and use a std::variant.

Upvotes: 4

Related Questions