Difference between "can call" and "can compile"

Steven Schveighoffer schveiguy at gmail.com
Tue Sep 15 13:10:41 UTC 2020

On 9/15/20 7:26 AM, Paul Backus wrote:
> On Tuesday, 8 September 2020 at 20:04:32 UTC, Steven Schveighoffer wrote:
>> Here's what it says when there is no constraint (and when I actually 
>> call fun inside use):
>> onlineapp.d(9): Error: undefined identifier oops
>> onlineapp.d(4): Error: template instance onlineapp.bad!() error 
>> instantiating
>> onlineapp.d(20):        instantiated from here: use!(bad)
>> This is what I want it to do. Why can't it just do that? Why do I need 
>> to see 10 more or even 5 more lines of irrelevant error messages?
> It sounds to me like your problem is that template constraints are doing 
> the exact thing they're designed to do: causing errors that would 
> otherwise be reported inside a template to be reported outside of it.

That's not what they are designed to do. It does not cause an error if a 
constraint doesn't pass, it's just not a match.

Template constraints are designed to allow the template author to 
fine-tune how his template can be used. This allows template overloading 
that otherwise would be impossible or at least really difficult.

> Re: irrelevant error messages, I think they're unavoidable in the 
> general case. What if there are multiple template overloads? Only one of 
> them is going to have the error you care about, but the compiler has no 
> way to know which one. And likewise with static if and static assert 
> statements inside the template body.

It would know which one -- the one that matches. I want to say with my 
template constraint "I found a match that I can call, so use me". and 
then when the match fails to compile (NOT fails to match), then I get a 
targeted error message.

> The optimal solution to this problem, from a programming-language-theory 
> perspective, is a system like Rust's traits that allows the compiler to 
> type-check generic code prior to instantiation. Since we don't have 
> that, we're going to have to settle for something "good enough."

The compiler can type-check the interface, without checking the code.

We can already do this with types, I want to do it with calls.

An example

void foo(T)(T t) if (isIntegral!T)
    int x = t;

foo has declared "I accept all types that are integral", even though foo 
doesn't actually work for integers larger than int (a bug in either the 
constraints or the offending line).

But if you do foo(long.max), it does not come back and say "can't find 
foo", it says "error, can't assign int x to long t", or whatever. That's 
the error I want to see. Now I can either change the constraint to 
reflect it should only take int size or less, or change the line to work 
with longs.

In contrast, there's no way in the template constraints to say "I accept 
all parameters for which I can call it this way". You have to say "I 
accept all parameters for which this code compiles".

There is a difference in specification, and I would prefer the latter.

Without this facility, you are hiding all implementation errors, 
regardless of origin, behind constraints, which makes it really 
difficult to find them.


More information about the Digitalmars-d mailing list