For years I've publicly shown my distaste for C++, especially its templates. And like Software Design, I've had great difficulty justifying my disdain, not because the way C++ implements templates isn't technically wrong or incorrect, but because it's not usable.
Naively, templates seem to be made for classes that wrap primitive types into greater memory structures, for example linked lists, tuples, matrices, large numbers by combining smaller numbers, fractions, and so on. Those work well because they assume nothing of the choice of a template type but that it behaves like a primitive (or even numerical) type.
Also, it is important to note that the template type that the user selects affects the whole instance type, for example A<int>
is class A
using int
as its (first) template. This makes sense since when using that instance you must be able to expect a different behaviour and input / output types from its type. For example A<int>
is clearly not the same as A<float>
.
There is nothing wrong per se about how templates in C++ are implemented. They are essentially macros, but that are expanded in the C++ language as an augmentation of the type. So, all the advantages of macro replacements, plus type safety when you make use of the type.
Now, problems arise when a template class starts using the template type as an object. For example, if the template is T
, class A<T>
could have a statement like T.foo()
. In this case, nothing in the template class declaration can tell you that T
must implement the function void foo();
. You'll discover if the type you chose for A
is "valid" when you compile it, since if it doesn't implement foo
, you'll get a compilation error. That seems like a good thing, since two disctinct classes that have nothing in common in their class hierarchies can be interchanged as the template type. But there's something dangerous when doing this.
Using templates as a form of composition is maybe the worst approach you can take. Having class A
contain a single constructor that takes a required argument of type I
, which is an abstract base class (an interface, essentially), makes it really clear to any user of that class what is expected: That class needs a specific "kind" of instance to be used. This is the simple inversion of control pattern using interfaces. With templates? Well, you'll have to see if the code compiles. In a way, that subverts the many advantages of using a programming language that was designed with strong type safety in mind. It's as if C++ behaved a bit like Python of JavaScript when you start using templates for composition, with no type safety whatsoever until nearly the end of the compilation process. If you use templates for composition, then the more composition combinations you implement, the more code and distinct types are generated and compiled. So, code bloat, executable size bloat, and so on. But the fundamental issue is that, for a developer, using templates is not very usable because it leads to errors and hand to decypher compilation errors in classes you simply want to use.
The template type can also be artifically too contraining for run-time dynamism, something essential in inversion of control. Is A<B>
really different than A<C>
, even though B
and C
both use the same base class I
, and could be interchanged at run time? If instead you use A<I>
to allow B
and C
to be interchanged at run time, then why would you need to use a template for A
in the first place?
When I first learned about C++ templates, my issue wasn't that it existed, it's that back then (end of the 90s) so many developers used that for composition all the time. I've even read a book that introduced templates as a way to have a class use another class, skipping altogether the obvious and better-designed "tuple" or "fraction" examples. And maybe that was combined with my experience a few years later seeing template types 5 or 6 levels deep in production code, and since that was so cumbersome to use, macros were used to "build up" the template type into code. Having seen the vast majority of code outside of the C++ Standard Library use classes that don't act like primitives in template types as mean of composition, it feels like C++ templates accidentally became the worst design pattern I've even seen: Unrestricted dependency types, more compiled code, more difficult to debug and cumbersome to use.
So, using templates with primitive types or classes that act like primitive types is OK then. Well, making a class act like a primitive means you'll have to do a lot of operator overloading, the other major issue I have with C++.
Published on April 26, 2013 at 21:15 EDT
Older post: Electronic Taxes
Newer post: On C++ Operator Overloading