Reputation: 74
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
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
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.
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.
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.
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).
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
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