Metaprogramming with Alias Sequences
Templates and compile-time execution in D form a basis for metaprogramming — building programs that generate another programs. This can be done in numerous ways, including mixins, template mixins, and of course templates by themselves. But there is another powerful metaprogramming tool that is not so widespread due to its peculiarity — so called alias sequences, also known as variadic template parameters or compile-time sequences. Formerly they were called type tuples, but now this term is not recommended to avoid confusion with `std.typecons.Tuple`.
Alias sequence is a concept rarely seen in other statically typed programming languages. Basically it is a compile-time list that can be destructured to a set of parameters (these can be function or template parameters). A standard library module to create and manipulate alias sequences is std.meta.
import std.stdio;
import std.meta;void main()
{
alias params = AliasSeq!("foo", "bar");
writeln(params);
}
The code above compiles to an exact equivalent of the following:
writeln("foo", "bar");
And the resulting output will be “foobar” in both cases.
Alias sequence can be accessed using an index and sliced, just like a regular array:
alias params = AliasSeq!(0, 5, 10, 40);
assert(params[0..2] == AliasSeq!(0, 5));
It also can be iterated using static foreach:
static foreach(param; params)
{
writeln(param);
}
This is an equivalent of calling `writeln` two times for each element of `params`:
writeln("foo");
writeln("bar");
No arrays or lists are created at runtime, and zero additional memory is allocated by the program! In the above examples alias sequences are always unrolled to stack variables, and `params` is a pure abstraction that exists only at compile time.
What is interesting, alias sequence can be instantiated to a variable, and this variable can be mutable and used in the same way as alias sequence itself:
alias literals = AliasSeq!(5, "hello");
auto vars = literals;
vars[0] = 100;
writeln(vars);
The only limitation is that such variable can’t be returned from a function. I solve this by using a simple structure that wraps variadic template parameters. I call it compound type, and it can be returned from a function, which make them work exactly like tuples in dynamically types languages. In such case return type of the function should be set to auto:
struct Compound(T...)
{
T tuple;
alias tuple this;
}Compound!(T) compound(T...)(T args)
{
return Compound!(T)(args);
}auto someFunc()
{
return compound(true, 0.5f, "hello");
}
What makes Compound cool is that it is implicitly convertible to its content thanks to `alias this`. This means that you can do the following:
alias tuple = AliasSeq!(bool, float, string);
tuple t = someFunc();
In the example above `t` will store (true, 0.5f, “hello”). This is a very illustrative example of metaprogramming — constructing new types on the fly. But alias sequences are much more than just a nice addition to D’s template system. Thanks to powerful introspection capabilities of the language, it is possible to use alias sequences to generate code that statically adapts itself to user-provided functionality. __traits and std.meta are especially useful for such tasks. The following function scans an import and prints all classes in it:
void printClasses(alias modul)()
{
alias memberNames = AliasSeq!(__traits(allMembers, modul));
alias Member(string name) = Alias!(__traits(getMember, modul, name));
alias members = staticMap!(Member, memberNames);
enum bool isClass(alias T) = is(T == class);
alias memberClasses = Filter!(isClass, members);
static foreach(Type; memberClasses)
{
writeln(Type.stringof);
}
}import mymodule;void main()
{
printClasses!(mymodule);
}