[comp.lang.c++] Overloaded Function Ambiguity Resolution Problems

Graham.Parrington@newcastle.ac.uk (Graham D. Parrington) (02/27/91)

Consider the following code fragment:

#include "stream.h"
class Base
{
public:
    Base() { cout << "Base::Base()\n"; }
    Base(char *) { cout << "Base::Base(char *)\n"; }
    void func(char*) { cout << "Base::func(char *)\n"; }
};

class Derived:public Base
{
public:
    Derived() { cout << "Derived::Derived()\n"; }
    void func(const Base&){ cout << "Derived::func(const Base&)\n"; } 

};

main()
{
    Derived *d = new Derived();

    d->func("hello");
}

What should it output? 
Cfront 2.1 (<<AT&T C++ Translator 2.1.0 03/31/90>>) gives:
	Base::Base()
	Derived::Derived()
	Base::Base(char *)
	Derived::func(const Base&)

while g++ (version 1.39.0 beta (based on GCC 1.39)) gives:
	Base::Base()
	Derived::Derived()
	Base::func(char *)

That is, g++ considers Base::func(char*) to be the correct match to the call
while cfront prefers to construct a Base first and then use Derived::func(Base)

My reading of the ARM pp318-> makes me believe that g++ is correct in this case
since there is an exact match on argument type (rule 1), whereas cfront appears
to apply rule 4 (user defined conversion). I suspect the differences lie in
search strategies in the two compilers, that is, g++ applies the rules in order
over the entire hierachy, while cfront applies the rules first on the current
class and then on its parents etc.
What do other compilers make of this example (Zortech, Glockenspiel, etc), and
more importantly which is the CORRECT behaviour??

EMAIL = Graham.Parrington@newcastle.ac.uk
POST  = Computing Laboratory, The University, Newcastle upon Tyne, UK NE1 7RU
VOICE = +44 91 222 8067		FAX = +44-91-222-8232

wmm@world.std.com (William M Miller) (02/28/91)

Graham.Parrington@newcastle.ac.uk (Graham D. Parrington) writes:
> class Base
> {
> public:
>     Base(char *) { cout << "Base::Base(char *)\n"; }
>     void func(char*) { cout << "Base::func(char *)\n"; }
> };
>
> class Derived:public Base
> {
> public:
>     void func(const Base&){ cout << "Derived::func(const Base&)\n"; }
>
> };
>
> main()
> {
>     Derived *d = new Derived();
>
>     d->func("hello");
> }
>
> What should it output?

The cfront output is correct, G++ is wrong.  See E&S section 13.1, page 310:
"Two function declarations of the same name refer to the same function if
they are in the same scope and have identical argument types (section 13).
A function member of a derived class is NOT in the same scope as a function
member of the same name in a base class."  In this example, d->func("hello")
can only refer to Derived::func(const Base&), since Base::func(char*) is
hidden in the scope of Derived.

-- William M. Miller, Glockenspiel, Ltd.
   wmm@world.std.com

randolph@tuna.ssd.kodak.com (Gary L. Randolph) (02/28/91)

In article <1991Feb27.084909.5063@newcastle.ac.uk> Graham.Parrington@newcastle.ac.uk (Graham D. Parrington) writes:
gdp>Consider the following code fragment:
gdp>class Base
gdp>{
gdp>public:
gdp>    Base() { cout << "Base::Base()\n"; }
gdp>    Base(char *) { cout << "Base::Base(char *)\n"; }
gdp>    void func(char*) { cout << "Base::func(char *)\n"; }
gdp>};
gdp>class Derived:public Base
gdp>{
gdp>public:
gdp>    Derived() { cout << "Derived::Derived()\n"; }
gdp>    void func(const Base&){ cout << "Derived::func(const Base&)\n"; } 
gdp>};
gdp>main()
gdp>{
gdp>    Derived *d = new Derived();
gdp>    d->func("hello");
gdp>}
gdp>What should it output? 
gdp>Cfront 2.1 (<<AT&T C++ Translator 2.1.0 03/31/90>>) gives:
gdp>	Base::Base()
gdp>	Derived::Derived()
gdp>	Base::Base(char *)
gdp>	Derived::func(const Base&)

gdp>while g++ (version 1.39.0 beta (based on GCC 1.39)) gives:
gdp>	Base::Base()
gdp>	Derived::Derived()
gdp>	Base::func(char *)
gdp>That is, g++ considers Base::func(char*) to be the correct match to the call
gdp>while cfront prefers to construct a Base first and then use Derived::func(Base)
gdp>My reading of the ARM pp318-> makes me believe that g++ is correct in this case
gdp>since there is an exact match on argument type (rule 1), whereas cfront appears
gdp>to apply rule 4 (user defined conversion). I suspect the differences lie in
gdp>search strategies in the two compilers, that is, g++ applies the rules in order
gdp>over the entire hierachy, while cfront applies the rules first on the current
gdp>class and then on its parents etc.
gdp>What do other compilers make of this example (Zortech, Glockenspiel, etc), and
gdp>more importantly which is the CORRECT behaviour??

