April 23, 2018
I think both versions are not equivalent at all. Consider [1]:

```
import std.meta;

void main()
{
    pragma(msg, __traits(getMember, A, "Foo1").stringof); // Foo1(int N) if (N & 1)
    pragma(msg, __traits(getAttributes, __traits(getMember, A, "Foo1"))[0]); // tuple("int", "odd")
    alias f1a = Instantiate!(__traits(getMember, A, "Foo1"), 1); // This is expected
    pragma(msg, f1a); // A
    alias f1b = Instantiate!(__traits(getMember, A, "Foo1"), "+"); // Why would I know that I can even instantiate?? Also, can I haz UDA plz?
    pragma(msg, f1b); // B
}

class A {
    @("int", "odd")
	template Foo1(int N) if (N & 1)    {
        enum Foo1 = "A";
    }
    @("string", "+")
	template Foo1(string op) if (op == "+") {
        enum Foo1 = "B";
    }
}
```

In this case you could perhaps use an alias parameter to achieve a similar effect. I haven't tried it but it would be really messy, if it even works.

What seems clear to me from this case is that *internally* the compiler sees a *set* of "overloads" (change that word if you think it should only apply to functions), but I can only get the first of the batch!

For example, I might want to add some information in the UDA on how to instantiate the template, but then I can't get the UDA either. I'm sure it's somewhere, just that we get no access to it, and that shouldn't be too hard to add.

I think this clarifies a bit the problem I see.

A.

[1]: https://run.dlang.io/is/zRDHGn

