When a constraint for an alias parameter checks that calling it compiles, it instantiates any function literal template. That triggers any latent bugs in the user's function literal that couldn't be detected before IFTI instantiates it. Those bugs then cause the constraint to fail, because the literal does not compile. The constraint then removes the otherwise matching template from the list of candidates to use.
void f(alias a)() if (is(typeof(a()))) {}
void main()
{
f!(x => blarg);
}
https://issues.dlang.org/show_bug.cgi?id=11907
https://issues.dlang.org/show_bug.cgi?id=14217
This is a common pattern in Phobos.
Initially I thought a constraint expression like __traits(compiles, a())
should be moved to a static if
test with a helpful error message. std.algorithm.all was changed to use static assert instead:
https://issues.dlang.org/show_bug.cgi?id=13683
https://github.com/dlang/phobos/pull/6607/files
But that still swallows the actual compile error and just says the callable doesn't work without really saying why (why isn't it a unary predicate if it accepts range.front
as an argument?). Alternatively, the constraint check could be removed and you will get an internal error actually stating the precise error in the callable's body. But it doesn't help with overloading (see below).
What if there was a new trait to solve this? Suppose __traits(callable, expr, args)
, that just does IFTI (if expr
is a function template) and then checks that the parameter list of expr
accepts args
. It does not check that expr(args)
actually compiles, it ignores any errors in the body of expr
.
It doesn't check the return type, because that's not possible in general when the body does not compile and return type inference is needed. But overloading on the return type of a callable doesn't seem as important compared to overloading on the parameters of a callable.
The new trait can be used as a constraint or as a static if
condition. As a constraint it can help to determine which overload matches. Imagine two overloads:
// body would call a(int)
void f(alias a)()
if (__traits(callable, a, int()))
// body would call a(int, int)
void f(alias a)()
if (__traits(callable, a, int(), int()))
The trait would allow one of the overloads to be selected based on the number of parameters that a
has, even when the body of a
contains an error. Then one of the overloads of f
is instantiated and an internal template error will be raised that a
has an error. Despite this being an internal error, it is far better than the Phobos status quo because you can see what's actually failing with a precise error message.