Template specializations

Template specializations are a means to restrict the types with which one can instantiate a template. They are created by adding : <type> after declaring a template type, like so:

void foo(T : SomeType)(T data)

The compiler will use this template only when its argument is of type SomeType or of a type that is is implicitly convertible to it. This is useful when you want to perform specific operations on the template types that require certain properties or methods. Without the specialisation, if we were to instantiate foo incorrectly, the compiler would emit an error from inside the function. This is confusing for library functions as the errors look unrelated to the code calling the function. Using the specialisation, the call to foo itself would issue the error, thus pointing clearly to the problem.

Demo

Compile the code in demo/template-specializations/template_specializations.d. See that the error message says the problem comes from innerFun and then is propagated throughout the call stack:

dmd -unittest -main template_specializations.d
template_specializations.d(8): Error: incompatible types for `(data) + (5)`: `string` and `int`
template_specializations.d(14): Error: template instance `template_specializations.innerFun!string` error instantiating
template_specializations.d(20):        instantiated from here: `privateFun!string`
template_specializations.d(26):        instantiated from here: `publicFun!string`
make: *** [../../Makefile:7: template_specializations] Error 1

If only publicFun were a public fun while the others were private, this error message would create 2 problems:

  1. The message would leak implementation details to the developer. These details might contain private information that might end up compromising the app’s security.
  2. It would be confusing to the user as they wouldn’t know where the place of the error ((data) + (5)) is.

Practice

Add a template specialisation in the above demo so that the error appears to be generated by the function publicFun.

Logger

Going back to our logger, we want all our log functions to use templates. The signature of the function should look like this:

string log(Data)(Data data, LogLevel level, string file = __FILE__)

In order for the compiler to tell the log function for strings from the one for bools we need to use what we’ve just talked about: template specializations. Modify the signature of your log functions to use the same template

Remember that when multiple functions with the same name and different template specializations are defined, the compiler chooses the most specialised template to instantiate. For example, in the snippet below, the compiler would instantiate foo with an argument of type Derived, not Base.

struct Base {}

struct Derived : Base {}

void foo(T : Base)(T obj)
{
    writeln("Base");
}

void foo(T : Derived)(T obj)
{
    writeln("Derived");
}

unittest
{
    Derived d;
    foo(d);
}