On 10 April 2012 22:45, H. S. Teoh <[email protected]> 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.

Reply via email to