c++/3708: [2003-01-02] static_cast between classes finds ambiguous base conversion
Giovanni Bajo
giovannibajo@libero.it
Thu May 1 01:55:00 GMT 2003
P Hornby <p.hornby@ned.dem.csiro.au> wrote:
> Long ago I eliminated the template, to get
>
> class B
> {
> public:
> B() {}
>
> operator int () { return 1; }
> };
>
> class D : public B
> {
> public:
> D () {}
>
> operator Foo () { return Foo(1); };
> };
Which is still ambigous for both GCC and Comeau.
> I seem to recall that D's methods are to be preferred over any of B's in
> the event of such conflicts.
That would be very strange, I never heard of a situation where methods of
the most derived class are preferred over members of a less derived class to
resolve ambiguity.
> I also recall something that implied that
> such conflict resolution had a limit on the complexity, and that this
> limit was greater than a single direct call to a method of a class.
I am not sure about what you mean here.
> forget. In any case, the template version of the conflict is excluded on
> the grounds that the D::operator Foo() -> Foo::Foo(const Foo&) requires
> no template instantiation. (under the rule that the less instantiated
> resolution yields to the more instantiated (in this case explict)
> resolution).
I think you're slightly missing the point. There is no ambiguity between
operator Foo() and template <class T> operator T(). Let's go step by step.
The compiler needs to convert "d" (of type D) to type Foo. Since Foo() has a
constructor taking a parameter of type "int", the compiler knows that the
conversion can be done directly (finding an user-defined conversion to Foo,
and calling the copy constructor) or indirectly (finding an user-defined
conversions to int and calling the custom constructor). In the standard,
there is nothing on why a constructor might be preferred over the other, so
the conversion to type Foo() is not "better" in any way to the conversion to
type int, since both have an accessible constructor in Foo (the implicit
copy constructor for the former, the explicit custom constructor for the
latter).
So, the compiler just found out it can try to convert to either Foo or int.
It checks for custom conversion functions, and it finds two of them: a
conversion to Foo, and a template conversion operator. One is in B and one
is in D, but §12.3p5 says "[...]A conversion function in a derived class
does not hide a conversion function in a base class unless the two functions
convert to the same type[...]". So both of them are visible.
First, let's say that the compiler tries to convert to int. The non-template
operator obviously cannot be used, but the template operator can be
instanciated with type int. So the compiler now know that there is an
available conversion from D to int, through the template operator in B.
Second, it tries to see if it's possible to convert Foo to int. In this
case, the compiler finds that there is a non-template operator Foo() which
can be used, but also a template operator that could be instanciated with
type Foo. So what? Again §12.3p5: "Function overload resolution selects the
best conversion function to perform the conversion". Thus, the compiler
prefers the non-template version over the template one (for the usual
overaload rules).
As you see, the compiler ends up with two valid paths to perform the
required conversion. One path goes through conversion to int (thanks to the
custom constructor) and one path through conversion to Foo (thanks to the
implicit copy constructor).
> This would be the case if the conflict were to arise from a
> D::operator T(), so why break the rule for B::operator T()? (I know why
> the compiler is doing this, and it is very hard to fix. I understand why
> you are keen to close it).
No, it's wrong. Even if you move the template operator into D, there is
still an ambiguity.
In fact (again) the ambiguty is not about choosing between the non-template
conversion operator and the template one. Remember that a group of
conversion functions do not form an overload set. You seem to think as your
code is in the same situation of:
template <typename T>
void foo(T a);
void foo(int a);
where the second overload is obviously preferred to the first if the
function is called with a varible of type int. But here, the situation is
different. There is no overload set. The ambiguity is between which
constructor must be called! Both of them are accessible, both of them are
reachable through custom conversion functions (which are not overloaded!
they are different functions!), so there is no way to solve this ambiguity.
To solve this as you think, there should be an explicit paragraph in the
standard stating that, in case of ambiguity during a conversion, a
conversion made through a non-template operator should be preferred over a
conversion made through a template operator. But there is no such a rule.
> As I've stated elsewhere g++ (and now Comeau) are the only compilers I
> have encountered that do not resolve this conflict. I only ask that you
> consider this once more before closing it. (You may run into a language
> lawyer next time).
Sure, there is no hurry. In case nobody helps us here, I'll bring the issue
to comp.std.c++ soon.
Giovanni Bajo
More information about the Gcc-bugs
mailing list