September 01, 2016
On 01.09.2016 19:55, Meta wrote:
> On Thursday, 1 September 2016 at 17:49:13 UTC, Timon Gehr wrote:
>> On 01.09.2016 19:21, Meta wrote:
>>> ...
>>>
>>> I just thought of this, but cannot test if it works. If it does, maybe
>>> it would be a suitable solution?
>>>
>>> void f(T)(T t) if(isSomething!T) {}
>>> void f(T)(T t) if(isSomethingElse!T) {}
>>> //Taken if no other "overload" of f will intantiate with the given T
>>> void f(T)(T t) if(!__traits(compiles, alias _ = .f!T)) {}
>>
>> It shouldn't work, but DMD currently seems to allow it. (If you fix
>> the syntax error.) I would expect it to break later.
>>
>>
>> The following causes an ICE (DMD segfaults).
>>
>> import std.stdio;
>>
>> int f(T)(T t) if(!__traits(compiles,.f!T)) {
>>     return 0;
>> }
>> int f(T)(T t) if(!__traits(compiles,.f!T)) {
>>     return 1;
>> }
>>
>> void main(){
>>     writeln(f(2));
>> }
>
> The idea is that there'd only be one such "fallback" template, so that
> you cannot get into a situation such as this. I'm guessing the ICE is
> due to a recursive dependency between the two f templates?

I posted the ICE to show that DMD does not necessarily have a clear concept of how your code should be interpreted. Note that you are essentially saying: "If not X then X". It's very easy to run into behaviour that seems inconsistent once you do things like this.

enum isSomething(T)=false;

int f(T)(T t) if(isSomething!T){
    return 0;
}
int f(T)(T t) if(!compiles!".f!int") {
    return 2;
}

enum compiles(string s) = __traits(compiles,mixin(s));
pragma(msg, compiles!".f!int");         // false
pragma(msg, __traits(compiles,.f!int)); // true


DMD cannot properly process code like this (i.e. code that contradicts itself, where the same expression in the same context can be true in one part of the compilation, but false later). Examples can be constructed where the semantics of the resulting code depends on the order that modules are passed on the command line. It's not specified anywhere what should happen, and it is not immediately clear.

DMD shouldn't accept code like this in the first place. It's very brittle, the result depends on random compiler implementation details.
September 01, 2016
On Thursday, 1 September 2016 at 18:24:13 UTC, Timon Gehr wrote:
>> The idea is that there'd only be one such "fallback" template, so that
>> you cannot get into a situation such as this. I'm guessing the ICE is
>> due to a recursive dependency between the two f templates?
>
> I posted the ICE to show that DMD does not necessarily have a clear concept of how your code should be interpreted. Note that you are essentially saying: "If not X then X". It's very easy to run into behaviour that seems inconsistent once you do things like this.

Well, I'd argue that's not quite right and the correct interpretation is "If not the other X then this X", due to the `!__traits(compiles, .f!T)`, explicitly telling the compiler to check if the *other* "overloads" compile. I don't actually know whether template constraints are considered to be at module scope or in the template scope, though, so maybe I'm completely wrong on this.


> enum isSomething(T)=false;
>
> int f(T)(T t) if(isSomething!T){
>     return 0;
> }
> int f(T)(T t) if(!compiles!".f!int") {
>     return 2;
> }
>
> enum compiles(string s) = __traits(compiles,mixin(s));
> pragma(msg, compiles!".f!int");         // false
> pragma(msg, __traits(compiles,.f!int)); // true
>
>
> DMD cannot properly process code like this (i.e. code that contradicts itself, where the same expression in the same context can be true in one part of the compilation, but false later). Examples can be constructed where the semantics of the resulting code depends on the order that modules are passed on the command line. It's not specified anywhere what should happen, and it is not immediately clear.
>
> DMD shouldn't accept code like this in the first place. It's very brittle, the result depends on random compiler implementation details.

