Static Checks vs Templates: Always Instantiate Your Templates


One of the key benefits of static typing is the compile-time checking it enables. Compile your program successfully, and entire classes of programmer errors are guaranteed not to exist - regardless of how thorough your unittests are. (Unittests can't realistically be as thorough as static compiler checks. They're an important supplement to static compile-time checks, but not a replacement for them.)

Unfortunately, templates leave a slight hole in this system of guarantees. By the very nature of templates, the code inside cannot be compiled unless the template is actually instantiated. And if the code isn't compiled, it can't be statically checked (beyond mere syntax checks, anyway).

So you're left with this potential scenario: You write a new template in some library, or make some changes/additions to an existing template, then try out your test project and unittest suite. Everything compiles and runs successfully, so you commit your changes and push to the VCS server. Another coder pulls your work, tries to use your template...and gets compiler errors.

Oops! Your test project, or your unittest suite, never actually used the template! Or maybe it only used one instantiation and never attempted other instantiations that were intended to work. Your user became the first one to actually attempt compiling your code, and became the first to discover it didn't work.

Due to the nature of templates, it's impossible for the compiler to test all possible instantiations of a template to ensure they compile (or don't compile, in some cases) as expected, particularly if the template is part of a library. With sufficient sophistication, it may be possible for a compiler to guess at a few sets of arguments that satisfy your template's parameter constraints and then check that they compile. But even D doesn't currently do that.

The moral of this story: Always instantiate your templates. Make sure you always have at least some dummy instantiations that are expected to work. Here's an example of doing it in D:

struct FooType(T) { ... } private alias test_FooType = TypeTuple!( FooType!int, FooType!string, FooType!char ); void fooFunc(T)() { ... } private alias test_fooFunc = TypeTuple!( fooFunc!int, fooFunc!string, fooFunc!(FooType!int) );

It's better, of course, to unittest them. (Tip: It can often help to use foreach over a TypeTuple of the instantiations you want to unittest.) But at the very least, be sure to have some basic instantiations. It's only a one-liner, after all, and may very well save you some embarrassment.

Leave a comment

Captcha