(no subject)

Aug 04, 2006 10:07

Why Templates Are Viruses

WTF is it with C++?

So okay, I can deal with templates, they are in fact handy beasts, though I also think the tendency to turn them into a compiler within a compiler in C++ just makes the code hard to follow. And I wish people would use REAL naming conventions. Life was so much easier in Java.

But here's the thing. When you're writing code, you don't want to have to #include the universe in order to get it to compile. Why? If you have to ask, you haven't done enough development, but suffice it to say that the more things that #include file A, the more things that will recompile when you modify file (or any file that file A includes). Furthermore, the more likely you'll run into a nasty circularity in your #includes that causes great joy.

So, the right thing to do is use type references in the headers without actually using the types. For example:

class Constraint;
class DatabaseImpl;
class Result;

class DatabaseFrontEnd
{
public:
DatabaseFrontEnd(DatabaseImpl* impl);
Result* query(Constraint* constraint);

private:
DatabaseImpl *impl;
};

Straightforward. We define that the types Constraint, DatabaseImpl and Result exist, but we don't have to define what they are because we aren't actually using them. Thus, only people that actually care have to bother #including files like Constraint.h, DatabaseImpl.h or Result.h. This is as it should be, because chances are that whoever is calling query() probably doesn't really care how the front end got created, and shouldn't be dependent on DatabaseImpl; that's the whole point of abstracting the front end.

Someone out there will create Constraint.h, and it will contain something like

class Constraint
{
public:
int minValue;
int maxValue;
int maxCount;
};

So what's the problem? Well, C++ has these nifty things called templates, and these other nifty things called typedefs. So, this means that someone could decide that Constraint is in fact something like:

typedef ArithmeticBounds Constraint;

Any code that used Constraint would still compile, for example:

...
Constraint myConstraint;
myConstraint.minValue = 4;
myConstraint.maxValue = 8;
Result *r = frontEnd->query(&myConstraint);
...

In addition, the DatabaseFrontEnd would still be fine. That's because all a typedef is is an alias for the type, kind of like a macro.

On the other hand, suppose someone clever does something like this to replace the Constraint class:

template class Constraint
{
public:
PRIMTYPE minValue;
PRIMTYPE maxValue;
PRIMTYPE maxCount;
};

Now we have a problem. The DatabaseFrontEnd no longer compiles, because Constraint isn't a class. It looks like a class, and in fact uses the class specifier, but it's not - it's a template, and thus our simple little DatabaseFrontEnd is now broken. This leaves us two choices:
  1. Create a little class that is actually a class that hides the template.
  2. Turn our simple little DatabaseFrontEnd into a template as well.
Note that when we turn DatabaseFrontEnd from a class into a template, we are likely to run into the exact same problem somewhere else. Soon, everything in your system is a freakin' template, and you go from 30 second compile times to 30 minute compile times because you have to include the whole beastly class in the header file.

GAH!
Previous post Next post
Up