5

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?

Robᵩ
  • 163,533
  • 20
  • 239
  • 308
Ryan Reich
  • 2,398
  • 2
  • 17
  • 15
  • 7
    Sounds like a horrible idea. C++ isn't Java. –  Jul 19 '11 at 02:31
  • 2
    `C(T &z): x(z + 1) {}` - you can't bind `x` to that temporary :-/. `virtual universal_base& operator+(const universal_base &x) = 0;` - `+` should return by value. – Tony Delroy Jul 19 '11 at 02:48
  • @Mike: I don't know Java yet. Could you explain what makes this work in Java but not in C++? – Ryan Reich Jul 19 '11 at 02:54
  • 7
    @Ryan Java uses pointer semantics for class types and every instance lives as long as necessary (more or less). In comparison C++ uses value semantics (for everything) with a complex type system and uses several kind of lifetimes. – Luc Danton Jul 19 '11 at 03:23
  • @Ryan: Java also uses a virtual machine and (typically) an intermediate step in compilation: source "compiled" to virtual machine codes, virtual machine codes compiled "just in time" to CPU-specific machine code. Consequently, there's less benefit in eliminating complexity / optimise in the initial compile, as there's a second chance (though the work involved costs every user at run-time). And anyway, it doesn't work well in Java... interfaces that only some of the derived classes can really implement meaningfully and scattered tests of run-time type (akin to `dynamic_cast`) are error prone. – Tony Delroy Jul 19 '11 at 05:43
  • 2
    You could also replace for-loops with gotos and functions with copy-paste :-) But seriously, templates and inheritance are orthogonal concepts: Inheritance goes "down" from more and more common ancestors, while templates make "parallel" copies of formally identical code operating on *unrelated* types. – Kerrek SB Jul 19 '11 at 10:27

7 Answers7

14

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.

Nemo
  • 70,042
  • 10
  • 116
  • 153
4

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.
Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • I was hoping to get some suggestions of uses of templates to which this method is *not* an alternative. The part with the question mark says so explicitly. – Ryan Reich Jul 19 '11 at 02:41
  • "part with the question mark says so explicitly" - it's ambiguous as the context has been established by your "setup", so it's unclear whether you're asking "does this completely replace templates?" for that scenario or more generally... ;-P Anyway... hope the various answers you've received make it clearer. You might find some of the discussion of another answer I made useful too: http://stackoverflow.com/questions/5854581/polymorphism-in-c/5854862#5854862 – Tony Delroy Jul 19 '11 at 05:48
4

No, it's not even theoretically possible, without either throwing away the ability to take advantage of a lot of commonality, or else inventing some other mechanism that's not based on inheritance (e.g., aspect oriented programming).

Ultimately, of course, it's possible to live with it (e.g., Smalltalk), but then it's theoretically possible to write anything you want to in pure assembly language too. Neither, however, fits the current model and thinking about C++ well at all. You may have sufficient reason to justify doing things differently, but you should definitely be aware that you are cutting against the grain, so to speak.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
3

I think the arguments against a universal "Object" class would be relevant here:

Why doesn't C++ have a universal class Object?

I was going to stress "compilte-time vs runtime" and type safety, but I think other answers cover it well.

I'd probably use a C interface for such a module: Write a dynamic, type-safe back-end in C++ with templates, then a wrapper with extern "C" functions to use it.

Gabriel Devillers
  • 3,155
  • 2
  • 30
  • 53
aib
  • 45,516
  • 10
  • 73
  • 79
  • Thanks for the link! It looks like the arguments are similar to the ones given here. – Ryan Reich Jul 19 '11 at 02:56
  • Why has providing a plugin interface anything to do with providing a _universal_ base class? If you feel like your interface would benefit from a base class for all objects involved in the interface (sounds iffy, if you ask me), then derive all of them from it. But there's no reason to also derive all types within the interface's _implementation_ from it nor is there a reason to not to use templates. I have written a few C APIs to wrap C++ code and much of the APIs' implementations used templates to glue the C API to the C++ code. – sbi Jul 19 '11 at 08:34
  • Anyway, `+1` from me for the link to the horse's mouth. – sbi Jul 19 '11 at 08:35
  • I was going for "it could work - if you had no other option." I'll delete that part, should make for a better answer. – aib Jul 20 '11 at 01:28
2

