Jesse Phillips
Posted on October 20, 2019
Intro
Templates have been around for some time, C++ being the most well-known language to support them. They are turning complete, and if you have only worked with them in C++ you're likely glad Java, C#, and Go don't have them.
Templates are one strategy to writing generic code. You'll see similar concepts in the Java and C# feature known as generics. Templates harbor much more power, and D takes that power to the max.
I mentioned templates being Turing complete, I must first note this is generally avoided in D because of compile time function evaluation (CTFE). What I want to focus on is the consept.
Templates are a compile time construct. They can be thought of as a function which takes compile time arguments (hence my note about CTFE).
Compile Time Arguments
What is a compile time argument?
- Types
- Values
- Symbols
Types
Types are the most common, and what generics tackles. In this case the template creates a symbol to the type allowing for the template. Enough talk let's look at some code.
void handle(T)() {
T content;
}
handle!int();
This is not a helpful template, takes no arguments and returns nothing. What I'm trying to show is how T becomes a symbol representing a type. In this particular example T
is int
. D has chosen !() to indicate compile time parameters (the parentheses can be dropped for a single argument).
Values
int handle(int val)() {
return val;
}
assert(handle!3() == 3);
By itself this ability is not very valuable, static if
, static assert
and other compile time inspection brings the ability to create restrictions and generate new code.
auto x = octal!177;
This is how octal and hex literals are handled. This even prevents compiling invalid octal digits, though the failure message could be improved.
auto y = octal!178; // compile failure
As I noted, the power isn't in the template. This actually converts the integer to a string and then executes normal conversion routine.
auto z = octal!int("177");
This is the same executed code, but it will evaluate at runtime.
Symbols
This is one of the most versatile compile time parameters. Without getting technical, almost all of a programming language is a symbol of some sort. This means types and values can be passed as a symbol.
When trying to think of an example of using a symbol, the main challenge was choosing something distinguishing and to really show how aliasing works. I decided some code generation was the answer.
string addTwo(alias a)() {
return a.stringof ~ "+=2;";
}
int four = 4;
mixin(addTwo!four);
assert(four == 6);
In this example addTwo builds the string four+=2;
. Then within the context of the context of 'four' mixin
is used compile that string in that context.
alias
has given access to the symbol four
where we used the language feature stringof
to get the string representing that symbol.
Look at that, a five line example and we not only use templates, but also compile time evaluation and code generation.
Templates
Templates create a structure for code and uses compile time variables as place holders for different parts of the code. If we take the first example and add different instantiations:
void handle(T)() {
T content;
}
handle!int();
handle!string();
handle!bool();
The compiler does the work of generating (the names are made-up and the points don't matter).
void handleInt() {
int content;
}
void handleString() {
string content;
}
void handleBool() {
bool content;
}
D expands on this concept and mixes it with the reasons languages have functions.
Comment
This post actually bugs me. I was looking over available articles to read and one of them was about object oriented programming in C#.
The issue with these type of posts is that they are a technical explanation of the tool. They rarely give a good caviots to the problems to using these tools and when to reach for them.
Posted on October 20, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.