Is this a compiler bug?

G

Guest

Hi,

I have some weird compiler behaviour, so I'm hoping that one of the gurus
here will be able to tell me whether or not it really is a compiler bug.

In short, the compiler is saying that it cannot choose between two
overloaded versions of a method when, IMHO, one overload is clearly a
"better" choice than the other. I've looked through all the rules for
overload resolution, and I can't see any justification for the compiler's
error message.

I'll include a stripped down demonstration of the "bug" below. But first, a
"disclaimer": yes, I have included an implict conversion operator on one of
the classes and yes, the problem does go away if I remove it. However,
regardless of the fact that I've included it, I still believe the compiler is
wrong about the ambiguity.

What do you think?

Here's the code:

public class A
{
public static implicit operator B(A a)
{
return new B(); //actual implementation omitted
}
}

public class B
{
}

public class ShowError
{
public static void Method(A a, B b)
{ }

public static void Method(B b, A a)
{ }

public static void ThisFailsToCompile()
{
A a = new A();
Method(a, null);
//Compiler says this call is ambiguous, BUT
//Surely Method(A a, B b) is better than Method(B b, A a)
//since the former requires NO implicit conversion of the first
param
}

}

John
 
P

Pohihihi

John,

Do not matter if you give Method(a, null); or Method(null, a) either way it
is not clear to the compiler if it is Method((B)a, null) or Method(a, null)
should be called. Guidelines for implicit maintions that implicit conversion
should be very clear and should not create confusion. In this case it is
confusing.

Thanks,
Po
 
G

Guest

Po,
is not clear to the compiler if it is Method((B)a, null) or Method(a, null)
should be called.

IMHO, the C# documentation says that Method(a, null) is the better match
because its first parameter if already of the correct type. That makes it
better than Method((B)a, null), in which the first parameter requires
conversion. Therefore, according to the documentation, the compiler should
automatically choose Method(a, null).
Guidelines for implicit maintions that implicit conversion
should be very clear and should not create confusion. In this case it is
confusing.

For humans, yes! My point is that, according to the documentation, it
should _not_ confuse the compiler.

John

PS: I do have good reasons for writing such "confusing" code; I just don't
have time and space to describe them here. Anyway, the point is that the
_compiler_ should not get confused.
 
J