Cfront has the correct behavior.  What you have here is one of the most common
mistakes I have seen people make. DERIVATION IS *NOT* OVERLOADING. See page
310 of the ARM.

The name of the function, func, exists in two DIFFERENT scopes. The derived class
essentially hides Base::func.  But I PUBLICly derive you say? True, that is why
the following is legal:
        d->Base::func(char *);
private derivation would make this statement illegal.

I post this rather than emailing because it is a VERY common mistake.

Gary Randolph

craig@gpu.utcs.utoronto.ca (Craig Hubley) (03/03/91)

In article <1991Feb27.204841.14690@world.std.com> wmm@world.std.com (William M Miller) writes:
>The cfront output is correct, G++ is wrong.  See E&S section 13.1, page 310:
>"Two function declarations of the same name refer to the same function if
>they are in the same scope and have identical argument types (section 13).
>A function member of a derived class is NOT in the same scope as a function
>member of the same name in a base class."  In this example, d->func("hello")
>can only refer to Derived::func(const Base&), since Base::func(char*) is
>hidden in the scope of Derived.

This seems reasonable and powerful, but it isn't what people were saying 
before when we were arguing about the interaction of virtuals/overloads/
conversion.  That gave me the idea g++ was doing it right, although
I disagree with what it's doing!  

So can we take this to mean that only overloading a function (i.e. providing
a definition func(char*)) in the derived class itself should cause this 
behavior ?

Taken as a whole, this statement in E&S would seem to imply that resolution
of which function to call should happen classwise FIRST, exhausting all
possibilities for a subclass-specific function before moving to the base
class.  I would still quibble about the definition of "identical" argument
types in a language that supports builtin conversions, and argue that most
type-and-conversion-safe extensions to an argument list ought to be accepted
as new signatures for the old function.  Of course no sane programmer would
implement it any differently anyway (i.e. Derived::func(char*) HAD BETTER
CALL Derived::func(Base(char*)) ) but it is still a source of potential error
and uncertainty.

-- 
  Craig Hubley   "...get rid of a man as soon as he thinks himself an expert."
  Craig Hubley & Associates------------------------------------Henry Ford Sr.
  craig@gpu.utcs.Utoronto.CA   UUNET!utai!utgpu!craig   craig@utorgpu.BITNET
  craig@gpu.utcs.toronto.EDU   {allegra,bnr-vpa,decvax}!utcsri!utgpu!craig

sakkinen@tukki.jyu.fi (Markku Sakkinen) (03/08/91)

In article <1991Feb27.204841.14690@world.std.com> wmm@world.std.com (William M Miller) writes:
>Graham.Parrington@newcastle.ac.uk (Graham D. Parrington) writes:
>> class Base
>> {
>> public:
>>     Base(char *) { cout << "Base::Base(char *)\n"; }
>>     void func(char*) { cout << "Base::func(char *)\n"; }
>> };
>>
>> class Derived:public Base
>> {
>> public:
>>     void func(const Base&){ cout << "Derived::func(const Base&)\n"; }
>>
>> };
>>
>> main()
>> {
>>     Derived *d = new Derived();
>>
>>     d->func("hello");
>> }
>>
>> What should it output?
>
>The cfront output is correct, G++ is wrong.  See E&S section 13.1, page 310:
>"Two function declarations of the same name refer to the same function if
>they are in the same scope and have identical argument types (section 13).
>A function member of a derived class is NOT in the same scope as a function
>member of the same name in a base class."  In this example, d->func("hello")
>can only refer to Derived::func(const Base&), since Base::func(char*) is
>hidden in the scope of Derived.
>
>-- William M. Miller, Glockenspiel, Ltd.
>   wmm@world.std.com

The functions in the example have _not_ got identical argument types!
The problem seems to be that a new function declared in a derived
class hides _all_ functions with the same name in the base class,
no matter what the number and types of arguments.
If the constructor 'Base (char*)' were not there,
the invocation of 'func' would evidently cause a compiler diagnostic.

Current C++ has a lot of complicated and stupid visibility rules,
as if expressly designed to shoot programmers in the back;
this is one of them.

Markku Sakkinen
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
          SAKKINEN@FINJYU.bitnet (alternative network address)

