Almighty Alias

Timur Gafarov
3 min readJun 18, 2022

--

Alias is my favorite keyword in D! I never cease to be amazed of how many things it does. In D, almost anything can be aliased, and there are even some features that are only possible through aliasing.

Simplest use case of alias is defining an alternative name for a type or an object. This is extremely useful, for example, to keep your API backwards compatible when changing names:

alias OldType = NewType;

You can define an alias with an old name and mark it with a “deprecated” attribute, so the code using an old API will compile, but trigger a deprecation warning:

deprecated("use NewType instead") alias OldType = NewType;

Another usual use case of alias is making compile-time shorthands for lambdas:

alias Sqr = x => x * x;writeln([2, 3, 4].map!Sqr);

But probably the most mind-boggling feature of alias is that it can be used in template parameters. This is a conventional way to pass a value to template parameters rather than a type, so that this value can be used in a compile-time expression. In the following example the value of x is evaluated at compile time with a user-provided numeric literal:

auto sqr(alias a)()
{
enum sqrA = a * a;
return sqrA;
}
enum x = sqr!10;
writeln(x);

How about an alias in a templated alias? This is possible and pretty useful in some scenarios!

alias Inc(alias a) = { a++; };int x = 1;
Inc!x();
writeln(x);

In the example above x will be incremented using a closure alias template.

An elegant way to implement compile-time type constraints — templated enum with alias parameter:

enum bool isClass(alias T) = is(T == class);class MyClass
{
int x;
}
assert(isClass!MyClass);

Old-fashioned templates from the D1 era are still a thing, and alias is your best friend when working with them. For example, this is how you define a variable with a template:

template ArrayOfSquares(T, T initValue)
{
T[10] array = initValue * initValue;
alias ArrayOfSquares = array;
}
auto a = ArrayOfSquares!(int, 2);

Let’s return to alias parameters and make some more fun with them. Take a template that automatically initializes a state with a compile-time parameter:

struct ArrayOfCubes(T, T initValue)
{
T[10] array = initValue * initValue * initValue;
}
auto cubes = ArrayOfCubes!(int, 2)();

Now imagine that you want to use ArrayOfCubes in a function, and you need initValue (you don’t want to take a cube root, do you?). This seems trivial, but the following code won’t compile:

// Error
void someFunc(T, T initValue)(ArrayOfCubes!(T, initValue) cubes)
{
writeln(initValue);
}

Alias parameter, however, does the trick!

void someFunc(T, alias initValue)(ArrayOfCubes!(T, initValue) cubes)
{
writeln(initValue);
}

Last but not least, alias is used in “alias this” construct that makes a class or a struct behave like its member:

struct Foo
{
int x;
alias x this;
}
auto f = Foo(5);
writeln(f * 2); // same as f.x * 2

Alias works with symbols, types and compile-time expressions. Literals and runtime expressions can’t be aliased. For example, this will not compile:

int x = 8;
alias a = x + 1; // Error

It is also possible to alias more than one thing at once — I have a separate article on that.

--

--

Timur Gafarov
Timur Gafarov

No responses yet