[comp.std.c++] Inheritance in cfront and g++

fox@allegra.att.com (David Fox) (08/15/90)

In porting code from g++ to cfront I have come across the following
inconsistancy.  The following program compiles as-is in g++

	class A {
	public:
	  void f(int);
	};
	
	class B : public A {
	public:
	  void f(char*);
	//void f(int i) {A::f(i);}
	};
	
	main()
	{
	  B x;
	  x.f(3);
	}

And the call to x.f(3) finds the function f in class A.  In
cfront, however, the function f in class B "masks" the f in
class A, so you need the commented f(int) member function to 
get this to compile.  To me, the g++ semantics make more sense.
Do others agree?

880716a@aucs.uucp (Dave Astels) (08/17/90)

In article <FOX.90Aug15101045@devo.allegra.att.com> fox@allegra.att.com (David Fox) writes:
>In porting code from g++ to cfront I have come across the following
>inconsistancy.  The following program compiles as-is in g++
>
>	class A {
>	public:
>	  void f(int);
>	};
>	
>	class B : public A {
>	public:
>	  void f(char*);
>	//void f(int i) {A::f(i);}
>	};
>	
>	main()
>	{
>	  B x;
>	  x.f(3);
>	}
>
>And the call to x.f(3) finds the function f in class A.  In
>cfront, however, the function f in class B "masks" the f in
>class A, so you need the commented f(int) member function to 
>get this to compile.  To me, the g++ semantics make more sense.
>Do others agree?

I feel as you do.  B::f(char *) shouldn't override A::f(int).
Unfortunately Zortech (2.06) doesn't aggree.  It works as cfront does.

I would think that overriding would take place using the mangled name.
Thus f(int) would be completely different than f(char *).
-- 
"I liked him better before he died" - McCoy, ST V
===============================================================================
Dave Astels            |  Internet: 880716a@AcadiaU.CA
PO Box 835, Wolfville, |  Bitnet:   880716a@Acadia

mckenney@sparkyfs.istc.sri.com (Paul Mckenney) (08/18/90)

In article <FOX.90Aug15101045@devo.allegra.att.com> fox@allegra.att.com (David Fox) writes:
>In porting code from g++ to cfront I have come across the following
>inconsistancy.  The following program compiles as-is in g++
>
>	class A {
>	public:
>	  void f(int);
>	};
>	
>	class B : public A {
>	public:
>	  void f(char*);
>	//void f(int i) {A::f(i);}
>	};
>	
>	main()
>	{
>	  B x;
>	  x.f(3);
>	}
>
>And the call to x.f(3) finds the function f in class A.  In
>cfront, however, the function f in class B "masks" the f in
>class A, so you need the commented f(int) member function to 
>get this to compile.  To me, the g++ semantics make more sense.
>Do others agree?

In Section 13.1 (page 311) of the Annotated C++ Reference Manual by
Ellis and Stroustrup there is an explanation of cfront's hiding
behavior and a rationale for it.  The basic idea is that many
programming styles result in fairly deep derivation trees, with the
base and derived types scattered liberally through the program.  In
such a program, it can be difficult to keep track of all of the
overloaded functions and thus it would difficult to determine the
consequences of adding another overloaded function if this hiding did
not occur.

This restriction is not as onerous as it might be -- your example
shows one way to get around it.  Of course, this word-around can
get tedious if you have classes with lots of overloaded member
functions.  One possibility would be to allow the programmer
to specify the g++ semantics as follows:

	class A {
	public:
	  void f(int);
	};
	
	class B : public friend A {
	public:
	  void f(char*);
	};
	
	main()
	{
	  B x;
	  x.f(3);
	}

The ``friend'' keyword in class B's base class list would tell
the compiler that B's scope should include that of A, so that
``x.f(3)'' would find A::f.

What do you think?
					Thanx, Paul

jbuck@galileo.berkeley.edu (Joe Buck) (08/18/90)

Sorry for the long quote.

In article <1990Aug17.141710.5004@aucs.uucp>, 880716a@aucs.uucp (Dave
Astels) writes:
|> In article <FOX.90Aug15101045@devo.allegra.att.com>
fox@allegra.att.com (David Fox) writes:
|> >In porting code from g++ to cfront I have come across the following
|> >inconsistancy.  The following program compiles as-is in g++
|> >
|> >	class A {
|> >	public:
|> >	  void f(int);
|> >	};
|> >	
|> >	class B : public A {
|> >	public:
|> >	  void f(char*);
|> >	//void f(int i) {A::f(i);}
|> >	};
|> >	
|> >	main()
|> >	{
|> >	  B x;
|> >	  x.f(3);
|> >	}
|> >
|> >And the call to x.f(3) finds the function f in class A.  In
|> >cfront, however, the function f in class B "masks" the f in
|> >class A, so you need the commented f(int) member function to 
|> >get this to compile.  To me, the g++ semantics make more sense.
|> >Do others agree?

