Adding to a global AliasSeq
repr_man
jxl.ppg at gmail.com
Tue Aug 15 02:30:37 UTC 2023
Consider the following template mixin:
```d
mixin template Base()
{
int x(){ return 10; }
}
```
It could be used in a variety of structs as follows:
```d
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`:
```d
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:
```d
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:
```d
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`:
```d
alias BaseUsers = AliasSeq!(Child);
```
Then, `f` could have a constraint added:
```d
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:
```d
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?
More information about the Digitalmars-d-learn
mailing list