Producing nicer template errors in D libraries

Manu turkeyman at gmail.com
Tue Apr 10 13:55:01 PDT 2012


On 10 April 2012 22:45, H. S. Teoh <hsteoh at quickfur.ath.cx> 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, certain other
> instantiations failed, etc., etc.. Which is very unfriendly for newbies
> who don't speak dmd's dialect of encrypted Klingon. (And even for
> seasoned Star Trek^W^W I mean, D fans, it can take quite a few seconds
> before the real cause of the problem is located.)
>
> So I thought of a better way of doing it:
>
>        void put(T,R)(R range, T data)
>        {
>                static if (isOutputRange!R)
>                {
>                        ... // original code
>                }
>                else
>                {
>                        static assert(0, R.stringof ~ " is not an output
> range");
>                }
>        }
>
> This produces far more readable error messages when an error happens.
> But it also requires lots of boilerplate static if's for every function
> that currently uses isOutputRange in their signature constraint.
>
> 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.
>
> 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).)
>

Genius!
As a weak-arse newbie, I've trembled at some of the incomprehensible errors
you refer to, and this would definitely be appreciated.
I'll definitely remember this pattern for my own code.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.puremagic.com/pipermail/digitalmars-d/attachments/20120410/51eeef8a/attachment-0001.html>


More information about the Digitalmars-d mailing list