I would argue that this is an entirely different case than my example, but we are getting into compiler implementation details that I know nothing about, so I can't actually say whether it is (or should) be valid code.


September 01, 2016
On Thursday, 1 September 2016 at 17:21:02 UTC, Meta wrote:
> I just thought of this, but cannot test if it works. If it does, maybe it would be a suitable solution?
>
> void f(T)(T t) if(isSomething!T) {}
> void f(T)(T t) if(isSomethingElse!T) {}
> //Taken if no other "overload" of f will intantiate with the given T
> void f(T)(T t) if(!__traits(compiles, alias _ = .f!T)) {}

That's so dirty, I love it. I imagine if __traits(getOverloads worked for templates we could actually use this pretty reliably (by excluding the fallback template from the search).
September 01, 2016
On 01.09.2016 21:02, Meta wrote:
> On Thursday, 1 September 2016 at 18:24:13 UTC, Timon Gehr wrote:
>>> The idea is that there'd only be one such "fallback" template, so that
>>> you cannot get into a situation such as this. I'm guessing the ICE is
>>> due to a recursive dependency between the two f templates?
>>
>> I posted the ICE to show that DMD does not necessarily have a clear
>> concept of how your code should be interpreted. Note that you are
>> essentially saying: "If not X then X". It's very easy to run into
>> behaviour that seems inconsistent once you do things like this.
>
> Well, I'd argue that's not quite right and the correct interpretation is
> "If not the other X then this X", due to the `!__traits(compiles,
> .f!T)`, explicitly telling the compiler to check if the *other*
> "overloads" compile.

Even if that was the intention of the compiler implementation, the example below demonstrates why it cannot work.

> I don't actually know whether template constraints
> are considered to be at module scope or in the template scope, though,
> so maybe I'm completely wrong on this.
> ...

Template scope, but /nothing/ about '.' says "other".

>
>> enum isSomething(T)=false;
>>
>> int f(T)(T t) if(isSomething!T){
>>     return 0;
>> }
>> int f(T)(T t) if(!compiles!".f!int") {
>>     return 2;
>> }
>>
>> enum compiles(string s) = __traits(compiles,mixin(s));
>> pragma(msg, compiles!".f!int");         // false
>> pragma(msg, __traits(compiles,.f!int)); // true
>>
>>
>> DMD cannot properly process code like this (i.e. code that contradicts
>> itself, where the same expression in the same context can be true in
>> one part of the compilation, but false later). Examples can be
>> constructed where the semantics of the resulting code depends on the
>> order that modules are passed on the command line. It's not specified
>> anywhere what should happen, and it is not immediately clear.
>>
>> DMD shouldn't accept code like this in the first place. It's very
>> brittle, the result depends on random compiler implementation details.
>
> I would argue that this is an entirely different case than my example,
> but we are getting into compiler implementation details that I know
> nothing about, so I can't actually say whether it is (or should) be
> valid code.
>
>

It's basically the same thing.
September 01, 2016
On Thursday, 1 September 2016 at 19:32:23 UTC, Timon Gehr wrote:
>> Well, I'd argue that's not quite right and the correct interpretation is
>> "If not the other X then this X", due to the `!__traits(compiles,
>> .f!T)`, explicitly telling the compiler to check if the *other*
>> "overloads" compile.
>
> Even if that was the intention of the compiler implementation, the example below demonstrates why it cannot work.
>
>> I don't actually know whether template constraints
>> are considered to be at module scope or in the template scope, though,
>> so maybe I'm completely wrong on this.
>> ...
>
> Template scope, but /nothing/ about '.' says "other".

It means to look up the symbol f at module scope, so I guess it depends on whether the compiler excludes the current template from that lookup.

template f() if (someCondition) {}
template f() if (someOtherCondition) {}
template f() if (!__traits(compiles, { alias _ = .f!(); })) {}

Does `.f` refer to all symbols named f at module scope, or all symbols named f *other than* the symbol for which the current template constraint is being processed?

