My question, which is in the last paragraph, needs (in my opinion) some explanatory setup. Basically, I'm wondering if you can avoid using templates if instead you make all your would-be template classes instead inherit from a base class that declares virtual methods you will be using, among them a function for memory allocation that, when implemented, will return a pointer to the derived (not base) type.
C++ does not seem to have the notion of a "universal base class" from which everything is implicitly derived; I imagine that class would be defined like this:
class universal_base
{
};
Of course, now that I have defined it, I can make all my classes derive from it. Then, because of polymorphism, any references or pointers to universal_base
that I pass around will be basically the same as template parameters:
template <typename T>
class C
{
T &x;
int f(T &y);
C(T &z): x(z + 1) {}
};
class C
{
universal_base &x;
int f(universal_base &y);
C(universal_base &z): x(z + 1) {}
};
The difference is that in the first construction, the expression z + 1
can't be guaranteed to be valid; you just have to tell users that T
must overload operator+
. In the second construction, I could add a virtual such operator to universal_base
:
// in universal_base
public:
virtual universal_base& operator+(const universal_base &x) = 0;
and use typeid
and dynamic_cast
in the implementations to get the argument right. This way, it is impossible to write ill-formed code, because the compiler will complain if you don't implement operator+
.
Of course, this way it is not possible to declare members of non-reference type:
class C: public universal_base
{
universal_base x; // Error: universal_base is a virtual type
};
However, this can be gotten around through careful use of initialization. In fact, if I wanted to create a template for the above,
template <typename T>
class C: public universal_base
{
T x;
};
I would almost certainly be giving it objects of type T
at some point. In that case, there is no reason that I could not do the following:
class universal_base
{
public:
virtual universal_base& clone() = 0;
};
class C: public universal_base
{
universal_base &x;
C(universal_base &y) : x(y.clone()) {}
}
Effectively, I create a variable of a type that is determined at runtime. This of course requires that every object of type C
be appropriately initialized, but I do not think this is a huge sacrifice.
This is not academic, since it has the following use: if I am writing a module that is intended to be linked into other programs and handle their data in some generic way, I cannot possibly know the types that will be used. Templates are not helpful in this situation, but the technique above works fine.
So, my question: does this completely replace templates, modulo the thing about initialization? Is it inefficient or dangerous somehow?
Templates provide compile-time polymorphism; virtual functions provide run-time polymorphism.
Of course there is nothing you can do at compile time that you cannot do at run time. The differences are:
Your use of typeid
and dynamic_cast
will incur a run-time performance hit. So will virtual functions in general; on a modern CPU, calls to a variable location can be hundreds of times slower than calls to a fixed location (because they tend to clobber the instruction prefetch machinery).
So performance is definitely one concern.
Next... Unless you force every class to implement every operator, you run the risk of one of your run-time checks failing. If a template tries to call +
on a type that does not implement it, the result will be a compile-time error.
In general, the static nature of templates allows for better compile-time checking and optimization. But there is nothing semantically wrong with your idea.
This is not academic, since it has the following use: if I am writing a module that is intended to be linked into other programs and handle their data in some generic way, I cannot possibly know the types that will be used. Templates are not helpful in this situation, but the technique above works fine.
If you're looking to simply link and run, then templates are not useful: they are compile-time polymorphic, not link-time or run-time. Virtual dispatch is an option, but only really works if you can at least define a common interface that the types should support (it makes safe usage even harder, but if necessary they can provide some "discovery" mechanisms so the calling code can work out which bits actually work). Using "fat interfaces" (look it up in Stroustrup's The C++ Programming Language) is a fragile and ugly solution, but sometimes it may be the best available.
Edit in response to your comment...
A few things templates can do that virtual dispatch can't:
+
might be "forwarded" to the parameter's +
- whether member or non-member - but only if available)If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With