On 10/04/12 21:45, H. S. Teoh wrote:
A lot of template code (e.g. a big part of Phobos) use signature
constraints, for example:

        void put(T,R)(R range, T data) if (isOutputRange!R) { ... }

This is all nice and good, except that when the user accidentally calls
.put on a non-range, you get reams and reams of compiler errors
complaining that certain templates don't match,

So here's take #2:

        void put(T,R)(R range, T data) if (assertIsOutputRange!R) { ... }

        template assertIsOutputRange(R)
        {
                static if (isOutputRange!R)
                        enum assertIsOutputRange = true;
                else
                        static assert(0, R.stringof ~ " is not an output 
range");
        }

Now we can stick assertIsOutputRange everywhere there was a signature
constraint before, without needing to introduce lots of boilerplate
code.

But what if there are several overloads of the same function, each with
different signature constraints? For example:

        int func(T)(T arg) if (constraintA!T) { ... }
        int func(T)(T arg) if (constraintB!T) { ... }
        int func(T)(T arg) if (constraintC!T) { ... }

If constraintA asserts, then the compiler will not compile the code even
if the call actually matches constraintB.

So for cases like this, we introduce a catchall overload:

        int func(T)(T arg)
                if (!constraintA!T&&  !constraintB!T&&  !constraintC!T)
        {
                static assert(0, "func can't be used for type " ~
                        T.stringof ~ " because<insert some excuse here>");
        }

Now the compiler will correctly resolve the template to instantiate,
while still providing a nice error message for when nothing matches.

This is the way we used to do it, before we had template constraints.
Although it works OK in simple cases, it doesn't scale -- you need to know all possible template constraints.

I would like to see something in the language conceptually like:

int func(T)(T arg) else { ... }

for a template which is instantiated only if all constraints have failed. 'default' is another keyword which could be used, and 'if(false)' is another, but else is probably more natural. Any attempt to instantiate an 'else' template always results in an error, just as now. (in practice: if instantiating the else template didn't trigger a static assert, a generic error message is issued)

It is an error for there to be more than one matching 'else' template.



What do y'all think of this idea?

(Personally I think it's really awesome that D allows you to customize
compiler errors using static assert, and we should be taking advantage
of it much more. I propose doing this at strategic places in Phobos,
esp. where you'd otherwise get errors from 5 levels deep inside some
obscure nested template that hardly anybody understands how it's related
to the original failing instantiation (e.g. a no-match error from
appendArrayWithElemImpl instantiated from appendToArrayImpl instantiated
from nativeArrayPutImpl instantiated from arrayPutImpl instantiated from
putImpl instantiated from put -- OK I made that up, but you get the
point).)

Definitely.

Incidentally, when all template constraints fail, the compiler could check them all again, and tell you exactly which conditions failed...

Algorithm: We know that:

false = !constraint1() && !constraint2() && !constraint3().

break each constraints into top-level boolean expressions. Then simplify (possibly using a BDD). easy (but common) example, if constraint1() = !A() && B(), constraint2 = !A() && C(), constraint3() == !A() && !B() && !D()

it simplifies to:  false = !A().
So we generate an error only saying that !A() failed.



Reply via email to