September 15, 2020
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?
>
> -Steve

Hear, hear.

And yes, `hasMember` also doesn't solve my problem in any way at all.
September 15, 2020
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.

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.

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."
September 15, 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.

-Steve
September 15, 2020
On Monday, 7 September 2020 at 14:57:24 UTC, Steven Schveighoffer wrote:
> Consider a template function like this:
>
> void foo(T)(T v)
> {
>    auto x = v.someProperty;
> }
>
> [...]

This is already possible from a library solution, see https://forum.dlang.org/post/iribazqozceygujkvvjg@forum.dlang.org

However I imagine it is slow because of mixin operations + not well defined because it uses stringof, but if you just want it in some small project of yours you can just use that.
September 15, 2020
On 9/15/20 10:18 AM, WebFreak001 wrote:
> On Monday, 7 September 2020 at 14:57:24 UTC, Steven Schveighoffer wrote:
>> Consider a template function like this:
>>
>> void foo(T)(T v)
>> {
>>    auto x = v.someProperty;
>> }
>>
>> [...]
> 
> This is already possible from a library solution, see https://forum.dlang.org/post/iribazqozceygujkvvjg@forum.dlang.org
> 
> However I imagine it is slow because of mixin operations + not well defined because it uses stringof, but if you just want it in some small project of yours you can just use that.

But that's the issue. If I know what small set of types should work, then I can just write overloads for those types.

It's when I don't know what type is going to be passed in.

I could probably switch to using "hasMember" in this use case, which is likely to be correct for all purposes that I have. It's just not applicable to a public library, which might use things other than members.

-Steve
September 16, 2020
On Tuesday, 15 September 2020 at 19:12:04 UTC, Steven Schveighoffer wrote:
> I could probably switch to using "hasMember" in this use case, which is likely to be correct for all purposes that I have. It's just not applicable to a public library, which might use things other than members.
>
> -Steve

You mentioned UFCS, but if it is a template in a library then user defined UFCS shouldn't work. The only valid case I can think of would be for a built in array, using front() and friends. But the library would have to import those functions for it to work.

I do agree with what you are saying, in fact it almost seems like __traits(compiles) is a code smell. There's usually a better way to express it, and as is with the case you are talking about, a different feature would be more appropriate and provide better error messages.


September 15, 2020
On 9/15/20 8:20 PM, Jackel wrote:
> On Tuesday, 15 September 2020 at 19:12:04 UTC, Steven Schveighoffer wrote:
>> I could probably switch to using "hasMember" in this use case, which is likely to be correct for all purposes that I have. It's just not applicable to a public library, which might use things other than members.
>>
> 
> You mentioned UFCS, but if it is a template in a library then user defined UFCS shouldn't work. The only valid case I can think of would be for a built in array, using front() and friends. But the library would have to import those functions for it to work.

Yes, but input ranges and Phobos are not the only things that depend on UFCS.

There's also a question of whether a free function can be called with a provided type, which has no analogue to __traits(hasMember).

> I do agree with what you are saying, in fact it almost seems like __traits(compiles) is a code smell. There's usually a better way to express it, and as is with the case you are talking about, a different feature would be more appropriate and provide better error messages.

Yes, exactly. I've spent way more than my fair share unraveling via library surgery what the compiler should be able to do itself, as long as the proper tools existed.

Don't get me wrong, I'd be fine with a solution that doesn't need a new feature (that would be ideal actually).

-Steve
September 16, 2020
On Wednesday, 16 September 2020 at 00:28:59 UTC, Steven Schveighoffer wrote:
>
> Yes, but input ranges and Phobos are not the only things that depend on UFCS.
>
> There's also a question of whether a free function can be called with a provided type, which has no analogue to __traits(hasMember).
>
>> I do agree with what you are saying, in fact it almost seems like __traits(compiles) is a code smell. There's usually a better way to express it, and as is with the case you are talking about, a different feature would be more appropriate and provide better error messages.
>
> Yes, exactly. I've spent way more than my fair share unraveling via library surgery what the compiler should be able to do itself, as long as the proper tools existed.
>
> Don't get me wrong, I'd be fine with a solution that doesn't need a new feature (that would be ideal actually).
>
> -Steve

Hm!

I may have something:

```
public template hasInstantiationFor(alias F, T)
{
  import std.traits : ReturnType;
  struct NoMatch { }
  alias proxy = F;
  alias proxy(T) = () => NoMatch();
  enum hasInstantiationFor = !is(ReturnType!(proxy!T) == NoMatch);
}
```

I could swear this used to straight up not work, but it seems to work now, at least for small local tests.

I can't put it into production because it runs into a flood of unrelated bugs if I try to use it in an actual project. I swear it never ends with this language...

But I'll try to dustmite this and maybe in DMD 2.095 we'll have a solution that actually works.

Though it only works for templates that are characterized by specialization, not template inconditions. As far as I can tell there's no way to see if a template matches inconditions, because there's no "fallback incondition" like there is with specializations.

So canInstantiate is still needed.
September 16, 2020
On Tuesday, 15 September 2020 at 19:12:04 UTC, Steven Schveighoffer wrote:
> On 9/15/20 10:18 AM, WebFreak001 wrote:
>> [...]
>
> But that's the issue. If I know what small set of types should work, then I can just write overloads for those types.
>
> It's when I don't know what type is going to be passed in.
>
> I could probably switch to using "hasMember" in this use case, which is likely to be correct for all purposes that I have. It's just not applicable to a public library, which might use things other than members.
>
> -Steve

huh where did you get that from? My proposed solution there just checks if you can call a function with the given arguments only if it's passing all the template constraints.
September 16, 2020
On 9/16/20 1:58 AM, WebFreak001 wrote:
> On Tuesday, 15 September 2020 at 19:12:04 UTC, Steven Schveighoffer wrote:
>> On 9/15/20 10:18 AM, WebFreak001 wrote:
>>> [...]
>>
>> But that's the issue. If I know what small set of types should work, then I can just write overloads for those types.
>>
>> It's when I don't know what type is going to be passed in.
>>
>> I could probably switch to using "hasMember" in this use case, which is likely to be correct for all purposes that I have. It's just not applicable to a public library, which might use things other than members.
>>
> 
> huh where did you get that from? My proposed solution there just checks if you can call a function with the given arguments only if it's passing all the template constraints.

Oh, I misread what you were doing. I assumed you were using traits to get parameter information, and I didn't bother reading very well. Sorry!

Indeed, this is a close solution. You would need to be worried about possible import issues (or overloads). Plus you might have unused functions that are not called.

I'd also want to directly call the function instead. I may want to actually verify the linkage is correct.

Something like:

template doppleganger(alias fun)
{
    mixin("private void doppleganger" ~ def[def.indexOf('(' .. $] ~ " {}");
}

static assert(__traits(compiles, fun!foo(args)));

Need a way to pass templates. But it does seem doable. As you point out, the stringof thing is pretty sketchy.

But yes, this is pretty much what I would want to have. Just more of a solid feature than a hack ;)

-Steve