Consider the following template mixin:
mixin template Base()
{
int x(){ return 10; }
}
It could be used in a variety of structs as follows:
struct Child
{
mixin Base!();
}
Now, let's suppose we write a function with a parameter that should only be able to take items that mix in Base
:
auto f(T)(T arg)
{
return arg.x;
}
This works because D uses structural subtyping. However, this creates a problem: if we make another struct that looks like it mixes in Base
and pass it to the function, we could get unexpected results:
struct Other
{
int x = 5;
}
unittest
{
import std.stdio;
auto c = Child();
auto o = Other();
writeln(f(c));
writeln(f(o));
}
The output from running dub test
is as follows:
10
5
Even worse, because of UFCS, if there happens to be a function with the name x
in scope, it could get called:
auto x(int arg)
{
import std.stdio;
writeln("Hello world");
return arg;
}
unittest
{
import std.stdio;
auto c = Child();
auto o = Other();
auto i = 7;
writeln(f(c));
writeln(f(o));
writeln(f(i));
}
The output from running dub test
is the following:
10
5
Hello world
7
Obviously, this case is fairly trivial; however, I could see it becoming more of a problem in large projects, where an import could pull something into the namespace and there would be no compiler error about the function (in this case, f
) being used incorrectly.
The problem could be partially solved by enforcing that no one uses UFCS (through using regular call syntax and fully qualifying all functions), but this is ultimately not a sustainable solution, as it involves all contributing parties knowing about and using this convention. And, if anyone makes a mistake, it would be difficult to find.
A better solution would be to make some sort of list of structs that mix in Base
:
alias BaseUsers = AliasSeq!(Child);
Then, f
could have a constraint added:
auto f(T)(T arg)
if(staticIndexOf!(T, BaseUsers) != -1)
{
return arg.x;
}
Now, dub test
fails properly:
Error: template `f` is not callable using argument types `!()(Other)`
Candidate is: `f(T)(T arg)`
with `T = Other`
must satisfy the following constraint:
` staticIndexOf!(T, BaseUsers) != -1`
Error: template `f` is not callable using argument types `!()(int)`
Candidate is: `f(T)(T arg)`
with `T = int`
must satisfy the following constraint:
` staticIndexOf!(T, BaseUsers) != -1`
The problem with this, of course, is that every user of Base
must be known ahead of time and added to the list. Apart from being error-prone, it also prevents this approach from being used in a library.
In "Fantasy D", I would want to do something like this in the template mixin:
mixin template Base()
{
BaseUsers ~= typeof(this);
int x(){ return 10; }
}
However, since AliasSeq
s are immutable, and since there is no way that I know of to override a fully qualified alias, this does not work.
Is there any template-fu of which I am not aware that would make the thing I am trying to do possible?