digitalmars.D - "else if" for template constraints
- Steven Schveighoffer (71/71) Aug 17 2015 I was just looking at fixing this
- Steven Schveighoffer (8/12) Aug 17 2015 I guess this was a slightly wrong opinion. Because the first overload
- Timon Gehr (3/5) Aug 17 2015 Implementation is trivial. (A naive implementation strategy which works
- Zoadian (4/19) Aug 17 2015 wouldn't is(typeof(replace(array, from, to, stuff))) better be a
- Zoadian (3/5) Aug 17 2015 nevermind, I missed that the first constraint is negated.
- Idan Arye (22/34) Aug 17 2015 It looks a bit ugly, that the `else` is after a function
- Steven Schveighoffer (3/22) Aug 17 2015 Yes, I like this much better.
- Meta (4/32) Aug 17 2015 At that point, couldn't you just use static if inside the body of
- Idan Arye (10/45) Aug 17 2015 No. Consider this: http://dpaste.dzfl.pl/a014aeba6e68. The having
- anonymous (14/23) Aug 17 2015 The idea is to have only one template:
- BBasile (18/42) Aug 17 2015 There is also, as a similar option, the "dispatcher" solution,
- Steven Schveighoffer (6/29) Aug 19 2015 What if there is another foo template that handles double? possibly in
- Enamex (35/62) Sep 04 2015 The biggest problem, I think, is that a template can has multiple
- Enamex (13/32) Sep 04 2015 On second thought, it wouldn't help as much as I'd thought with
I was just looking at fixing this bug:https://issues.dlang.org/show_bug.cgi?id=14925 A little background for the root cause: replaceInPlace has 2 versions. One is a specialized version that replaces the actual elements in an array with another array of the same type. The second version just uses replace, and then overwrites the original array reference. This is used when the stuff to replace is not an array, or the array elements don't match, or the target array has const or immutable elements. The constraint for version 1 is: if(isDynamicArray!Range && is(ElementEncodingType!Range : T) && !is(T == const T) && !is(T == immutable T)) More on that later. The constraint for version 2 is: if(isInputRange!Range && ((!isDynamicArray!Range && is(ElementType!Range : T)) || (isDynamicArray!Range && is(ElementType!Range : T) && (is(T == const T) || is(T == immutable T))) || isSomeString!(T[]) && is(ElementType!Range : dchar))) The issue (as I noted in the bug report), is that the array being replaced is "some string", and the element type of the stuff to replace is a dchar. But the first version is better for replacing a char[] in a char[], and works just fine. So I set about fixing this third constraint. We need to only call this version if the "some string" actually has non-mutable characters. So I proceeded to add another "is(T == const T) || is(T == immutable T)", but then I realized, wait, the first version will also be called if the ElementEncodingType of Range *fits into* a T (note the use of a colon instead of ==). Therefore, replaceInPlace!(wchar, char[]) will incorrectly try and call the first version, when it should call the second. Therefore, I needed to update the constraints on the first version. This screws up the whole dynamic, because both constraints are evaluated INDEPENDENTLY. You can't have any overlap, so any changes to the first constraint may cause issues with the second (and in this case, it does). My second set of constraints was starting to look REALLY complicated. What I really need for the second constraint is: "doesn't match the first version AND I can call replace with those arguments". So that's what I did: if(!(... /* whole constraint from first version */) && is(typeof(replace(array, from, to, stuff)))) This is not very DRY. One thing I could do is factor out the main constraint into another template: enum _replaceInPlaceConstraint(T, Range) = ... Great! But then the docs don't reflect the true constraint (they just have this _replaceInPlaceConstraint, er.. constraint), and I've contributed to the ever-growing template bloat of phobos. How often are you writing overloaded templates, and you want to say "if it doesn't match anything else, do this"? I'd love to see some form of syntax that brings template constraints in line with tried-and-true if/else statements. One way to do this is to lexically order the if constraints, and if any of them start with "else", then they are mutually exclusive with the immediately preceding constraint for the same symbol (just like normal else). So for example, you'd have: void replaceInPlace(T, Range)(ref T[] array, size_t from, size_t to, Range stuff) if(isDynamicArray!Range && is(Unqual!(ElementEncodingType!Range) == T) && !is(T == const T) && !is(T == immutable T)) { /* version 1 that tries to write into the array directly */ } void replaceInPlace(T, Range)(ref T[] array, size_t from, size_t to, Range stuff) else if(is(typeof(replace(array, from, to, stuff)))) { /* version 2, which simply forwards to replace */ } looks much better IMO. Can we do something like this? I'm not a compiler guru, so I defer to you experts out there. -Steve
Aug 17 2015
On 8/17/15 9:18 AM, Steven Schveighoffer wrote:The issue (as I noted in the bug report), is that the array being replaced is "some string", and the element type of the stuff to replace is a dchar. But the first version is better for replacing a char[] in a char[], and works just fine.I guess this was a slightly wrong opinion. Because the first overload uses remove, and remove cannot work with char[] (see phobos schizophrenia regarding strings), it must call the always-allocating second version. In any case, the template constraints are still simpler with the new style (if(!priorconstraints) && ...) -Steve
Aug 17 2015
On 08/17/2015 03:18 PM, Steven Schveighoffer wrote:Can we do something like this? I'm not a compiler guru, so I defer to you experts out there.Implementation is trivial. (A naive implementation strategy which works is to just use the obvious lowering.)
Aug 17 2015
On Monday, 17 August 2015 at 13:18:43 UTC, Steven Schveighoffer wrote:void replaceInPlace(T, Range)(ref T[] array, size_t from, size_t to, Range stuff) if(isDynamicArray!Range && is(Unqual!(ElementEncodingType!Range) == T) && !is(T == const T) && !is(T == immutable T)) { /* version 1 that tries to write into the array directly */ } void replaceInPlace(T, Range)(ref T[] array, size_t from, size_t to, Range stuff) else if(is(typeof(replace(array, from, to, stuff)))) { /* version 2, which simply forwards to replace */ } looks much better IMO. Can we do something like this? I'm not a compiler guru, so I defer to you experts out there. -Stevewouldn't is(typeof(replace(array, from, to, stuff))) better be a static if inside the first version?
Aug 17 2015
On Monday, 17 August 2015 at 16:57:33 UTC, Zoadian wrote:wouldn't is(typeof(replace(array, from, to, stuff))) better be a static if inside the first version?nevermind, I missed that the first constraint is negated. In that case I agree, else if would be nice.
Aug 17 2015
On Monday, 17 August 2015 at 13:18:43 UTC, Steven Schveighoffer wrote:void replaceInPlace(T, Range)(ref T[] array, size_t from, size_t to, Range stuff) if(isDynamicArray!Range && is(Unqual!(ElementEncodingType!Range) == T) && !is(T == const T) && !is(T == immutable T)) { /* version 1 that tries to write into the array directly */ } void replaceInPlace(T, Range)(ref T[] array, size_t from, size_t to, Range stuff) else if(is(typeof(replace(array, from, to, stuff)))) { /* version 2, which simply forwards to replace */ }It looks a bit ugly, that the `else` is after a function declaration instead of directly after the if's "then" clause. How about doing it with the full template style? template replaceInPlace(T, Range) if(isDynamicArray!Range && is(Unqual!(ElementEncodingType!Range) == T) && !is(T == const T) && !is(T == immutable T)) { void replaceInPlace(ref T[] array, size_t from, size_t to, Range stuff) { /* version 1 that tries to write into the array directly */ } } else if(is(typeof(replace(array, from, to, stuff)))) { void replaceInPlace(ref T[] array, size_t from, size_t to, Range stuff) { /* version 2, which simply forwards to replace */ } }
Aug 17 2015
On 8/17/15 1:00 PM, Idan Arye wrote:It looks a bit ugly, that the `else` is after a function declaration instead of directly after the if's "then" clause. How about doing it with the full template style? template replaceInPlace(T, Range) if(isDynamicArray!Range && is(Unqual!(ElementEncodingType!Range) == T) && !is(T == const T) && !is(T == immutable T)) { void replaceInPlace(ref T[] array, size_t from, size_t to, Range stuff) { /* version 1 that tries to write into the array directly */ } } else if(is(typeof(replace(array, from, to, stuff)))) { void replaceInPlace(ref T[] array, size_t from, size_t to, Range stuff) { /* version 2, which simply forwards to replace */ } }Yes, I like this much better. -Steve
Aug 17 2015
On Monday, 17 August 2015 at 17:17:15 UTC, Steven Schveighoffer wrote:On 8/17/15 1:00 PM, Idan Arye wrote:At that point, couldn't you just use static if inside the body of the template instead of using template constraints?It looks a bit ugly, that the `else` is after a function declaration instead of directly after the if's "then" clause. How about doing it with the full template style? template replaceInPlace(T, Range) if(isDynamicArray!Range && is(Unqual!(ElementEncodingType!Range) == T) && !is(T == const T) && !is(T == immutable T)) { void replaceInPlace(ref T[] array, size_t from, size_t to, Range stuff) { /* version 1 that tries to write into the array directly */ } } else if(is(typeof(replace(array, from, to, stuff)))) { void replaceInPlace(ref T[] array, size_t from, size_t to, Range stuff) { /* version 2, which simply forwards to replace */ } }Yes, I like this much better. -Steve
Aug 17 2015
On Monday, 17 August 2015 at 21:27:47 UTC, Meta wrote:On Monday, 17 August 2015 at 17:17:15 UTC, Steven Schveighoffer wrote:No. Consider this: http://dpaste.dzfl.pl/a014aeba6e68. The having two foo templates is illegal(though it'll only show when you try to instantiate foo), because each of them covers all options for T. When T is neither int nor float, the foo *function* in the first template is not defined, but the *foo* template is still there. With the suggested syntax, the first foo template would only be defined for int and float, and the second will only be defined for char and bool - so there is no conflict.On 8/17/15 1:00 PM, Idan Arye wrote:At that point, couldn't you just use static if inside the body of the template instead of using template constraints?It looks a bit ugly, that the `else` is after a function declaration instead of directly after the if's "then" clause. How about doing it with the full template style? template replaceInPlace(T, Range) if(isDynamicArray!Range && is(Unqual!(ElementEncodingType!Range) == T) && !is(T == const T) && !is(T == immutable T)) { void replaceInPlace(ref T[] array, size_t from, size_t to, Range stuff) { /* version 1 that tries to write into the array directly */ } } else if(is(typeof(replace(array, from, to, stuff)))) { void replaceInPlace(ref T[] array, size_t from, size_t to, Range stuff) { /* version 2, which simply forwards to replace */ } }Yes, I like this much better. -Steve
Aug 17 2015
On Monday, 17 August 2015 at 22:32:10 UTC, Idan Arye wrote:On Monday, 17 August 2015 at 21:27:47 UTC, Meta wrote:[...]The idea is to have only one template: template foo(T) { static if (is(T == int)) { ... } else static if (is(T == float)) { ... } else static if (is(T == char)) { ... } else static if (is(T == bool)) { ... } }At that point, couldn't you just use static if inside the body of the template instead of using template constraints?No. Consider this: http://dpaste.dzfl.pl/a014aeba6e68. The having two foo templates is illegal(though it'll only show when you try to instantiate foo), because each of them covers all options for T. When T is neither int nor float, the foo *function* in the first template is not defined, but the *foo* template is still there.
Aug 17 2015
On Monday, 17 August 2015 at 22:44:15 UTC, anonymous wrote:On Monday, 17 August 2015 at 22:32:10 UTC, Idan Arye wrote:There is also, as a similar option, the "dispatcher" solution, like for 'std.conv.to' There is the main template that dispatches the call to the right non templated (or specialized) overload, so that the entry point is just used to redirect the cases that are fundamentaly different. --- auto foo(T) { static if(...) return fooImpl!T(); else static if(...) return fooImpl!T(); else //etc } private auto fooImpl(T)(){} private auto fooImpl(T)(){} private auto fooImpl(T)(){} ---On Monday, 17 August 2015 at 21:27:47 UTC, Meta wrote:[...]The idea is to have only one template: template foo(T) { static if (is(T == int)) { ... } else static if (is(T == float)) { ... } else static if (is(T == char)) { ... } else static if (is(T == bool)) { ... } }At that point, couldn't you just use static if inside the body of the template instead of using template constraints?No. Consider this: http://dpaste.dzfl.pl/a014aeba6e68. The having two foo templates is illegal(though it'll only show when you try to instantiate foo), because each of them covers all options for T. When T is neither int nor float, the foo *function* in the first template is not defined, but the *foo* template is still there.
Aug 17 2015
On 8/17/15 6:44 PM, anonymous wrote:On Monday, 17 August 2015 at 22:32:10 UTC, Idan Arye wrote:What if there is another foo template that handles double? possibly in another file? There would be a conflict for instantiation. That is why we use template constraints instead of static if -- the constraint disqualifies the instantiation. -SteveOn Monday, 17 August 2015 at 21:27:47 UTC, Meta wrote:[...]The idea is to have only one template: template foo(T) { static if (is(T == int)) { ... } else static if (is(T == float)) { ... } else static if (is(T == char)) { ... } else static if (is(T == bool)) { ... } }At that point, couldn't you just use static if inside the body of the template instead of using template constraints?No. Consider this: http://dpaste.dzfl.pl/a014aeba6e68. The having two foo templates is illegal(though it'll only show when you try to instantiate foo), because each of them covers all options for T. When T is neither int nor float, the foo *function* in the first template is not defined, but the *foo* template is still there.
Aug 19 2015
On Monday, 17 August 2015 at 13:18:43 UTC, Steven Schveighoffer wrote:I was just looking at fixing this bug:https://issues.dlang.org/show_bug.cgi?id=14925 [...] How often are you writing overloaded templates, and you want to say "if it doesn't match anything else, do this"? I'd love to see some form of syntax that brings template constraints in line with tried-and-true if/else statements. One way to do this is to lexically order the if constraints, and if any of them start with "else", then they are mutually exclusive with the immediately preceding constraint for the same symbol (just like normal else). So for example, you'd have: void replaceInPlace(T, Range)(ref T[] array, size_t from, size_t to, Range stuff) if(isDynamicArray!Range && is(Unqual!(ElementEncodingType!Range) == T) && !is(T == const T) && !is(T == immutable T)) { /* version 1 that tries to write into the array directly */ } void replaceInPlace(T, Range)(ref T[] array, size_t from, size_t to, Range stuff) else if(is(typeof(replace(array, from, to, stuff)))) { /* version 2, which simply forwards to replace */ } looks much better IMO. Can we do something like this? I'm not a compiler guru, so I defer to you experts out there. -SteveThe biggest problem, I think, is that a template can has multiple 'predicates' to agree to be instantiated, but only some of them can mutually exclusive (the specialization syntax produces mutually exclusive ones, the if-constraints don't). Thinking about it from this angle, I believe the most flexible and sensible solution would be to support a sort of "early return" from a template. Thus: template Bar(T) { static if( is(T == int) || is(T == string) || ... ) { //stuff } else static if( stuff ) { // other stuff } else { template return; // I know my T took whatever your type was but I actually don't match, please exclude me from your list for this instance... } } template Bar(T) { static if( is(T == float) || is(T == int[]) || ... ) { // Bar!float/Bar!(int[]) stuff } else static if( OTHER_OTHER_stuff ) { // other other stuff } else { template return; // I know my T took whatever your type was but I actually don't match, please exclude me from your list for this instance... } }
Sep 04 2015
On Friday, 4 September 2015 at 15:52:08 UTC, Enamex wrote:The biggest problem, I think, is that a template can has multiple 'predicates' to agree to be instantiated, but only some of them can mutually exclusive (the specialization syntax produces mutually exclusive ones, the if-constraints don't). [...] template Bar(T) { static if( is(T == int) || is(T == string) || ... ) { //stuff } else static if( stuff ) { // other stuff } else { template return; // I know my T took whatever your type was but I actually don't match, please exclude me from your list for this instance... } } [...]On second thought, it wouldn't help as much as I'd thought with overloading problems. What we want is a 'which template has more '&&'ed expressions in its constraint?' which sounds pretty awful. I have no idea how this could work :/ Especially given that D's constraints are way more open than, say, Haskell's, in their checking; though ironically not their declaration/implementation (D is open because it checks for structural conformance of a struct instead of nominative & structural; but it's more restricted because it easily only checks for structure of the type as declared, with no way to attach recognized-to-the-constraint ad-hoc interfaces to a type like Haskell's with type-classes).
Sep 04 2015