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.
