digitalmars.D - Template constraints should introduce identifiers inside their scopes
- HuskyNator (53/53) Sep 21 2022 Consider these semantically identical functions:
- Petar Kirov [ZombineDev] (3/9) Sep 21 2022 Agreed, it's been bothering me as well. (It is a language design
- Paul Backus (2/6) Sep 21 2022 +1, this has annoyed me in the past too.
- Quirin Schroll (56/105) Sep 21 2022 They are not semantically the same. The first can be instantiated
- Paul Backus (15/48) Sep 21 2022 If you want the semantics to match exactly you can replace the
- HuskyNator (4/14) Sep 21 2022 Apologies, I only read your comment now.
- Timon Gehr (27/35) Sep 22 2022 I think this is not true.
- Paul Backus (6/20) Sep 22 2022 In general, yes. In the context of the specific example in this
- jmh530 (8/32) Sep 22 2022 I think the underlying principle is that the user generally
- HuskyNator (7/15) Sep 21 2022 I am aware of the differences when the if statement fails, though
- Nick Treleaven (3/10) Sep 22 2022 This is in bugzilla:
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, `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); } 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 } ``` Apart from these examples, depending on complexity, one might even think of a scenario in which a type returned inside the template condition should be matched against, in which case the body of the function would be required to contain a duplicate statement: `is(aliasMixin!M M2: T[L], T, uint L);` (eg. when getUnsignedType returns a more complex type) --- 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.
Sep 21 2022
On Wednesday, 21 September 2022 at 11:23:57 UTC, HuskyNator wrote:[..] --- 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.Agreed, it's been bothering me as well. (It is a language design question, not a implementation bug.)
Sep 21 2022
On Wednesday, 21 September 2022 at 11:23:57 UTC, HuskyNator wrote: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.+1, this has annoyed me in the past too.
Sep 21 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); } ```
Sep 21 2022
On Wednesday, 21 September 2022 at 12:18:19 UTC, Quirin Schroll wrote:On Wednesday, 21 September 2022 at 11:23:57 UTC, HuskyNator wrote:If you want the semantics to match exactly you can replace the `static if` with a `static assert` (or add `else static assert(0);`).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' } ``` [...] 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.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?`static if` already handles these cases. I don't know exactly what the rules are (needs better documentation), but presumably they would work the same way for constraints.In your case, ```d void foo(M : T[L], T, uint L)(M list) { } ``` does the trick.As I'm sure you're aware, there are cases where the desired constraint cannot be expressed using template specializations. For example: ```d auto fun(R)(R r) if (isInputRange!R && is(ElementType!R == T[], T)) ```
Sep 21 2022
On Wednesday, 21 September 2022 at 12:32:38 UTC, Paul Backus wrote:`static if` already handles these cases. I don't know exactly what the rules are (needs better documentation), but presumably they would work the same way for constraints.Apologies, I only read your comment now.As I'm sure you're aware, there are cases where the desired constraint cannot be expressed using template specializations. For example: ```d auto fun(R)(R r) if (isInputRange!R && is(ElementType!R == T[], T)) ```Thank you, this is indeed what I was aiming for :)
Sep 21 2022
On 21.09.22 14:32, Paul Backus wrote:I think this is not true. ```d module a; template foo(T){ static assert(is(T==int)); } template bar(T)if(is(T==int)){} ``` ```d module b; template foo(T)if(is(T==float)){} template bar(T)if(is(T==float)){} ``` ```d module c; import a,b; alias foo1=foo!int; // ok alias foo2=foo!float; // error alias bar1=bar!int; // ok alias bar2=bar!float; // ok ``` `static assert` can give you a custom error message, but template constraints have much better overloading behavior. The two features are not comparable. Ideally template constraints would be improved so they are a strictly better choice than static assert whenever they are appropriate.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.If you want the semantics to match exactly you can replace the `static if` with a `static assert` (or add `else static assert(0);`).
Sep 22 2022
On Thursday, 22 September 2022 at 16:52:54 UTC, Timon Gehr wrote:On 21.09.22 14:32, Paul Backus wrote:[...]I think this is not true.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.If you want the semantics to match exactly you can replace the `static if` with a `static assert` (or add `else static assert(0);`).`static assert` can give you a custom error message, but template constraints have much better overloading behavior. The two features are not comparable.In general, yes. In the context of the specific example in this thread, they are the same. In any case, the difference has no bearing on the main topic of discussion here, which is name visibility.
Sep 22 2022
On Thursday, 22 September 2022 at 18:00:53 UTC, Paul Backus wrote:On Thursday, 22 September 2022 at 16:52:54 UTC, Timon Gehr wrote:I think the underlying principle is that the user generally expects them to behave similarly. Enhancing template constraint name visibility is probably a good thing on that principle. Figuring out some way for Timon's example to compile might also be good (perhaps overload sets would need an ordering to put templated functions with template constraints above those without template constraints?).On 21.09.22 14:32, Paul Backus wrote:[...]I think this is not true.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.If you want the semantics to match exactly you can replace the `static if` with a `static assert` (or add `else static assert(0);`).`static assert` can give you a custom error message, but template constraints have much better overloading behavior. The two features are not comparable.In general, yes. In the context of the specific example in this thread, they are the same. In any case, the difference has no bearing on the main topic of discussion here, which is name visibility.
Sep 22 2022
On Wednesday, 21 September 2022 at 12:18:19 UTC, Quirin Schroll wrote: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.I am aware of the differences when the if statement fails, though the intent remains the same.There are better ways than a string `mixin` to do this.I am aware, this is merely an example.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?I would presume it to work identically to its behavior inside a static if statement.
Sep 21 2022
On Wednesday, 21 September 2022 at 11:23:57 UTC, HuskyNator wrote:Consider these semantically identical functions: ```d void bar(M)(M list) if (is(M : T[L], T, uint L)) { pragma(msg, "Length: " ~ L.to!string); // undefined identifier 'L' } ```This is in bugzilla: https://issues.dlang.org/show_bug.cgi?id=6269
Sep 22 2022