Jon Skeet [C# MVP]

John Rusk said:
I have some weird compiler behaviour, so I'm hoping that one of the gurus
here will be able to tell me whether or not it really is a compiler bug.

No, it's not a bug, although it's subtle.

The first argument favours Method(A, B). However, look at the second
argument, with respect to the list of "better" conversions, where T1 is
A, T2 is B, C1 is the conversion from the null type to A, C2 is the
conversion from the null type to B, and S is the null type.

o T1 and T2 are different conversions (no result)
o S is neither T1 nor T2 (no result)
o There *is* an implicit conversion from T1 to T2, and no implicit
conversion from T2 to T1, so C1 is the better conversion.

So, for the second argument, Method(B,A) is preferred - so the compiler
doesn't know what to do.

Hope that helps - let me know if you want me to go through it in more
detail. (It looks like you've spent time poring over the specs already,
so hopefully the above should be pretty straightforward by now!)
 
J

Jon Skeet [C# MVP]

Hope that helps - let me know if you want me to go through it in more
detail. (It looks like you've spent time poring over the specs already,
so hopefully the above should be pretty straightforward by now!)

Meant to ask before - would you like me to add a comment on this for
the specification committee? I happen to be looking through the C# 2.0
specs at the moment, and have collected some comments for them. This
strikes me as an area which could do with at least a bit more
explanation - would you mind if I used your example code?
 
G

Guest

PS For the record, I've just found a way to resolve the ambiguity in the
example I gave. It ain't pretty, but it's working.

It relies on two facts: firstly, the problem case is when the "b" param is
the null literal. Secondly, we cannot resolve the ambiguity but we _can_
break the "tie" by introducing a third method which is an even _better_
match, in the specific case when the b param is null.

First, I added this class to the project:

public class NullObj
{
private NullObj() //private = no way to construct this class
{ }

public static implicit operator B(NullObj n)
{
return null;
}
}

There's no way to construct it, so the only possible "value" for a
variable/param of this type is "null".

Second, I added two more overrides of the problem method. They look like
this:

public static void Method(A a, NullObj n)
{
Method(a, (B)null);
}

public static void Method(NullObj n, A a)
{
Method((B)null, a);
}

These overrides will "win" when code calls Method(a, null) and Method(null,
a).

E.g. given the call
Method(a, null)

and the following signatures:
1. Method(B b, A a)
2. Method(A a, B a)
3. Method(A a, NullObj n)

#3 wins because compared to #1, its first param is a better conversion and
its second param is no worse; and compared to #2, its first param is no worse
and its second param is a better conversion - due to the one-way implicit
conversion from NullObj to B.

As you know, that conversion is never actually used. The solution still
works even if the new implicit conversion is coded like this:

public static implicit operator B(NullObj n)
{
throw new NotImplementedException();
}

In summary, it's kind of ugly but kind of cool too ;-)

In case anyone else ever has this problem, likely Google search strings are
the C# compiler error message, namely: CS0121 (or 0121 or 121) The call is
ambiguous between the following methods or properties.
 
G

Guest

Hope that helps - let me know if you want me to go through it in more
Yes, but when I re-read them yesterday I missed the crucial bit :) Thanks
for pointing it out.
Meant to ask before - would you like me to add a comment on this for
the specification committee? I happen to be looking through the C# 2.0
specs at the moment, and have collected some comments for them. This
strikes me as an area which could do with at least a bit more
explanation - would you mind if I used your example code?

You're welcome to use my example code. Perhaps it would help if the
reasoning behind this rule was explained a bit more, that might make it
easier it remember.

Regards,

John
 
J

Jon Skeet [C# MVP]

John Rusk said:
Yes, but when I re-read them yesterday I missed the crucial bit :) Thanks
for pointing it out.

No problem. I love this kind of question. (Bit sad, I know.)
You're welcome to use my example code. Perhaps it would help if the
reasoning behind this rule was explained a bit more, that might make it
easier it remember.

Exactly. I can't see why it's there, at the moment. No doubt it'll all
make sense with a bit of explanation.
 
M

Mike Schilling

Jon Skeet said:
No problem. I love this kind of question. (Bit sad, I know.)


Exactly. I can't see why it's there, at the moment. No doubt it'll all
make sense with a bit of explanation.

Perhaps. The oddities that result from discarding overrides before finding
the best match don't seem to make much sense. (http://tinyurl.com/ckd8e, if
it's been too long.)
 
J

Jon Skeet [C# MVP]

Mike Schilling said:
Perhaps. The oddities that result from discarding overrides before finding
the best match don't seem to make much sense. (http://tinyurl.com/ckd8e, if
it's been too long.)

Don't worry - that was one of the first things I commented on :)

(And it's surprised everyone I've shown it to so far...)
 
M

Mike Schilling

Jon Skeet said:
Don't worry - that was one of the first things I commented on :)

(And it's surprised everyone I've shown it to so far...)

I have a writeup on it, including an example and suggestions for addressing
it. It didn't motivate the Microsoft folks I gave it to, but you're welcome
to it, if you'd like.
 
J

Jon Skeet [C# MVP]

Mike Schilling said:
I have a writeup on it, including an example and suggestions for addressing
it. It didn't motivate the Microsoft folks I gave it to, but you're welcome
to it, if you'd like.

Unfortunately, I can't see a way of fixing it at this point - if
existing apps rely on the current overloading rules, changing the rules
would break those apps.

I'm hoping that it'll at least be recognised as a flaw in future
versions...
 
M

Mike Schilling

Jon Skeet said:
Unfortunately, I can't see a way of fixing it at this point - if
existing apps rely on the current overloading rules, changing the rules
would break those apps.

It's not possible to make the compiler choose a different overload, but it
is possible to have the problematical cases cause a compilation error.
(That is, fix the overload rules, and consider any situation where new and
old rules would choose different ones "problematical") Since the bad
behavior is the compiler choosing the wrong overload, not runtime dispatch
to the wrong override, there is no change to the behavior of binaries.
And it would need to be fixed gradually, over a few versions (silence
becomes warning becomes error, with a flag at each stage to escalate
warnings to errors, or vice versa.)
 
G

Guest

Exactly. I can't see why it's there, at the moment. No doubt it'll all
make sense with a bit of explanation.

Thinking about it over the past day or so, I've concluded that the rule
(about one-way implicit conversions) is there because its a clever way to
"kill two birds with one stone". Perhaps you've thought of this too, so I'd
be interested in whether you think MS were wrong to take this approach, or
whether this explaination fails to adequately explain their decision.

Here's how it kills two birds with one store. Consider two specific cases:

Case A, subclasses:

void Method(object o)
void Method(MyClass o)

if you call it with something that has a compile-time type of MyClass, it
will call the second version. (Exactly _why_ you'd write code like this, I
don't know, since it depends on the compile-time type not the run-time type,
which is icky. But anyway, it ties in with the next case....)

Case B, numeric types:

void Method(double d)
void Method(int i)

if you call it with an int, it should choose the second one - and it will
because int is convertable to double but not vice versa.

Why is this similar to the first case? Because, in the first case, MyClass
"is a" object. In the second case, in some sense, an int "is a" double.
I.e. the one-way-conversion rule generalizes the usual "is a" semantics of
inheritance, to apply them in a more general sense. If X is implicity
convertable to Y, then in a sense any X "is a" Y.

In all cases, the rule picks the most specific override - i.e. the class at
the bottom of the tree of "is a" relationships. I.e. it picks int instead of
double, and MyClass instead of object.

Anyway, that's the only rationale that I've been able to think of, for the
rule.

As for the other issue mentioned elsewhere in this thread - in which C#
picks the wrong method due to where the various versions where defined in the
class heirarchy - that's _got_ to be a compiler bug, surely!
 
M

Mike Schilling

As for the other issue mentioned elsewhere in this thread - in which C#
picks the wrong method due to where the various versions where defined in
the
class heirarchy - that's _got_ to be a compiler bug, surely!

No, it's strictly according to the spec. See the thread I referenced (via
tinyurl) for the gruesome details.
 
G

Guest

No, it's strictly according to the spec. See the thread I referenced (via
tinyurl) for the gruesome details.

Opps, that's what I meant - more a "spec bug" than "code bug", as you and
Jon have discussed in the other thread. I can't believe that MS don't seem
to be listening to your concerns!
 
J

Jon Skeet [C# MVP]

John Rusk said:
Thinking about it over the past day or so, I've concluded that the rule
(about one-way implicit conversions) is there because its a clever way to
"kill two birds with one stone". Perhaps you've thought of this too, so I'd
be interested in whether you think MS were wrong to take this approach, or
whether this explaination fails to adequately explain their decision.

<snip>

Ah, yes. I think you're right - although a better example would be:

void Method(object o)
void Method(MyBaseClass o)

and you call it with an expression of type MyDerivedClass (with the
obvious hierarchy). In the example you gave, you'd instantly run into
the rule of "if the type is exactly correct, use that one".

Now, that suggests a good transformation of your code to show what's
going on. Consider this:

public class Derived: Base
{
}

public class Base
{
}

class Example
{
static void Method(Derived a, Base b)
{
}

static void Method(Base b, Derived a)
{
}

static void Main()
{
A a = new A();
Method(a, null);
}
}

It now seems more reasonable for the compiler to complain, because null
as a Derived is more specific than null as a Base.

I'll edit my annotation appropriately.
 
G

Guest

I'll edit my annotation appropriately.

Cool.

Did you also intend to change the first line of Main(), from using A to
using Derived?

John

PS I hope you'll also be able to find a way to encourage some
clarification/documentation of the case where there's no inheritance, just an
implicit conversion. It's probably a rarer case, but that makes it more
confusing when it does crop up.
 
J

Jon Skeet [C# MVP]

John Rusk said:
Cool.

Did you also intend to change the first line of Main(), from using A to
using Derived?

Yes. Fixed now, thanks :)
PS I hope you'll also be able to find a way to encourage some
clarification/documentation of the case where there's no inheritance, just an
implicit conversion. It's probably a rarer case, but that makes it more
confusing when it does crop up.

Possibly. I'll see what I can do. I'm not saying I have *any* power in
these matters, btw - all I can do is comment.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top