Difference between "can call" and "can compile"

Steven Schveighoffer schveiguy at gmail.com
Mon Sep 7 14:57:24 UTC 2020


Consider a template function like this:

void foo(T)(T v)
{
    auto x = v.someProperty;
}

Now, I might create a constraint based on whether I can compile that 
function:

void bar(alias x)() if (__traits(compiles, x(1)))
{
    x(1);
}

now, if I try to call bar like:

bar!foo();

I get a compilation error. But it's not in foo, it's in bar. It says:

Error: template instance onlineapp.bar!(foo) does not match template 
declaration bar(alias x)()
   with x = foo(T)(T v)
   must satisfy the following constraint:
        __traits(compiles, x(1))

In this instance, the error message is straightforward. With a simple 
case like this, I can figure it out and move on.

However, in a more complex case, perhaps the constraint is encapsulated 
in a template. Perhaps that template basically checks if it can call it 
in various ways, with various types. Perhaps it depends on the context 
in which it was called.

And my alias I'm passing in is INTENDED to work. In fact, it might be 
specifically DESIGNED to work with this exact function. However, I don't 
get to see why it doesn't. I just get a "must satisfy the following 
constraint: isSomeConstraint!foo"

The problem is, if the intention is for it to pass, but the 
implementation is bugged, the error message is useless. If the thing I'm 
passing is a type with 200 LOC, and there's an error buried in there 
somewhere, the compiler is telling me "somewhere in this, it doesn't 
work". And sometimes those function signatures and their constraints are 
hairy themselves.

My usual steps taken at this point are to extract the constraint 
template and actually try to call the functions in the constraints with 
that type, and see what fails. This is somewhat annoying, and sometimes 
difficult to do based on where the code that implements the constraint 
actually is, and where the code actually calling it is (I may have to 
duplicate a lot of stuff).

But the thing is -- we already HAVE a constraint system that says 
whether the code should be callable or not -- the template constraint! 
In fact, we can declare right on foo that it should only compile if I 
can call someProperty on the value:

void foo(T)(T v) if (__traits(compiles, v.someProperty))
{
    auto x = v.someProperty;
}

However, for this extra bit of forethought, I get no different error 
messages.

I was wondering, would it be a useful addition to have a way to say "can 
call" instead of "can compile"? In other words, this has a valid 
template IFTI match, regardless of whether it compiles or not. In which 
case, you can distinguish mismatch errors from implementation errors.

In the example above, foo without the template constraint instantiated 
with int has a matched call, but the match doesn't compile. Whereas, the 
one with the template constraint doesn't have a match, and so the call 
itself will not compile. This difference could push the error down to 
where it really belongs, and save me the time of having to perform 
exploratory surgery on a library and its dependencies.

Thoughts?

-Steve


More information about the Digitalmars-d mailing list