Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can templates be completely avoided in C++?

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.

BEGIN setup

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.

END setup

So, my question: does this completely replace templates, modulo the thing about initialization? Is it inefficient or dangerous somehow?

like image 386
Ryan Reich Avatar asked Jul 19 '11 02:07

Ryan Reich


2 Answers

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:

  1. Performance
  2. Compile-time checking

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.

like image 193
Nemo Avatar answered Sep 30 '22 13:09

Nemo


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:

  • Compile-time optimisations: inlining and the consequent optimisation such as dead-code elimination, fixed-size loop unrolling.
  • Curiously Recurring Template Pattern (CRTP) provide implementation callable in the constructor,
  • Substitution Failure Is Not An Error (SFINAE) allows more intelligent function call resolution, including limited ability to introspect on the interface of the parameter type
  • Templated classes can provide functions that work for some parameter type/value but not others, only generating a compile-time error if those functions are actually used for an unsupported parameter (for example, + might be "forwarded" to the parameter's + - whether member or non-member - but only if available)
  • Specialisation: which allows templates to handle special cases differently.
  • Concepts (which aren't in C++ yet - won't make C++11 either - but reading about them will emphasise how templates depend on semantics, which is more flexible than the set function signatures in virtual dispatch)
  • Templates are a form of parametric polymorphism, so the client code can simply attempt to use them without introducing a base class, or taking the space for a virtual dispatch pointer.
    • Run-time polymorphism doesn't work well with multiple processes accessing objects in shared memory, as the virtual dispatch pointers set by one process may not point to the right place for another.
  • Avoid the (slow) heap. Virtual dispatch typically involves creating objects on the heap as their size varies down the derivation heirarchy and isn't known at compile time. Templates avoid those costs.
    • Array sizes can be sometimes be stipulated as template parameters, avoiding dynamic allocation.
like image 34
Tony Delroy Avatar answered Sep 30 '22 13:09

Tony Delroy