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:
- The message would leak implementation details to the developer. These details might contain private information that might end up compromising the app’s security.
- 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 bool
s 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);
}