> I feel as you do.  B::f(char *) shouldn't override A::f(int).
> Unfortunately Zortech (2.06) doesn't aggree.  It works as cfront does.

> I would think that overriding would take place using the mangled name.
> Thus f(int) would be completely different than f(char *).

Unfortunately, E&S (which is the base document for the standard) mandates
the cfront behavior.  Unless this is changed, you can expect future
versions of g++ to be changed to conform (since Michael Tiemann has
promised to track the standard).

Stroustrup justifies this by pointing out cases where the g++ behavior
might lead to surprises when new functions are added, but I'm not
swayed by his argument.

--
Joe Buck
jbuck@galileo.berkeley.edu	 {uunet,ucbvax}!galileo.berkeley.edu!jbuck	

sakkinen@tukki.jyu.fi (Markku Sakkinen) (08/20/90)

In article <38219@ucbvax.BERKELEY.EDU> jbuck@galileo.berkeley.edu (Joe Buck) writes:
>In article <1990Aug17.141710.5004@aucs.uucp>, 880716a@aucs.uucp (Dave
>Astels) writes:
>|> In article <FOX.90Aug15101045@devo.allegra.att.com>
>fox@allegra.att.com (David Fox) writes:
>|> >In porting code from g++ to cfront I have come across the following
>|> >inconsistancy. [...]

I.e. a function declaration in an inner scope hides all functions
with the same name declared in outer scopes, independently of
argument types. According to the Release 2.0 Ref.Man., Sc. 13.1,
this applies even to other cases than base class - derived class.

>|> [...]  To me, the g++ semantics make more sense.
>|> >Do others agree?
>
>> I feel as you do.  B::f(char *) shouldn't override A::f(int).
>> Unfortunately Zortech (2.06) doesn't aggree.  It works as cfront does.
>
>> I would think that overriding would take place using the mangled name.
>> Thus f(int) would be completely different than f(char *).
>
>Unfortunately, E&S (which is the base document for the standard) mandates
>the cfront behavior.  Unless this is changed, you can expect future
>versions of g++ to be changed to conform (since Michael Tiemann has
>promised to track the standard).
>
>Stroustrup justifies this by pointing out cases where the g++ behavior
>might lead to surprises when new functions are added, but I'm not
>swayed by his argument.

This is an unorthogonal kludge, which makes the rules
of a complicated language even more complicated.
As far as I can see, it is also a deviation from older language
specifications. Already the subtle difference between virtual
and non-virtual overloading can be surprising enough; we don't
need this hiding rule to bite us.

If there are enough special cases to warrant this kind of behaviour,
it should be declared with a new keyword, say "semipublic"
instead of "public" base class. In the non-class-related case,
the Ref.Man. example goes like this:
  int f (char *);
  void g ()
  {
    extern f (int);
    ...
  }
It would be more sensible if this implied only ordinary overloading,
and an additional keyword ('hide') should be used in those weird cases
where one really wants to hide all functions named 'f' in the outer scope.

