<div class="gmail_quote">On 10 April 2012 22:45, H. S. Teoh <span dir="ltr"><<a href="mailto:hsteoh@quickfur.ath.cx">hsteoh@quickfur.ath.cx</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
A lot of template code (e.g. a big part of Phobos) use signature<br>
constraints, for example:<br>
<br>
        void put(T,R)(R range, T data) if (isOutputRange!R) { ... }<br>
<br>
This is all nice and good, except that when the user accidentally calls<br>
.put on a non-range, you get reams and reams of compiler errors<br>
complaining that certain templates don't match, certain other<br>
instantiations failed, etc., etc.. Which is very unfriendly for newbies<br>
who don't speak dmd's dialect of encrypted Klingon. (And even for<br>
seasoned Star Trek^W^W I mean, D fans, it can take quite a few seconds<br>
before the real cause of the problem is located.)<br>
<br>
So I thought of a better way of doing it:<br>
<br>
        void put(T,R)(R range, T data)<br>
        {<br>
                static if (isOutputRange!R)<br>
                {<br>
                        ... // original code<br>
                }<br>
                else<br>
                {<br>
                        static assert(0, R.stringof ~ " is not an output range");<br>
                }<br>
        }<br>
<br>
This produces far more readable error messages when an error happens.<br>
But it also requires lots of boilerplate static if's for every function<br>
that currently uses isOutputRange in their signature constraint.<br>
<br>
So here's take #2:<br>
<br>
        void put(T,R)(R range, T data) if (assertIsOutputRange!R) { ... }<br>
<br>
        template assertIsOutputRange(R)<br>
        {<br>
                static if (isOutputRange!R)<br>
                        enum assertIsOutputRange = true;<br>
                else<br>
                        static assert(0, R.stringof ~ " is not an output range");<br>
        }<br>
<br>
Now we can stick assertIsOutputRange everywhere there was a signature<br>
constraint before, without needing to introduce lots of boilerplate<br>
code.<br>
<br>
But what if there are several overloads of the same function, each with<br>
different signature constraints? For example:<br>
<br>
        int func(T)(T arg) if (constraintA!T) { ... }<br>
        int func(T)(T arg) if (constraintB!T) { ... }<br>
        int func(T)(T arg) if (constraintC!T) { ... }<br>
<br>
If constraintA asserts, then the compiler will not compile the code even<br>
if the call actually matches constraintB.<br>
<br>
So for cases like this, we introduce a catchall overload:<br>
<br>
        int func(T)(T arg)<br>
                if (!constraintA!T && !constraintB!T && !constraintC!T)<br>
        {<br>
                static assert(0, "func can't be used for type " ~<br>
                        T.stringof ~ " because <insert some excuse here>");<br>
        }<br>
<br>
Now the compiler will correctly resolve the template to instantiate,<br>
while still providing a nice error message for when nothing matches.<br>
<br>
What do y'all think of this idea?<br>
<br>
(Personally I think it's really awesome that D allows you to customize<br>
compiler errors using static assert, and we should be taking advantage<br>
of it much more. I propose doing this at strategic places in Phobos,<br>
esp. where you'd otherwise get errors from 5 levels deep inside some<br>
obscure nested template that hardly anybody understands how it's related<br>
to the original failing instantiation (e.g. a no-match error from<br>
appendArrayWithElemImpl instantiated from appendToArrayImpl instantiated<br>
from nativeArrayPutImpl instantiated from arrayPutImpl instantiated from<br>
putImpl instantiated from put -- OK I made that up, but you get the<br>
point).)<br></blockquote><div><br></div><div>Genius!</div><div>As a weak-arse newbie, I've trembled at some of the incomprehensible errors you refer to, and this would definitely be appreciated.</div><div>I'll definitely remember this pattern for my own code.</div>
</div>