"else if" for template constraints

Steven Schveighoffer via Digitalmars-d digitalmars-d at puremagic.com
Mon Aug 17 06:18:42 PDT 2015


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


More information about the Digitalmars-d mailing list