Cool project!
The mechanism you use is very special-purpose, in that you have
to write a lot of specific code to get such nice output. There's
a trick I came up with, that I've been meaning to post about,
which gives slightly less nice output, but requires no manual
effort and is completely general.
It looks like this: first, write a validator function
bool testInputRange(T)() {
static assert(is(typeof(T.init.empty)));
static assert(is(typeof(T.init.front)));
static assert(is(typeof(T.init.popFront)));
return true;
}
(The return value is a dummy. It's not strictly necessary, but
I'm not going to bother getting rid of it here for the purposes
of concision.)
We can then say:
void f(T)(T x) if (isInputRange!T) { ... }
enum isInputRange(T) = is(typeof(testInputRange!T));
as usual. No surprises. But now replace the definition with:
enum isInputRange(T) = is(typeof(testInputRange!T)) ||
testInputRange!T && false;
Obviously, testInputRange!T && false is just false, and
is(typeof(testInputRange!T)) || false is just
is(typeof(testInputRange!T)). So this seems logically equivalent
to the previous definition. But now we get nice error messages:
struct S {}
f(S());
gives an error like this:
range_check.d(12): Error: static assert: `is(typeof(S().empty))`
is false
range_check.d(10): instantiated from here:
`testInputRange!(S)`
range_check.d(4): instantiated from here:
`isInputRange!(S)`
Telling us exactly what the problem is (no 'empty' function),
with no manual effort.
---
There is one issue with this: it doesn't tell you about more than
one problem at once. In this case, S was also missing front and
popFront, but the error message only mentioned empty. One
solution is as follows:
void StaticAssert(alias x)() if (x) {}
bool testInputRange(T)() {
StaticAssert!(is(typeof(T.init.empty)));
StaticAssert!(is(typeof(T.init.front)));
StaticAssert!(is(typeof(T.init.popFront)));
return true;
}
Now we get to hear about all the problems, but we don't get to
know what they actually were:
range_check.d(12): Error: template instance
`range_check.StaticAssert!false` does not match template
declaration `StaticAssert(alias x)()`
with `x = false`
must satisfy the following constraint:
` x`
range_check.d(13): Error: template instance
`range_check.StaticAssert!false` does not match template
declaration `StaticAssert(alias x)()`
with `x = false`
must satisfy the following constraint:
` x`
range_check.d(14): Error: template instance
`range_check.StaticAssert!false` does not match template
declaration `StaticAssert(alias x)()`
with `x = false`
must satisfy the following constraint:
` x`
range_check.d(4): Error: template instance
`range_check.isInputRange!(S)` error instantiating
range_check.d(8): Error: template `range_check.f` cannot deduce
function from argument types `!()(S)`
range_check.d(4): Candidates are: `f(T)(T x)`
range_check.d(5): `f(T)(T x)`
Well, we get the line numbers (12, 13, 14), so we can check the
source code, but it would be much nicer if the error message
itself would tell us the problem.