The general approach that you outline is fairly common, especially in Java. You might run into problems trying to generalize it to the point you describe. For instance, if you force everyone to implement operator+, do you also force everyone to implement all other operators as well? (+=, -=, /, ++, -- etc.) and if you do, you'll end up defeat much of the purpose of type checking (i.e. if you allow all types to convert to everything else and have all operators), and you'll also end up with your types having dummy implementations of operators that don't make sense for the type.

Now, as for the question if this replaces templates, the answer is no, but it might replace the specific use of templates that you have in mind.

Arvid
  • 10,915
  • 1
  • 32
  • 40
  • I suppose you would force people to implement the operators you use. I don't think it defeats type checking if you make your base class minimal: its derived types can differentiate themselves in all sorts of ways and have only the flimsiest of required interfaces. A class required to implement + can be an arithmetic type, something like a pointer, a 3D transformation, ... Conversely, if all your classes are required to implement + because some generic code requires it, maybe that's because they really all do need +. – Ryan Reich Jul 19 '11 at 02:49
2

No, is the short and sweet answer.

C++ is not designed for dynamic allocation like languages like Java- your program will run horrendously slowly, memory management will be an incredible bitch, and there are some template constructs that you could never replace anyway. For example, you can't participate in overload resolution with such a universal_base.

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.

You ship generic code in header files, not pre-compiled libraries. There are many libraries which are header-only and this is just fine, and many are headers and libraries. If you look at headers for the newest COM libraries, they implement a template QueryInterface in the header that delegates to the linked QueryInterface from the library. There's nothing wrong with shipping a library where most or even all of the code is written in the header. Look at the Standard library- almost all of it is templates. That should be a big hint- good C++ uses templates for generic code. Nobody will use a C++ library that runs like a dog anyway, if they don't want a fast library they wouldn't be using C++.

Let me put it this way: Templates exist and are so widely used because the other solutions to the problem suck in every conceivable fashion. Even OOP-crazy languages like Java and C# who have a universal base and a garbage collector had to introduce their own "templates", and even broke backwards compatibility to do it for C#, because they're just so much better.

Puppy
  • 144,682
  • 38
  • 256
  • 465
-2

You're trying to write an ancient and obscure language called C. Rumor has, with sufficient time and zen meditation, almost all C++ can be processed into this archaic representation. It simply takes time, grasshopper.

In all seriousness, the general pattern you're looking for appears to be an interface and implementation.

Templates can be avoided in most cases just be copying the code and customizing it by hand, but when you need to handle a situation like this, having a base class that defines all the important stuff is where you typically start.

For example:

class IAddable
{
public:
    virtual IAddable * Add(IAddable * pOther) = 0;
};

class Number
    : public IAddable
{
public:
    virtual IAddable * Add(IAddable * pOther)
    {
        return new Number(this, pOther);
    }

    Number(Number * a, Number * b)
        : m_Value(a->m_Value + b->m_Value)
    { };

private:
    int m_Value;
};

That's a basic, unsafe example, but it illustrates the point. So yes, your concept is generally sound.

As far as using virtual operators, if such a thing is possible (not used them myself), it seems like it would work.

Something to consider is making sure the two operands can be operated on, for which you might want to define a type check in your interface (similar to COM's IUnknown::QueryInterface);

You also have to watch how you handle the inheriting types; a lot of pointers will be involved since neither you nor the compiler may know quite what they are. Make sure you check for errors and wrong types rather often (return error values or convert the types or throw or what-have-you).

ssube
  • 47,010
  • 7
  • 103
  • 140
  • Could you say why this is trying to write in C? I am intentionally invoking the dynamic-typing and class-member features that make C++ different from C. Or do you mean that C programming often exploits implicit conversions? – Ryan Reich Jul 19 '11 at 03:16
  • 1
    Using a static or explicit or written out (not sure the best term) method for something C++ provides a language feature for (be it templates, virtual functions, whatever) usually brings you back to C's feature set. Most C++ things can be mimicked, often at the expensive of having a lot more very complex code, in ANSI C. I find that rather interesting, personally. That big of my answer wasn't terribly technical or serious, though. – ssube Jul 19 '11 at 03:56
  • @Ryan Reich: It's basically writing in C because C++ > C not because of OOP or inheritance, but because of templates and inline functions and RAII, all of which are things you'd be giving up if you won't put complex code in the header. That basically is C code, you're just using a C++ compiler to generate the vtables. – Puppy Jul 19 '11 at 09:48