Reputation: 377
I would like an array of pointers to instances of a template class. My problem would be solved if C++ allowed templated virtual methods in a base class, with a templated derived class.
Therefore, how would one implement templated virtual methods?
Below I have a solution which seems to work, but I'm interested in comments about my implementation.
The template parameter is infinitely variable, e.g., I cannot enumerate every specialization of this template class. The template class T
can be any POD, array of POD, or struct of POD.
The complete set of T
is known at compile time. Basically, I have a file which defines all the different T
used to instantiate the objects, and use Xmacros (https://en.wikipedia.org/wiki/X_Macro) to create the array of objects.
I know this isn't a great idea. Let's gloss over that for the time being. This ends up being more a curiosity.
These are the things I've looked into.
class Base {
virtual void SomeMethod() = 0;
}
template <class T>
class Derived : Base {
void SomeMethod() {...}
}
The problem with this is I cannot declare all the virtual methods in Base
that I want to overload, as virtual methods cannot be templated. Otherwise, it would be a perfect solution.
I am using C++17, so I could define the virtual base methods taking std::any
. But it cannot hold arrays, which precludes its use here.
It seems this would not help me create an array of these different objects. I would need to do something like
template <typename D, typename T>
class Base
{
...
};
template <typename T>
class Derived : public Base<Derived, T>
{
...
};
So I still end up with trying to create an array of Derived<T>
objects.
Again it looks like I would need to enumerate every possible type the Visitable
class needs to service, which, while not impossible (again, I have a file which defines all the different T
that will be used) seems like more Xmacros, which is just making the problem more complicated.
This is what I came up with. It will run in https://www.onlinegdb.com/online_c++_compiler
#include <iostream>
#include <array>
#include <typeinfo>
// Base class which declares "overloaded" methods without implementation
class Base {
public:
template <class T>
void Set(T inval);
template <class T>
void Get(T* retval);
virtual void Print() = 0;
};
// Template class which implements the overloaded methods
template <class T>
class Derived : public Base {
public:
void Set(T inval) {
storage = inval;
}
void Get(T* retval) {
*retval = storage;
}
void Print() {
std::cout << "This variable is type " << typeid(T).name() <<
", value: " << storage << std::endl;
}
private:
T storage = {};
};
// Manually pointing base overloads to template methods
template <class T> void Base::Set(T inval) {
static_cast<Derived<T>*>(this)->Set(inval);
}
template <class T> void Base::Get(T* retval) {
std::cout << "CALLED THROUGH BASE!" << std::endl;
static_cast<Derived<T>*>(this)->Get(retval);
}
int main()
{
// Two new objects
Derived<int>* ptr_int = new Derived<int>();
Derived<double>* ptr_dbl = new Derived<double>();
// Base pointer array
std::array<Base*, 2> ptr_arr;
ptr_arr[0] = ptr_int;
ptr_arr[1] = ptr_dbl;
// Load values into objects through calls to Base methods
ptr_arr[0]->Set(3);
ptr_arr[1]->Set(3.14);
// Call true virtual Print() method
for (auto& ptr : ptr_arr) ptr->Print();
// Read out the values
int var_int;
double var_dbl;
std::cout << "First calling Get() method through true pointer." << std::endl;
ptr_int->Get(&var_int);
ptr_dbl->Get(&var_dbl);
std::cout << "Direct values: " << var_int << ", " << var_dbl << std::endl;
std::cout << "Now calling Get() method through base pointer." << std::endl;
ptr_arr[0]->Get(&var_int);
ptr_arr[1]->Get(&var_dbl);
std::cout << "Base values: " << var_int << ", " << var_dbl << std::endl;
return 0;
}
When this is run, it shows that calling the methods on Base
correctly point to the Derived
implementations.
This variable is type i, value: 3
This variable is type d, value: 3.14
First calling Get() method through true pointer.
Direct values: 3, 3.14
Now calling Get() method through base pointer.
CALLED THROUGH BASE!
CALLED THROUGH BASE!
Base values: 3, 3.14
Essentially I am manually creating the virtual method pointers. But, since I am explicitly doing so, I am allowed to use template methods in Base
which point to the methods in Derived
. It is more prone to error, as for example for each template method I need to type the method name twice, i.e., I could mess up:
template <class T> void Base::BLAH_SOMETHING(T inval) {
static_cast<Derived<T>*>(this)->WHOOPS_WRONG_CALL(inval);
}
So after all this, is this a terrible idea? To me it seems to achieve my objective of circumventing the limitation of templated virtual methods. Is there something really wrong with this? I understand there could be ways to structure the code that make all this unnecessary, I am just focusing on this specific construction.
Upvotes: 1
Views: 1202
Reputation: 119877
The normal run-off-the-mill method of dealing with subclasses that contain unknown types is to move the entire thing to a virtual function. Thus, instead of
superclass->get_value(&variable_of_unknown_type);
print(variable_of_unknown_type);
you write
superclass->print_value();
Now you don't need to know about any of the types a subclass might contain.
This is not always appropriate though, because there could be lots of operations. Making every operation a virtual function is troublesome if you are adding new operations all the time. On the other hand, the set of possible subclasses is often limited. In this case your best bet is the Visitor. Visitor rotates the inheritance hierarchy 90°, so to speak. Instead of fixing the set of operations and adding new subclasses freely, you fix the set of subclasses and add new operations freely. So instead of
superclass->print_value();
you write
class PrinterVisitor : public MyVisitor
{
virtual void processSubclass1(Subclass1* s) { print(s->double_value); }
virtual void processSubclass2(Subclass2* s) { print(s->int_value); }
};
superclass->accept(PrinterVisitor());
Now accept
is the only virtual
function in your base class. Note there are no casts that could possibly fail anywhere in the code.
Upvotes: 0
Reputation: 2695
It is more prone to error, as for example for each template method I need to type the method name twice
Oh, that's the least of your concerns. Imagine if you downcast to the wrong type.
At least save yourself a headache and use dynamic_cast
:
class Base {
public:
virtual ~Base() = default;
template <class T>
void Set(T inval) {
dynamic_cast<Derived<T>&>(*this).Set(inval);
}
template <class T>
T Get() {
return dynamic_cast<Derived<T>&>(*this).Get();
}
};
template <class T>
class Derived : public Base {
public:
void Set(T inval) {
storage = inval;
}
T Get() {
return storage;
}
private:
T storage{};
};
Other than that, I agree with the comments, this is probably not the right approach to your problem.
Upvotes: 1