Markku Sakkinen
Department of Computer Science
University of Jyvaskyla (a's with umlauts)
Seminaarinkatu 15
SF-40100 Jyvaskyla (umlauts again)
Finland
          SAKKINEN@FINJYU.bitnet (alternative network address)

perry@key.COM (Perry The Cynic) (08/21/90)

In article <38219@ucbvax.BERKELEY.EDU> jbuck@galileo.berkeley.edu (Joe Buck) writes:
> [Quote deleted, complaining that a function member definition hides
>  all functions of that name in parent classes, not just the one with
>  the specific parameter profile. Notes that g++ does not do that.]
> 
> Unfortunately, E&S (which is the base document for the standard) mandates
> the cfront behavior.  Unless this is changed, you can expect future
> versions of g++ to be changed to conform (since Michael Tiemann has
> promised to track the standard).
> 
> Stroustrup justifies this by pointing out cases where the g++ behavior
> might lead to surprises when new functions are added, but I'm not
> swayed by his argument.
> 
Actually the argument goes like this: a group of overloaded functions
is really intended to provide different *variants* of one functionality.
Two functions without closely tied purpose and operation should not be
overloaded (within the same class). Thus, if you redefine a function in
a descendant class, you should make damn sure that the close relationship
between the function variants in the parent is not violated (by failing
to override one of them). At minimum, you should add inline members to the
child to explicitly confirm that some variants of the parent are still
applicable unchanged.

Let's say there's this
	class Frob {
	public:
		void frobMe(void);
		void frobMe(int howOften);
	};
	Frob frobber;
where the frobMe(void) is trying to guess how often you want to frob,
and then calls frobMe(int).

If you create
	class MyFrob : public Frob {
	public:
		void frobMe(int moreOften);
	};
	MyFrob frobber;
(and neglect to redefine frobMe(void) as well), then somebody who is used
to deal with a Frob may keep calling frobber.frobMe(void), if only because
he didn't change his source but you changed yours. Of course frobMe(void)
still calls the old frobMe(int), which is likely NOT what you intended.
As things stand, the unsuspecting user of frobMe(void) will find that his
source suddenly stops compiling, and he'll probably come battering down
your door to demand an explanation.

Of course you can argue that frobMe(int) should really be virtual. Maybe the
rule should be relaxed to specify that only redeclaring non-virtual functions
performs full hiding, but that would be another piece of user-visible (and,
probably, user-confusing) complexity. As it stands, you can avoid these
problems by judicious choice of (function) names.
  -- perry

P.S.:	I've worked in ADA, where relaxed one-variant-at-a-time overloading
	rules are in effect. Let me tell you that it's no fun trying to track
	down *which* fooBar(xyz) the compiler decided to call this time,
	especially with implicit conversions clouding the issue. By current
	C++ rules, the first (inner-most) class defining any fooBar member
	*must* be the one containing the thing you called.
-- 
--------------------------------------------------------------------------
Perry The Cynic (Peter Kiehtreiber)		       perry@arkon.key.com
** What good signature isn't taken yet? **  {amdahl,sgi,pacbell}!key!perry

brucec@phoebus.phoebus.labs.tek.com (Bruce Cohen;;50-662;LP=A;) (08/21/90)

In article <1990Aug20.115115.3646@tukki.jyu.fi> sakkinen@tukki.jyu.fi (Markku Sakkinen) writes:
> ...
>
> I.e. a function declaration in an inner scope hides all functions
> with the same name declared in outer scopes, independently of
> argument types. According to the Release 2.0 Ref.Man., Sc. 13.1,
> this applies even to other cases than base class - derived class.
>
> ...
>
> This is an unorthogonal kludge, which makes the rules
> of a complicated language even more complicated.
> As far as I can see, it is also a deviation from older language
> specifications. Already the subtle difference between virtual
> and non-virtual overloading can be surprising enough; we don't
> need this hiding rule to bite us.

I agree that it's a kludge, and a dangererous one at that.  Name hiding is
one of the easiest ways to inadvertantly leave landmines in your code.
Granted that someone will get it wrong no matter which way you standardize
it, the best way is to remove complexity wherever possible, to make it
easier for programmers to predict what will happen.

> If there are enough special cases to warrant this kind of behaviour,
> it should be declared with a new keyword, say "semipublic"

Can anyone give me a case where the *programmer*, as opposed to the
overprotective language designer, wants / needs this capability?  Is there
a case which can't be more cleanly handled with a disciplined use of
scoping of a base class' members and controlled scoping of inheritance?

--
---------------------------------------------------------------------------
NOTE: USE THIS ADDRESS TO REPLY, REPLY-TO IN HEADER MAY BE BROKEN!
Bruce Cohen, Computer Research Lab        email: brucec@tekcrl.labs.tek.com
Tektronix Laboratories, Tektronix, Inc.                phone: (503)627-5241
M/S 50-662, P.O. Box 500, Beaverton, OR  97077

jimad@microsoft.UUCP (Jim ADCOCK) (08/22/90)

In article <1990Aug20.115115.3646@tukki.jyu.fi> sakkinen@jytko.jyu.fi (Markku Sakkinen) writes:
>
>I.e. a function declaration in an inner scope hides all functions
>with the same name declared in outer scopes, independently of
>argument types. According to the Release 2.0 Ref.Man., Sc. 13.1,
>this applies even to other cases than base class - derived class.

>
>This is an unorthogonal kludge, which makes the rules
>of a complicated language even more complicated.
>As far as I can see, it is also a deviation from older language
>specifications. Already the subtle difference between virtual
>and non-virtual overloading can be surprising enough; we don't
>need this hiding rule to bite us.
>

I disagree.  I support the interpretation that same-named functions 
with differing parameters should represent a function-set -- multiple 
implementations of the same behavior, only optimized for different pass 
parameters.

If you want to implement differing functionality, use different function
names, not parameters, to differentiate your routines.

Thus, if one needs to override any one of the function-set in a derived class,
it is pretty safe to assume all the other functions in the set, optimized on 
particular pass-parameter, need to be overridded too.  In the odd case where 
a programmer decides one of the function-set does not need to be re-implemented,
that function can be carried forward from the base class via a trivial in-line
function.

In any case, if you've got a lot of functions in your function-set, you're
probably doing something wrong.