On 04/23/2018 05:00 PM, Alex wrote:
> On Monday, 23 April 2018 at 14:22:13 UTC, Simen Kjærås wrote:
>> As with all things D, the only real spec is the compiler source code. :p :(
> 
> :p
> 
>> Proving that two templates are equivalent is in general impossible, since any amount of wasted computation could be performed before the end result is returned, and inputs must be tested exhaustively for the proof to be valid. The fact that two templates give the same result in one special case does not mean that they are equivalent in the general case, and the compiler needs to care about the general case.
> 
> Ok, thats exactly the point. If you have functions
> 
> void foo() {}
> void foo(int n) {}
> 
> There is no ambiguity which function will be chosen if it will be called.
> 
> If you have templates
> 
> // form 1
> template Foo(int N) if (N & 1)    {} // A
> template Foo(int N) if (!(N & 1)) {} // B
> 
> OR
> 
> // form 2
> template foo(int N)
> {
>      static if(N & 1){} // A
>      else{} // B
> }
> 
> There is also no ambiguity which will be called.
> 
> However, getOverloads will behave differently.
> 
> This is not bad at all. But you have to admit, that while now, there is no way to distinguish form 1 and form 2, with the new getOverloads there will be.
> This seems strange to me, because there is no reason to distinguish form 1 and form 2. (Because the callable code, which will be generated is the same, I hope... ?)
> 
> So, in particular, I'm not against the feature. And if the equivalence between form 1 and form 2 is gone, so what. But I don't understand the reasoning why something which is now equal won't be equal any more later?

April 23, 2018
On Monday, 23 April 2018 at 15:44:10 UTC, Simen Kjærås wrote:
> Ah, but I'm not looking to instantiate the templates, but to learn about them - how many parameters do they take? Are their UDAs different, so that I should warn the programmer? Must I wrap them in different ways?
>

So... Do I have to correct my semantic equality assumptions?

There should be a large chapter in the docu about this. It would change at the same time with your feature and the things which are implied and which are not would be evident to the world...
April 23, 2018
On Monday, 23 April 2018 at 16:16:09 UTC, Arafel wrote:
> ```
> import std.meta;
>
> void main()
> {
>     pragma(msg, __traits(getMember, A, "Foo1").stringof); // Foo1(int N) if (N & 1)
>     pragma(msg, __traits(getAttributes, __traits(getMember, A, "Foo1"))[0]); // tuple("int", "odd")
>     alias f1a = Instantiate!(__traits(getMember, A, "Foo1"), 1); // This is expected
>     pragma(msg, f1a); // A
>     alias f1b = Instantiate!(__traits(getMember, A, "Foo1"), "+"); // Why would I know that I can even instantiate?? Also, can I haz UDA plz?
>     pragma(msg, f1b); // B
> }
>
> class A {
>     @("int", "odd")
> 	template Foo1(int N) if (N & 1)    {
>         enum Foo1 = "A";
>     }
>     @("string", "+")
> 	template Foo1(string op) if (op == "+") {
>         enum Foo1 = "B";
>     }
> }
> ```

I'm not arguing about the case of different interfaces. It is more or less ok, as from different argument types it will be unambiguous which template will be instantiated. It is the case of differentiating templates by their structure and/or constraints.

In this case, it is almost sure, that more then one form of implementation exists. However, the forms will yield the same semantic result. And I'm wondering why the implementation form alone leads to differentiation.
April 23, 2018
On Monday, 23 April 2018 at 16:52:11 UTC, Alex wrote:
> On Monday, 23 April 2018 at 16:16:09 UTC, Arafel wrote:
>> ```
>> import std.meta;
>>
>> void main()
>> {
>>     pragma(msg, __traits(getMember, A, "Foo1").stringof); // Foo1(int N) if (N & 1)
>>     pragma(msg, __traits(getAttributes, __traits(getMember, A, "Foo1"))[0]); // tuple("int", "odd")
>>     alias f1a = Instantiate!(__traits(getMember, A, "Foo1"), 1); // This is expected
>>     pragma(msg, f1a); // A
>>     alias f1b = Instantiate!(__traits(getMember, A, "Foo1"), "+"); // Why would I know that I can even instantiate?? Also, can I haz UDA plz?
>>     pragma(msg, f1b); // B
>> }
>>
>> class A {
>>     @("int", "odd")
>> 	template Foo1(int N) if (N & 1)    {
>>         enum Foo1 = "A";
>>     }
>>     @("string", "+")
>> 	template Foo1(string op) if (op == "+") {
>>         enum Foo1 = "B";
>>     }
>> }
>> ```
>
> I'm not arguing about the case of different interfaces. It is more or less ok, as from different argument types it will be unambiguous which template will be instantiated. It is the case of differentiating templates by their structure and/or constraints.
>
> In this case, it is almost sure, that more then one form of implementation exists. However, the forms will yield the same semantic result. And I'm wondering why the implementation form alone leads to differentiation.

Well, with templates the overload resolution must be always unambiguous:

```
import std.stdio;
void main()
{
	pragma(msg, A.Foo1!2);
	pragma(msg, A.Foo1!3);
	static assert(!is (typeof(A.Foo1!6))); // Compilation failure if there is any ambiguity
}

class A {
	template Foo1(int N) if ((N % 2) == 0)    {
		enum Foo1 = "A";
     }
	template Foo1(int N) if ((N % 3) == 0) {
		enum Foo1 = "B";
	}
}
```

Also, you can try without a constraint, it will still complain.

But you are arguing from the point of view of a hypothetical semantical equivalence that I don't think it's so clear. Both are tools that in some cases can lead to the same result, but there are also cases where they don't math.

You could also argue that function overloads are just semantically equivalent to a single function with variadic arguments. Whether the compiler actually lowers it like that or not should be just an implementation detail, and thus simply not relevant.

And from a syntactical point of view, it wouldn't make any sense if the following "overloads" were treated differently:

```
class A {
    @("int", "odd")
	template Foo1(int N) if (N & 1)    {
        enum Foo1 = "A";
    }
    @("int", "even")
	template Foo1(int N) if (!(N & 1))    {
        enum Foo1 = "B";
    }
    @("string", "+")
	template Foo1(string op) if (op == "+") {
        enum Foo1 = "C";
    }
    @("multi", "string")
	template Foo1(T...) if (allSatisfy!(isSomeString, typeof(T)) && T.length > 1) {
        enum Foo1 = "D";
    }
    @("multi", "double")
	template Foo1(T...) if (allSatisfy!(isFloatingPoint, typeof(T)) && T.length > 1) {
        enum Foo1 = "E";
    }
}
```

How would you know which ones are "real" overloads (in your meaning)?

A.
April 23, 2018
On Monday, 23 April 2018 at 17:46:10 UTC, Arafel wrote:
>
> You could also argue that function overloads are just semantically equivalent to a single function with variadic arguments.

It is not. As there are exact known, distinct, finite numbers and types of arguments of functions, which can be used. For example, zero and one.

So:

void foo(){}
void foo(int){}

How this is achievable with variadic functions?

> Whether the compiler actually lowers it like that or not should be just an implementation detail, and thus simply not relevant.

Right.

>
> And from a syntactical point of view, it wouldn't make any sense if the following "overloads" were treated differently:
>
> ```
> class A {
>     @("int", "odd")
> 	template Foo1(int N) if (N & 1)    {
>         enum Foo1 = "A";
>     }
>     @("int", "even")
> 	template Foo1(int N) if (!(N & 1))    {
>         enum Foo1 = "B";
>     }
>     @("string", "+")
> 	template Foo1(string op) if (op == "+") {
>         enum Foo1 = "C";
>     }
>     @("multi", "string")
> 	template Foo1(T...) if (allSatisfy!(isSomeString, typeof(T)) && T.length > 1) {
>         enum Foo1 = "D";
>     }
>     @("multi", "double")
> 	template Foo1(T...) if (allSatisfy!(isFloatingPoint, typeof(T)) && T.length > 1) {
>         enum Foo1 = "E";
>     }
> }
> ```

So, in my opinion, a callable object is overloaded, when there exist more then one interface how to call it.

Before a template is instantiated, no callable objects exist.

Now, you can go on say well, a template defines a family of objects, which build up an overload set. This is not doable, see the discussion before.

But of course, you can go the other way and say well, if there exist a declaration then, based on the declaration inspection, let us define an overload set, based on the same symbol names.

This is a different way of defining an overload, which is not obvious, especially if you see the pragma output from a former post. Therein the pragmas, which act also before runtime do not differ between declarations, but only between instantiations.
1 2 3
Next ›   Last »