wmm@world.std.com (William M Miller) (03/09/91)

sakkinen@tukki.jyu.fi (Markku Sakkinen) writes:
> >The cfront output is correct, G++ is wrong.  See E&S section 13.1, page 310:
> >"Two function declarations of the same name refer to the same function if
> >they are in the same scope and have identical argument types (section 13).
> >A function member of a derived class is NOT in the same scope as a function
> >member of the same name in a base class."  In this example, d->func("hello")
> >can only refer to Derived::func(const Base&), since Base::func(char*) is
> >hidden in the scope of Derived.
> >
> >-- William M. Miller, Glockenspiel, Ltd.
> >   wmm@world.std.com
>
> The functions in the example have _not_ got identical argument types!
> The problem seems to be that a new function declared in a derived
> class hides _all_ functions with the same name in the base class,
> no matter what the number and types of arguments.
> If the constructor 'Base (char*)' were not there,
> the invocation of 'func' would evidently cause a compiler diagnostic.

Sorry if I wasn't explicit enough or if the citation from E&S was confusing.
Yes, that's exactly what I was saying: lookup is by name only, not name and
function signature.  The search stops at the nearest scope with a
declaration of the name, and if it's the wrong type, the compiler reports an
error rather than continuing the search.

> Current C++ has a lot of complicated and stupid visibility rules,
> as if expressly designed to shoot programmers in the back;
> this is one of them.

Actually, it's designed to prevent programmers being shot in the back, as it
were; having to look through all the levels of a deeply-nested hierarchy to
determine which of a set of overloaded member functions will be invoked by a
given call, as would be necessary if hiding were by name and signature
instead of name alone, is tedious and error-prone.  It's especially
dangerous in that the addition of an overloaded member function in a base
class would be able to hijack a call that used to resolve to a derived class
member function.  This kind of bug can be very difficult to track down: all
you know is that your program used to work before you installed the new
version of the library but that it doesn't work now.

-- William M. Miller, Glockenspiel, Ltd.
   wmm@world.std.com

sakkinen@tukki.jyu.fi (Markku Sakkinen) (03/12/91)

In article <1991Mar8.165325.22778@world.std.com> wmm@world.std.com (William M Miller) writes:
>sakkinen@tukki.jyu.fi (Markku Sakkinen) writes:
>> ...
>> The problem seems to be that a new function declared in a derived
>> class hides _all_ functions with the same name in the base class,
>> ...
>> Current C++ has a lot of complicated and stupid visibility rules,
>> as if expressly designed to shoot programmers in the back;
>> this is one of them.

Apologies for too strong language.  This is certainly an issue on which
sensible persons can have different opinions.

>Actually, it's designed to prevent programmers being shot in the back, as it
>were; having to look through all the levels of a deeply-nested hierarchy to
>determine which of a set of overloaded member functions will be invoked by a
>given call, as would be necessary if hiding were by name and signature
>instead of name alone, is tedious and error-prone.  It's especially
>dangerous in that the addition of an overloaded member function in a base
>class would be able to hijack a call that used to resolve to a derived class
>member function.  This kind of bug can be very difficult to track down: all
>you know is that your program used to work before you installed the new
>version of the library but that it doesn't work now.

In the situation you describe, the _interface_ of a class is modified
(by adding a public or protected member function) after other classes
have already been derived from it.  Such a situation should better
(but of course cannot always) be avoided;  not all ensuing problems
are corrected by the name hiding rule.

A very typical situation would be one in which a base class defines
an overloaded function for some argument types and a derived class
would like to add versions for some additional argument types.
The hiding rule makes this impossible.

In my opinion, the difficulties that the hiding rule tries to alleviate
(creating new problems on the way) are to a large part caused
by the too liberal application by C++ of automatic type conversions.
This in turn is a convenience in many situations, of course.

Markku Sakkinen
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
          SAKKINEN@FINJYU.bitnet (alternative network address)

sarima@tdatirv.UUCP (Stanley Friesen) (03/13/91)

In article <1991Mar12.084906.28195@tukki.jyu.fi> sakkinen@jytko.jyu.fi (Markku Sakkinen) writes:
>A very typical situation would be one in which a base class defines
>an overloaded function for some argument types and a derived class
>would like to add versions for some additional argument types.
>The hiding rule makes this impossible.

No it doesn't, it just makes it non-trivial, and introduces a slight window
for errors.  To accomplish the addition of a new overloaded member function
in a derived class you just redefine the entire set in the derived class,
all of them but the new one can be inline functions that simply call the
parent's function (no additional overhead).
-- 
---------------
uunet!tdatirv!sarima				(Stanley Friesen)