Template constraints should introduce identifiers inside their scopes

Quirin Schroll qs.il.paperinik at gmail.com
Wed Sep 21 12:18:19 UTC 2022


On Wednesday, 21 September 2022 at 11:23:57 UTC, HuskyNator wrote:
> Consider these semantically identical functions:
>
> ```d
> void foo(M)(M list) {
> 	static if (is(M : T[L], T, uint L)) {
> 		pragma(msg, "Length: " ~ L.to!string); // Length: 2
> 	}
> }
>
> void bar(M)(M list) if (is(M : T[L], T, uint L)) {
> 	pragma(msg, "Length: " ~ L.to!string); // undefined identifier 
> 'L'
> }
>
> void main(string[] args) {
> 	int[2] list = [1, 2];
> 	foo(list);
> 	bar(list);
> }
> ```
>
> Although semantically the same,

They are not semantically the same. The first can be instantiated 
with any type and conditionally makes an output (it is empty 
otherwise), the other says it cannot be instantiated unless the 
arguments have specific properties.

The first one is Design by Introspection (DbI), the second is a 
template with requirements.

> `bar` will fail.
> This can of course be solved by changing the template 
> arguments, but could lead to issues in more complex scenarios:
>
> ```d
> alias UnsignedType(T) = mixin(getUnsignedType!(T));
>
> string getUnsignedType(T)() {
> 	static if (is(T == byte))
> 		return "ubyte";
> 	else static if (is(T == short))
> 		return "ushort";
> 	else static if (is(T == int))
> 		return "uint";
> 	else
> 		assert(0, "Type not supported: " ~ T.stringof);
> }
> ```

There are better ways than a string `mixin` to do this.
If for some reason 
[`std.traits.Unsigned`](https://dlang.org/phobos/std_traits.html#Unsigned) is not for you, I’d do:

```d
alias UnsignedType(T) = typeof({
     static if (is(T == byte)) return cast(ubyte)0;
     else static if (is(T == short)) return cast(ushort)0;
     else static if (is(T == int)) return cast(uint)0;
     else static assert(0, "Type not supported: " ~ T.stringof);
}());
```
> ```d
> auto foo(T)(T number) if (is(UnsignedType!T T2)) {
> 	alias T2 = UnsignedType!T; // still required
> 	return cast(T2) number;
> }
>
> void main(string[] args) {
> 	foo(1).writeln; // 1
> 	foo(-1).writeln; // 4294967295
> }
> ```
>
> […]
>
> Given the above consideration, assuming this is not a bug 
> (tested on both dmd & ldc), I believe identifiers introduced 
> inside a template constraint should be visible inside the scope 
> of the template.

Your suggestion is very much independent of the DbI vs 
constraints. You want identifiers defined in a constraint to be 
visible in the body of the template. This is possible in simple 
cases like yours, but what if the `is` check is nested or negated?

In your case,
```d
void foo(M : T[L], T, uint L)(M list) { }
```
does the trick. It works unless you’d have a `||` concatenated 
`static if` condition:
```d
void foo(M)(M list)
{
     static if (is(M : T[n], T, size_t n) || is(M : T[], T))
     {
         // on either way the condition is true, `T` is defined.
         pragma(msg, T);

         // Coming from second condition, `n` is undefined
         pragma(msg, is(typeof(n)));
     }
}

void main()
{
     int[3] xs = [1, 2, 3];
     foo(xs);
     foo([1, 2]);
}
```

In that case, you can still come very close with this:
```d
void boo(M : T[n], T, size_t n)(M list) => boo_impl!T(list);
void boo(M : T[], T)(M list) if (!is(M : T[n], T, size_t n)) => 
boo_impl!T(list);
private void boo_impl(T, M)(M list)
{
     pragma(msg, T);
}
```


More information about the Digitalmars-d mailing list