Bug 1528 – [tdpl] overloading template and non-template functions
Status
RESOLVED
Resolution
FIXED
Severity
major
Priority
P2
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2007-09-22T21:44:00Z
Last change time
2014-05-24T09:14:39Z
Keywords
pull, rejects-valid, TDPL
Assigned to
k.hara.pg
Creator
davidl
Comments
Comment #0 by davidl — 2007-09-22T21:44:52Z
int[char] v;
char m;
int d;
template toString (U,C,T:U[C])
{
char[] toString(U m,C c, T t) { return ""; }
}
char[] toString(int k){return "";}
The confliction can actually be detected till the last moment of instantiation. I don't see why we prevent it at the moment.
Comment #1 by wbaxter — 2007-09-22T22:17:59Z
The ability to do this sort of overloading is scheduled to be added to D2.0, according to the notes from the Walter/Andrei talk at the D conference.
Comment #2 by yebblies — 2011-12-24T08:32:10Z
*** Issue 7134 has been marked as a duplicate of this issue. ***
Comment #3 by yebblies — 2011-12-24T08:37:21Z
From Andrei's comment in 7134:
This TDPL code does not compile:
class A {
// Non-overridable method
A opBinary(string op)(A rhs) {
// Forward to an overridable function
return opBinary(op, rhs);
}
// Overridable method, dispatch string at runtime
A opBinary(string op, A rhs) {
switch (op) {
case "+":
break;
case "-":
break;
}
}
}
Overloading template and non-template functions must be implemented.
Comment #4 by andrei — 2011-12-24T09:15:52Z
Thanks for consolidating!
Comment #5 by andrej.mitrovich — 2012-01-21T18:22:11Z
*** Issue 2972 has been marked as a duplicate of this issue. ***
Comment #6 by simen.kjaras — 2012-08-25T05:53:56Z
This bug causes problems with @disabled default constructors.
Given:
struct Foo {
@disable this();
this( T )( T a ) {
}
}
This bug triggers.
Given instead:
struct Bar {
@disable this( )( );
this( T )( T a ) {
}
}
The default constructor is not disabled.
Comment #7 by issues.dlang — 2012-08-25T11:18:39Z
> This bug causes problems with @disabled default constructors.
From what I can tell, @disable this() doesn't work _at all_: bug# 7021
I don't think that this bug has anything to do with it.
And I wouldn't expect @disable this()(); to work anyway, because the function that you're trying to disable doesn't exist. It would have to be instantiatied to exist, and it's not possible to call a default constructor on a struct, since they're not legal in the first place, so it can't be instantiated. @disable this()(); should probably result in an error.
Comment #8 by simen.kjaras — 2012-08-25T12:19:25Z
(In reply to comment #7)
> > This bug causes problems with @disabled default constructors.
>
> From what I can tell, @disable this() doesn't work _at all_: bug# 7021
@disable this() does work in some cases:
struct Foo {
@disable this( );
}
struct Bar {
Foo f;
this( int n ) {
// Error: constructor Bar.this field f must be initialized in constructor
}
}
void main( ) {
Foo f; // Error: variable main.d initializer required for type Foo
}
However, if you need a templated constructor, this goes out the window, for the reasons outlined above.
> I don't think that this bug has anything to do with it.
Yes and no. The problems of @disable this are exacerbated by this bug.
> And I wouldn't expect @disable this()(); to work anyway, because the function
> that you're trying to disable doesn't exist. It would have to be instantiatied
> to exist, and it's not possible to call a default constructor on a struct,
> since they're not legal in the first place, so it can't be instantiated.
> @disable this()(); should probably result in an error.
Nor would I, but when faced with the error that templated and non-templated functions cannot form an overload set together, that is the logical thing to try, and it doesn't work either.
(In reply to comment #9)
> https://github.com/D-Programming-Language/dmd/pull/1409
By implementing it, I found some corner cases. How should these behave?
int f1(int a, double=10) { return 1; }
int f1(int a, string="") { return 2; }
int f2(T:int)(T b, double=10) { return 1; }
int f2(T:int)(T b, string="") { return 2; }
// vs deduced parameter
int f3(int a) { return 1; }
int f3(T)(T b) { return 2; }
// vs specialized parameter
int f4(int a) { return 1; }
int f4(T:int)(T b) { return 2; }
// vs deduced parameter + template constraint (1)
int f5(int a) { return 1; }
int f5(T)(T b) if (is(T == int)) { return 2; }
// vs deduced parameter + template constraint (2)
int f6(int a) { return 1; }
int f6(T)(T b) if (is(T : int)) { return 2; }
void main()
{
f1(1); // ambiguous error
f1(1L); // ambiguous error
f2(1); // ambiguous error
f2(1L); // ambiguous error
f3(1); // ?
f3(1L); // ?
f4(1); // ?
f4(1L); // ?
f5(1); // ?
f5(1L); // ?
f6(1); // ?
f6(1L); // ?
}
Comment #11 by bugzilla — 2013-02-24T21:54:47Z
(In reply to comment #10)
> f3(1); // 1
> f3(1L); // 2
>
> f4(1); // ambiguous
> f4(1L); // ambiguous
>
> f5(1); // 1
> f5(1L); // 1
>
> f6(1); // 1
> f6(1L); // 2
> }
The more specialized overload always wins.
The constraint is not considered when evaluating which is "more specialized". (Because in general we cannot evaluate that.) The constraint only determines if an overload is to be considered - it does not determine ordering.
Comment #12 by k.hara.pg — 2013-02-24T22:30:26Z
(In reply to comment #11)
> The more specialized overload always wins.
>
> The constraint is not considered when evaluating which is "more specialized".
> (Because in general we cannot evaluate that.) The constraint only determines if
> an overload is to be considered - it does not determine ordering.
Thanks for quickly answer. So, the resolution result of f5 and f6 should be same as f3, because they are identical when hide their constraints. Right?
But, there is still questionable. How to calculate "The more specialized overload" between function template and non-template one? For example, try to consider case for f3(1). That is:
1. Normal function version f3(int) will match exactly to one int argument.
2. Function template version will deduce T <- int, and then instantiated function f3!(int) == void f3(int) will match _exactly_ to one int argument.
But, in general, template version is less specialized than non-template version. So, there is something necessary for ordering.
---
Consider one another case for f4(1L). That is:
1. Normal function will match to one long argument with conversion (MATCHconvert).
2. Function template version will deduce T <- long and then instantiated function f4!long will match exactly to one long argument.
Which is specialized? In general, template version would be intended to pick up non-exact matching for generic cases. But, as far as I infer from current dmd implementation, f4(1L) will be ambiguous (template type parameter deduction without specialization always be MATCHconvert).
Therefore, I'd ask question again to Walter: how does above cases behave?
Comment #13 by timon.gehr — 2013-02-25T02:15:54Z
(In reply to comment #12)
> ...
>
> Therefore, I'd ask question again to Walter: how does above cases behave?
I have implemented it in accordance with TDPL, and the following behaviour results:
int f1(int a, double=10) { return 1; }
int f1(int a, string="") { return 2; }
int f2(T:int)(T b, double=10) { return 1; }
int f2(T:int)(T b, string="") { return 2; }
// vs deduced parameter
int f3(int a) { return 1; }
int f3(T)(T b) { return 2; }
// vs specialized parameter
int f4(int a) { return 1; }
int f4(T:int)(T b) { return 2; }
// vs deduced parameter + template constraint (1)
int f5(int a) { return 1; }
int f5(T)(T b) if (is(T == int)) { return 2; }
// vs deduced parameter + template constraint (2)
int f6(int a) { return 1; }
int f6(T)(T b) if (is(T : int)) { return 2; }
void main(){
f1(1); // error: ambiguous
f1(1L); // error: ambiguous
f2(1); // error: ambiguous
f2(1L); // error: no match
static assert(f3(1)==1);
static assert(f3(1L)==2);
static assert(f4(1)==1);
static assert(f4(1L)==1);
static assert(f5(1)==1);
static assert(f5(1L)==1);
static assert(f6(1)==1);
static assert(f6(1L)==1);
}
Comment #14 by monarchdodra — 2013-02-27T04:39:08Z
(In reply to comment #13)
> I have implemented it in accordance with TDPL, and the following behaviour
> results:
>
> // vs deduced parameter + template constraint (2)
> int f6(int a) { return 1; }
> int f6(T)(T b) if (is(T : int)) { return 2; }
>
> void main(){
> static assert(f6(1)==1);
> static assert(f6(1L)==1);
> }
How does that work though, because here, you statically know that 1L will fit in your int. But what about:
static assert(f6(1L) == 1);
static assert(f6(ulong.max) == 2); // (a) ???
ulong ul = runtime();
static assert(f6(ul) == 2); // (b) ???
How would these resolve?
I am not really comfortable with the fact that a call can statically resolve to two different functions depending on the static information of the *value* of a parameter:
int f7(ubyte a) { return 1; }
int f7(T)(T b) if (is(T : int)) { return 2; }
void main(){
static assert(f6(200u)==1); //Calls first
static assert(f6(400u)==2); //Calls second (!?)
//Run-time variable with TDPL-like range knowledge
uint a = 400;
static assert(f6(a) == 2); //Calls second ?
a = 200;
static assert(f6(a) == 1); //But now calls first !?
}
Comment #15 by timon.gehr — 2013-02-27T06:29:43Z
(In reply to comment #14)
> ...
>
> static assert(f6(1L) == 1);
> static assert(f6(ulong.max) == 2); // (a) ???
>
No match.
> ulong ul = runtime();
> static assert(f6(ul) == 2); // (b) ???
>
No match.
> How would these resolve?
>
> I am not really comfortable with the fact that a call can statically resolve to
> two different functions depending on the static information of the *value* of a
> parameter:
>
Well, that is how the language is specified.
int f8(byte){ return 1; }
int f8(long){ return 2; }
void main(){
static assert(f8(1)==1); // calls first
static assert(f8(256)==2); // calls second
int x=1;
f8(x); // calls second
}
> int f7(ubyte a) { return 1; }
> int f7(T)(T b) if (is(T : int)) { return 2; }
>
> void main(){
> static assert(f6(200u)==1); //Calls first
No, calls second.
> static assert(f6(400u)==2); //Calls second (!?)
>
Yes.
> //Run-time variable with TDPL-like range knowledge
> uint a = 400;
Knowledge lost here.
> static assert(f6(a) == 2); //Calls second ?
> a = 200;
Ditto.
> static assert(f6(a) == 1); //But now calls first !?
> }
Comment #16 by k.hara.pg — 2013-02-28T20:46:30Z
(In reply to comment #13)
> // vs specialized parameter
> int f4(int a) { return 1; }
> int f4(T:int)(T b) { return 2; }
> static assert(f4(1)==1);
Just only this is wrong. f4(T:int) is specialized to int argument, so f4(1) could match both normal function version and template version *with the same extent*. Then it will be ambiguous.
> static assert(f4(1L)==1);
Ok. f4(T:int) cannot instantiate with T==long, so first version will be called.
(In reply to comment #15)
> (In reply to comment #14)
> > ...
> >
> > static assert(f6(1L) == 1);
> > static assert(f6(ulong.max) == 2); // (a) ???
> >
>
> No match.
>
> > ulong ul = runtime();
> > static assert(f6(ul) == 2); // (b) ???
> >
>
> No match.
Both (a) and (b) should be "no match", but with my experimental change, (a) wrongly matches to int. I found an another bug in there, and filed it as bug 9617.
Current test case results with my pull request:
https://github.com/D-Programming-Language/dmd/pull/1409
is:
--------
int f1(int a, double=10) { return 1; }
int f1(int a, string="") { return 2; }
int f2(T:int)(T b, double=10) { return 1; }
int f2(T:int)(T b, string="") { return 2; }
// vs deduced parameter
int f3(int a) { return 1; }
int f3(T)(T b) { return 2; }
// vs specialized parameter
int f4(int a) { return 1; }
int f4(T:int)(T b) { return 2; }
// vs deduced parameter + template constraint (1)
int f5(int a) { return 1; }
int f5(T)(T b) if (is(T == int)) { return 2; }
// vs deduced parameter + template constraint (2)
int f6(int a) { return 1; }
int f6(T)(T b) if (is(T : int)) { return 2; }
// vs nallowing conversion
int f7(ubyte a) { return 1; }
int f7(T)(T b) if (is(T : int)) { return 2; }
void main()
{
static assert(!__traits(compiles, f1(1))); // ambiguous
static assert(!__traits(compiles, f1(1L))); // ambiguous
static assert(!__traits(compiles, f2(1))); // ambiguous
static assert(!__traits(compiles, f2(1L))); // no match
assert(f3(1) == 1);
assert(f3(1L) == 2);
static assert(!__traits(compiles, f4(1)));
assert(f4(1L) == 1);
assert(f5(1) == 1);
assert(f5(1L) == 1);
assert(f6(1) == 1);
assert(f6(1L) == 1);
static assert(!__traits(compiles, f6(ulong.max))); // no match
// needs to fix bug 9617
ulong ulval = 1;
static assert(!__traits(compiles, f6(ulval))); // no match
assert(f7(200u) == 2);
assert(f7(400u) == 2);
uint uival = 400; // TDPL-like range knowledge lost here.
assert(f7(uival) == 2);
a = 200; // Ditto.
assert(f7(uival) == 2);
}
--------
I welcome more complicated test case.
Comment #17 by code — 2013-03-01T02:19:23Z
> template type parameter deduction without specialization always be MATCHconvert
AFAIK this is a kludgy implementation detail to make specialization work. The last time we worked on that we concluded (with Daniel Murphy?) that there should be an additional level between convert and exact. I don't remember the details right now, but I'll try to find the relevant discussion.
Comment #18 by timon.gehr — 2013-03-01T03:15:40Z
(In reply to comment #17)
> > template type parameter deduction without specialization always be MATCHconvert
>
> AFAIK this is a kludgy implementation detail
It is a bug.
> to make specialization work.
Why wouldn't they work otherwise?
> The
> last time we worked on that we concluded (with Daniel Murphy?) that there
> should be an additional level between convert and exact. I don't remember the
> details right now, but I'll try to find the relevant discussion.
There already is a level between them (conversion to const.)
Anyway, I do not see why another level would be required, or even helpful in any way.
Comment #19 by k.hara.pg — 2013-03-01T03:26:51Z
(In reply to comment #17)
> > template type parameter deduction without specialization always be MATCHconvert
>
> AFAIK this is a kludgy implementation detail to make specialization work. The
> last time we worked on that we concluded (with Daniel Murphy?) that there
> should be an additional level between convert and exact. I don't remember the
> details right now, but I'll try to find the relevant discussion.
Are you saying about MATCHdeduced? I had seen the Daniel Murphy's pull request somewhere, but it is completely unnecessary.
In IFTI, the match level for template arguments and for function arguments are distinguished. The former is prior than the latter, so there is 3 * 3 + 1 match level in IFTI.
For tiargs: exact or const or convert (3)
For funargs: exact or const or convert (* 3)
noatch (+ 1)
During implementation, I found that the two matching levels had not be separated correctly (the bug was in TemplateDeclaration::deduceFunctionTemplateMatch). After fixing the bug, I can believe that MATCHdeduced is not need anymore.
Comment #20 by timon.gehr — 2013-03-01T03:40:36Z
(In reply to comment #19)
> (In reply to comment #17)
> ...
>
> In IFTI, the match level for template arguments and for function arguments are
> distinguished. The former is prior than the latter, so there is 3 * 3 + 1 match
> level in IFTI.
>
> For tiargs: exact or const or convert (3)
> For funargs: exact or const or convert (* 3)
> noatch (+ 1)
> ...
It can be seen that way. They are ordered from worst to best like this:
no match
ifti convert
function convert
ifti const
function const
ifti exact
function exact
Comment #21 by k.hara.pg — 2013-03-01T03:55:37Z
(In reply to comment #20)
> (In reply to comment #19)
> > (In reply to comment #17)
> > ...
> >
> > In IFTI, the match level for template arguments and for function arguments are
> > distinguished. The former is prior than the latter, so there is 3 * 3 + 1 match
> > level in IFTI.
> >
> > For tiargs: exact or const or convert (3)
> > For funargs: exact or const or convert (* 3)
> > noatch (+ 1)
> > ...
>
> It can be seen that way. They are ordered from worst to best like this:
More precisely:
MATCHnomatch
tiargs:MATCHconvert / funarg:MATCHconvert
tiargs:MATCHconvert / funarg:MATCHconst
tiargs:MATCHconvert / funarg:MATCHexact
tiargs:MATCHconst / funarg:MATCHconvert
tiargs:MATCHconst / funarg:MATCHconst
tiargs:MATCHconst / funarg:MATCHexact
tiargs:MATCHexact / funarg:MATCHconvert
tiargs:MATCHexact / funarg:MATCHconst
tiargs:MATCHexact / funarg:MATCHexact
Additionally, when comparing normal function and template function matching, tiargs MATCH level is treated as equal to funargs level.
Comment #22 by timon.gehr — 2013-03-01T11:48:00Z
(In reply to comment #21)
> (In reply to comment #20)
> > (In reply to comment #19)
> > > (In reply to comment #17)
> > > ...
> > >
> > > In IFTI, the match level for template arguments and for function arguments are
> > > distinguished. The former is prior than the latter, so there is 3 * 3 + 1 match
> > > level in IFTI.
> > >
> > > For tiargs: exact or const or convert (3)
> > > For funargs: exact or const or convert (* 3)
> > > noatch (+ 1)
> > > ...
> >
> > It can be seen that way. They are ordered from worst to best like this:
>
> More precisely:
>
> MATCHnomatch
> tiargs:MATCHconvert / funarg:MATCHconvert
> tiargs:MATCHconvert / funarg:MATCHconst
> tiargs:MATCHconvert / funarg:MATCHexact
> tiargs:MATCHconst / funarg:MATCHconvert
> tiargs:MATCHconst / funarg:MATCHconst
> tiargs:MATCHconst / funarg:MATCHexact
> tiargs:MATCHexact / funarg:MATCHconvert
> tiargs:MATCHexact / funarg:MATCHconst
> tiargs:MATCHexact / funarg:MATCHexact
>
> Additionally, when comparing normal function and template function matching,
> tiargs MATCH level is treated as equal to funargs level.
Ah, now I see what you mean. You are matching foo!tiargs(funargs).
I was talking about resolving template vs. non-template overloads in the foo(funargs) setting.
Comment #23 by k.hara.pg — 2013-03-01T18:17:08Z
(In reply to comment #22)
> Ah, now I see what you mean. You are matching foo!tiargs(funargs).
>
> I was talking about resolving template vs. non-template overloads in the
> foo(funargs) setting.
I'm talking about both. You can think type parameters deduction and function arguments matching separately. For example:
void foo(T)(T) {}
void foo(T:int)(T) {}
With first overload version,
Phase 1: T is deduced to int. ==> MATCHconvert.
Phase 2: 1 matches T (==already deduced to int). ==> MATCHexact.
With second overload version,
Phase 1: T is deduced to int and is specialized to int. ==> MATCHexact.
Phase 2: 1 matches T (==already deduced to int). ==> MATCHexact.
Finally, foo(T)(T) is less specialized than foo(T:int)(T).
I explain more complicated case.
void bar(int, int) {}
void bar(T)(T, int) {}
bar(1, 1L);
With first overload version,
Phase 1: normal function is treated as having empty template parameter list, so
matching is always exact ==> MATCHexact
Phase 2: int <- 1 == MATCHexact, int <- 1L == MATCHconvert. ==> MATCHconvert
With second overload version,
Phase 1: T <- typeof(int). ==> MATCHconvert
Phase 2: T <- 1 == MATCHexact, int <- 1L == MATCHconvert ==> MATCHconvert
From the comparison of Phase 1 result, bar(int, int) is more specialized than bar(T)(T, int) for function arguments (1, 1L).
Comment #24 by code — 2013-03-14T08:46:43Z
(In reply to comment #23)
I think this rule is problematic for the function vs. deduced parameter case.
// vs deduced parameter
int f3(int a) { return 1; }
int f3(T)(T b) { return 2; }
f3(1L);
With first overload version,
Phase 1: normal function is treated as having empty template parameter list, so
matching is always exact ==> MATCHexact
Phase 2: int <- 1L == MATCHconvert ==> MATCHconvert
With second overload version,
Phase 1: T <- typeof(1L) = long ==> MATCHconvert
Phase 2: T <- 1L == MATCHexact ==> MATCHexact
- the current implementation in the pull request chooses
the function whereas comment #13 and #16 suggest to go
with the function template
https://github.com/9rnsr/dmd/blob/b141e29e29b1ec43873c7e0374d27d3fbbae8085/test/runnable/overload.d#L216https://github.com/9rnsr/dmd/blob/b141e29e29b1ec43873c7e0374d27d3fbbae8085/test/runnable/overload.d#L279
- C++ chooses the function template
- For me it's counterintuitive to call with conversion when an exact
match can be instantiated. Even an ambiguous error seems more reasonable to me.
Comment #25 by code — 2013-03-14T08:52:21Z
I find the C++ rule pretty plausible.
> In most cases a function template behaves just like a normal function when
> considering overload resolution. The template argument deduction is
> applied, if it succeeds, the function is added to the candidates set. Such
> a function is handled like any other function, except when two viable
> functions are equally good, the non-template one is selected. In case both
> are a specialisation of a function template, partial ordering rules are
> applied. The partial ordering rules are out of the scope of this article.
http://accu.org/index.php/journals/268#d0e340
Comment #26 by k.hara.pg — 2013-03-14T09:01:49Z
(In reply to comment #24)
> (In reply to comment #23)
>
> I think this rule is problematic for the function vs. deduced parameter case.
>
> // vs deduced parameter
> int f3(int a) { return 1; }
> int f3(T)(T b) { return 2; }
> f3(1L);
>
> With first overload version,
> Phase 1: normal function is treated as having empty template parameter list, so
> matching is always exact ==> MATCHexact
> Phase 2: int <- 1L == MATCHconvert ==> MATCHconvert
>
> With second overload version,
> Phase 1: T <- typeof(1L) = long ==> MATCHconvert
> Phase 2: T <- 1L == MATCHexact ==> MATCHexact
>
> - the current implementation in the pull request chooses
> the function whereas comment #13 and #16 suggest to go
> with the function template
>
> https://github.com/9rnsr/dmd/blob/b141e29e29b1ec43873c7e0374d27d3fbbae8085/test/runnable/overload.d#L216
> https://github.com/9rnsr/dmd/blob/b141e29e29b1ec43873c7e0374d27d3fbbae8085/test/runnable/overload.d#L279
>
> - C++ chooses the function template
>
> - For me it's counterintuitive to call with conversion when an exact
> match can be instantiated. Even an ambiguous error seems more reasonable to
> me.
Right now I think that was little bad example.
Ques. Why f3(1L) chooses non-template version?
Short Ans. Because the given argument 1L is a literal.
Long Ans.
In D, literals works as like polysemous value. 1L is implicitly convertible to int by Value Range Propagation, so it matchs with MATCHconvert.
If you give a runtime long value to f3:
long n;
f3(n);
With first overload version,
Phase 1: normal function is treated as having empty template parameter list, so
matching is always exact ==> MATCHexact
Phase 2: int <- n == MATCHnomatch (changed!!)
With second overload version,
Phase 1: T <- typeof(1L) = long ==> MATCHconvert
Phase 2: T <- 1L == MATCHexact ==> MATCHexact
So it will choose template version. How about?
Comment #27 by timon.gehr — 2013-03-14T15:34:50Z
(In reply to comment #25)
> I find the C++ rule pretty plausible.
>
> > In most cases a function template behaves just like a normal function when
> > considering overload resolution. The template argument deduction is
> > applied, if it succeeds, the function is added to the candidates set. Such
> > a function is handled like any other function, except when two viable
> > functions are equally good, the non-template one is selected. In case both
> > are a specialisation of a function template, partial ordering rules are
> > applied. The partial ordering rules are out of the scope of this article.
>
> http://accu.org/index.php/journals/268#d0e340
C++ does not have template value parameters. The C++ behaviour is approximated closely when IFTI type parameter deduction is treated as exact match. (There is no reason to do anything else, a type is required, and a type is given.)
However, it is not too clear that there must be one matching level for the template and one for the function.
@Kenji, why do you think this is required? Why does one matching level for everything not suffice?
Eg, I think the following code should not compile and show an ambiguity error.
void foo(double a)(int b){ }
void foo(int a)(double b){ }
void main(){
foo!1(1);
}
Comment #28 by code — 2013-03-14T15:55:08Z
(In reply to comment #26)
It's not about the polysemous literals.
What's irritating me is that a function call with conversion
is preferred over a function template instantiation with
exact arguments.
int f3(long) { return 1; }
int f3(T)(T) { return 2; }
int v = 2;
f3(v);
MATCHexact / MATCHconvert vs.
MATCHconvert / MATCHexact
Comment #29 by timon.gehr — 2013-03-14T15:56:33Z
(In reply to comment #28)
> (In reply to comment #26)
>
> It's not about the polysemous literals.
> What's irritating me is that a function call with conversion
> is preferred over a function template instantiation with
> exact arguments.
>
> int f3(long) { return 1; }
> int f3(T)(T) { return 2; }
>
> int v = 2;
> f3(v);
>
> MATCHexact / MATCHconvert vs.
> MATCHconvert / MATCHexact
IMO the second should be MATCHexact / MATCHexact.
Comment #30 by k.hara.pg — 2013-03-15T07:27:23Z
(In reply to comment #29)
> (In reply to comment #28)
> > (In reply to comment #26)
> >
> > It's not about the polysemous literals.
> > What's irritating me is that a function call with conversion
> > is preferred over a function template instantiation with
> > exact arguments.
> >
> > int f3(long) { return 1; }
> > int f3(T)(T) { return 2; }
> >
> > int v = 2;
> > f3(v);
> >
> > MATCHexact / MATCHconvert vs.
> > MATCHconvert / MATCHexact
>
> IMO the second should be MATCHexact / MATCHexact.
No. type parameter deduction is less specialized than matching to explicitly specialized type parameter.
void foo(T)(T) {} // generic version
void foo(T:int)(T) {} // specialized to int
foo(1); // calls T:int version
That is why f3(v) makes MATCHconvert / MATCHexact with template version.
Comment #31 by timon.gehr — 2013-03-15T15:52:59Z
(In reply to comment #30)
> ...
> Parameter deduction is less specialized than matching to explicitly
> specialized type parameter.
> ...
It's not less specialized than a normal function call without type parameters.
A type parameter should match a type argument exactly.
Specialization mustn't be confused with the matching level -- it should be checked in a second step, as is done for functions.
Why?: Specialization is a partial order, while the matching levels form a full order. There is no way to correctly implement specialization only by abusing matching levels.
(At the very least, the logic for template specialization should not interfere with template vs. function overloading!)
Comment #32 by k.hara.pg — 2013-03-15T16:51:36Z
(In reply to comment #31)
> (In reply to comment #30)
> > ...
> > Parameter deduction is less specialized than matching to explicitly
> > specialized type parameter.
> > ...
>
> It's not less specialized than a normal function call without type parameters.
> A type parameter should match a type argument exactly.
>
> Specialization mustn't be confused with the matching level -- it should be
> checked in a second step, as is done for functions.
>
> Why?: Specialization is a partial order, while the matching levels form a full
> order. There is no way to correctly implement specialization only by abusing
> matching levels.
>
> (At the very least, the logic for template specialization should not interfere
> with template vs. function overloading!)
I talked the expected behavior by using the words used in dmd implementation. Yes, MATCH(exact|const|convert|nomatch) might not be the best word to talk language specification. But it is not a true problem.
Essentially the thing I talking is:
void foo(int) {} // 1
void foo(T:int)(T) {} // 2
void foo(T)(T) {} // 3
foo(1);
Non template function (1) is specialized _as same as_ explicitly specialized template function (2). Then, normally deduced template function is less specialized than (1) and (2).
My patch represents the specialization order for IFTI by the pair of MATCH value. And, (3) is represented by MATCHconvert / MATCHexact internally.
Do not confuse internal representation and expected language specification/behavior.
Comment #33 by andrei — 2013-04-07T07:33:07Z
Great thread. Sorry I'm late to it! Let me make an appeal to simplicity - even if we come up with a very meaningful and consistent set of overloading rules, we lose if they are complicated. Simplicity is very important for figuring out what function will be called in a context.
In that spirit I'd like to propose a basic strategy as follows:
1. Define a SIMPLE partial ordering relation <= between functions called "at least as specialized" (and correspondingly a strict version < meaning "more specialized").
2. Whenever two or more functions match a call, there is a "winner" only if it is more specialized than all other candidates. (Not all candidates must be partially ordered among themselves, but the winner must be more specialized than all others.) Otherwise, there is ambiguity.
Now for the simple partial ordering relation: consider a call to a function fun(args) with two overloads fun1 and fun2 that would accept the arguments, i.e. fun1(args) and fun2(args) would both compile in isolation.
To decide whether fun1 and fun2 are partially ordered, we look at the sets of accepted arguments. If fun2 accepts any argument fun1 would accept, we write fun1 <= fun2 and we say "fun1 is at least as specialized as fun2" or "fun2 is not more specialized than fun1".
Now, if fun1 <= fun2 and fun2 <= fun1 then the two are "as specialized" so a decision cannot be made between the two. If only one of the relations applies, then one of them is strictly more specialized and it is preferred in an overloaded call. If neither relation applies, the functions are unordered so again a decision cannot be made.
On the examples given by Kenji:
int f1(int a, double=10) { return 1; }
int f1(int a, string="") { return 2; }
These functions are not ordered because e.g. f1(2, 3.4) would not be accepted by the second overload and f1(2, "hello") would not be accepted. So the call f1(5) is ambiguous.
int f2(T:int)(T b, double=10) { return 1; }
int f2(T:int)(T b, string="") { return 2; }
Same here, these overloads are not ordered.
// vs deduced parameter
int f3(int a) { return 1; }
int f3(T)(T b) { return 2; }
Here the second overload accepts f3("hello") so the first overload is more specialized. It will be preferred even in calls with an implicit conversion, e.g. f3(cast(short) 42). THIS IS A DEPARTURE FROM C++'S RULES.
// vs specialized parameter
int f4(int a) { return 1; }
int f4(T:int)(T b) { return 2; }
These two functions are in the same equivalence classes because they accept the same argument sets: f4_1 <= f4_2 and f4_2 <= f4_1. So any call that would match these two would be ambiguous.
// vs deduced parameter + template constraint (1)
int f5(int a) { return 1; }
int f5(T)(T b) if (is(T == int)) { return 2; }
The constraint is evaluated first and then "disappears" leaving two functions. Of these, the non-template is more specialized.
// vs deduced parameter + template constraint (2)
int f6(int a) { return 1; }
int f6(T)(T b) if (is(T : int)) { return 2; }
Same here. Given that the constraints are arbitrary Boolean expressions, we can't make good estimates on what sets of arguments they'll match.
Comment #34 by k.hara.pg — 2013-04-07T18:44:04Z
(In reply to comment #33)
[snip]
That's the rule described in TDPL. I can completely agree with you.
I believe that my patch implements it enough.
----
I'd like to add one note for your better understand.
> // vs specialized parameter
> int f4(int a) { return 1; }
> int f4(T:int)(T b) { return 2; }
>
> These two functions are in the same equivalence classes because they accept
> the same argument sets: f4_1 <= f4_2 and f4_2 <= f4_1. So any call that would
> match these two would be ambiguous.
That's true if and only if one int argument is given. In D, the template specialized parameter (T:int) means "T should exactly matches to int".
int fx(T:int)(T b) { return 2; }
void main() { fx(1L); }
test.d(2): Error: template test.fx does not match any function template declaration. Candidates are:
test.d(1): test.fx(T : int)(T b)
test.d(2): Error: template test.fx(T : int)(T b) cannot deduce template function from argument types !()(long)
So, f4 would match to non-template version if a non-int argument is given.
assert(f4(1L) == 1); // not ambiguous
Comment #35 by andrei — 2013-04-07T19:13:35Z
@Kenji: My bad, I forgot T:int means exact match, thought it's match with conversion. (BTW that's a mistake: The syntax in classes suggests that class T:U means subtyping. But it's too late to fix that now.)
So, you're right. Thanks for the clarification! Have you linked a pull request yet?
Comment #36 by k.hara.pg — 2013-04-07T19:28:23Z
(In reply to comment #35)
> @Kenji: My bad, I forgot T:int means exact match, thought it's match with
> conversion. (BTW that's a mistake: The syntax in classes suggests that class
> T:U means subtyping. But it's too late to fix that now.)
>
> So, you're right. Thanks for the clarification! Have you linked a pull request
> yet?
Oh, I forgot to add 'pull' tag. Will do.
URL is already in comment #9, but I re-post it here.
https://github.com/D-Programming-Language/dmd/pull/1409
Comment #37 by github-bugzilla — 2013-06-28T12:17:44Z
Commits pushed to master at https://github.com/D-Programming-Language/dmdhttps://github.com/D-Programming-Language/dmd/commit/edd0f6fbeeff70eccd7d2e15429b20418360eca5
fix Issue 1528 - [tdpl] overloading template and non-template functions
- Improve `resolveFuncCall` for integrated function call resolution.
All of error reporting is done in here.
- Remove `overloadResolve` and `deduceFuncitonTemplate`
The works was in `overloadResolve` are moved to `resolveFuncCall`,
and things was in `deduceFuncitonTemplate` are divided to
`templateResolve` and `resolveFuncCall`.
- Change the name from `overloadResolveX` to `functionResolve`
It is paired with `templateResolve`.
- Decide 'most specialized' function based on the two `MATCH` values derived from `tiargs` and `fargs`.
With non template functions, `last matching level for tiargs` is treated as `MATCHexact`.
----
The bug that is fixed at the same time:
fix Issue 9596 - Ambiguous match is incorrectly hidden by additional lesser match
The change in test/runnable/template9.d is related.
https://github.com/D-Programming-Language/dmd/commit/fba440cc7dc1210b0450f8b01d18661ebbd0da55
Merge pull request #1409 from 9rnsr/fix1528
Issue 1528 - [tdpl] overloading template and non-template functions
Comment #38 by bearophile_hugs — 2013-06-28T14:16:04Z
(In reply to comment #37)
> Commits pushed to master at
This is a significant improvement.
I am testing this feature a little. This is code reduced from your Comment 10:
int f1(int a, double=10) { return 1; }
int f1(int a, string="") { return 2; }
int f2(T:int)(T b, double=10) { return 1; }
int f2(T:int)(T b, string="") { return 2; }
void main() {
f1(1);
f2(1L);
}
It gives the errors:
test.d(6): Error: called with argument types:
(int)
matches both:
test.d(1): test.f1(int a, double _param_1 = 10.0000)
and:
test.d(2): test.f1(int a, string _param_1 = "")
test.d(7): Error: template test.f2 does not match any function template declaration. Candidates are:
test.d(3): test.f2(T : int)(T b, double = 10)
test.d(4): test.f2(T : int)(T b, string = "")
test.d(7): Error: template test.f2(T : int)(T b, double = 10) cannot deduce template function from argument types !()(long)
For the error at line 6 it nicely indents the lines test.d(1) and test.d(2).
While for the error at line 7 it doesn't indent the lines test.d(3) and test.d(4).
Comment #39 by issues.dlang — 2013-07-20T21:58:59Z
*** Issue 4749 has been marked as a duplicate of this issue. ***
Comment #40 by github-bugzilla — 2014-05-24T09:14:39Z