Actually, I just convinced myself that it's obviously not the latter and so must be the former, and now I see why this shouldn't work.
September 02, 2016
On Thursday, 1 September 2016 at 05:37:50 UTC, Manu wrote:
> So, consider a set of overloads:
>
>   void f(T)(T t) if(isSomething!T) {}
>   void f(T)(T t) if(isSomethingElse!T) {}
>   void f(T)(T t) {}
>
> I have a recurring problem where I need a fallback function like the bottom one, which should be used in lieu of a more precise match. This is obviously an ambiguous call, but this is a pattern that comes up an awful lot. How to do it in D?
>
> I've asked this before, and people say:
>
>   void f(T)(T t) if(!isSomething!T && !isSomethingElse!T) {}
>
> Consider that more overloads are being introduced by users spread out across many modules that define their own kind of T; this solution is no good.

In the past, I have suggested using the "default" keyword to specify a fallback function of this kind. I think it's a useful pattern for generic algorithms that have optimized variants on specific types for performance.

   void f(T)(T t) if(isSomething!T) {}
   void f(T)(T t) if(isSomethingElse!T) {}
   void f(T)(T t) default {}
September 03, 2016
On 3 September 2016 at 00:18, Xinok via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>
> In the past, I have suggested using the "default" keyword to specify a fallback function of this kind. I think it's a useful pattern for generic algorithms that have optimized variants on specific types for performance.
>
>    void f(T)(T t) if(isSomething!T) {}
>    void f(T)(T t) if(isSomethingElse!T) {}
>    void f(T)(T t) default {}

It's an interesting idea... flesh out a DIP?
September 03, 2016
On 9/3/16 2:41 AM, Manu via Digitalmars-d wrote:
> On 3 September 2016 at 00:18, Xinok via Digitalmars-d
> <digitalmars-d@puremagic.com> wrote:
>>
>> In the past, I have suggested using the "default" keyword to specify a
>> fallback function of this kind. I think it's a useful pattern for generic
>> algorithms that have optimized variants on specific types for performance.
>>
>>    void f(T)(T t) if(isSomething!T) {}
>>    void f(T)(T t) if(isSomethingElse!T) {}
>>    void f(T)(T t) default {}
>
> It's an interesting idea... flesh out a DIP?

We're better off without that. -- Andrei
September 02, 2016
On 9/1/2016 10:49 AM, Timon Gehr wrote:
> The following causes an ICE (DMD segfaults).
>
> import std.stdio;
>
> int f(T)(T t) if(!__traits(compiles,.f!T)) {
>     return 0;
> }
> int f(T)(T t) if(!__traits(compiles,.f!T)) {
>     return 1;
> }
>
> void main(){
>     writeln(f(2));
> }

Please post seg faults to bugzilla.

September 03, 2016
On 3 September 2016 at 11:38, Andrei Alexandrescu via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
> On 9/3/16 2:41 AM, Manu via Digitalmars-d wrote:
>>
>> On 3 September 2016 at 00:18, Xinok via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>>>
>>>
>>> In the past, I have suggested using the "default" keyword to specify a fallback function of this kind. I think it's a useful pattern for generic algorithms that have optimized variants on specific types for performance.
>>>
>>>    void f(T)(T t) if(isSomething!T) {}
>>>    void f(T)(T t) if(isSomethingElse!T) {}
>>>    void f(T)(T t) default {}
>>
>>
>> It's an interesting idea... flesh out a DIP?
>
>
> We're better off without that. -- Andrei

Then we need a decent way to do this.
As I've just expressed in the ADL thread, this whole pattern, which is
alleged as one of D's core offerings; templates + UFCS -> pipeline
programming (or 'component' programming as Walter likes to call it),
is loaded with issues, and other than SFINAE being a pita, and UFCS
coming to C++ 'soon'™, the whole thing is much easier in C++ right
now.