Topic: Virtual Functions and Default Parameter Values


Author: sdm@cs.brown.edu (Scott Meyers)
Date: 28 Jun 91 15:47:17 GMT
Raw View
Here's an interesting anomaly: a virtual function taking a default
parameter value can be redefined in a derived class with a different
default value.  This can lead to unexpected behavior:

  #include <iostream.h>

  class Base {
  public:
    virtual void f(char * className = "Base") { cout << className << endl; }
  };

  class Derived: public Base {
  public:
    virtual void f(char * className = "Derived") { cout << className << endl; }
  };

  main()
  {
    Derived *d = new Derived;
    d->f();                  // prints "Derived"

    Base *b = d;
    b->f();                  // prints "Base"
  }

Both calls to f resolve to Derived::f, as expected, but the parameter has
different values, depending on which pointer was used to invoke it.

As I understand it, the reason that a default parameter value must be given
in the declaration xor the definition of a function is that that prevents
inconsistency between the two (ARM 8.2.6, pp. 144-5).  Here we seem to have
a similar problem, except that it's between two declarations.

What would be the ramifications of prohibiting the specification of a
default value for a parameter in a redefinition of an inherited virtual
function, i.e., requiring that derived classes use the default parameter
values specified in the virtual functions they inherit?

Scott


-------------------------------------------------------------------------------
What do you say to a convicted felon in Providence?  "Hello, Mr. Mayor."




Author: mgates@luna.boeing.com (Michael Gates)
Date: 1 Jul 91 22:19:49 GMT
Raw View
In article <79633@brunix.UUCP> sdm@cs.brown.edu (Scott Meyers) writes:

>   Here's an interesting anomaly: a virtual function taking a default
>   parameter value can be redefined in a derived class with a different
>   default value.  This can lead to unexpected behavior:
>
>     class Base {
>     public:
>       virtual void f(char * className = "Base") { cout << className << endl; }
>     };
>
>     class Derived: public Base {
>     public:
>       virtual void f(char * className = "Derived") { cout << className << endl; }
>     };
>
>     main()
>     {
>       Derived *d = new Derived;
>       d->f();                  // prints "Derived"
>
>       Base *b = d;
>       b->f();                  // prints "Base"
>     }
>
>   Both calls to f resolve to Derived::f, as expected, but the parameter has
>   different values, depending on which pointer was used to invoke it.
[...]
>   What would be the ramifications of prohibiting the specification of a
>   default value for a parameter in a redefinition of an inherited virtual
>   function, i.e., requiring that derived classes use the default parameter
>   values specified in the virtual functions they inherit?

 The presence of the different default in the derived function
is causing Derived::f to hide Base::f, rather than overriding it. G++
1.39.1 behaves the same way. The ARM suggests that this is incorrect:

    10.2, page 208 - "If the function types (@8.25) are different,
    however, the functions are considered different and the virtual
    mechanism is not invoked ..."

    8.2.5, page 138 - "The return type and the argument types, but not
    the default arguments are part of the function type."

Personally, I think allowing different default arguments for virtual
functions in derived classes is very desirable.
--
et tu mgates?




Author: sdm@cs.brown.edu (Scott Meyers)
Date: 2 Jul 91 14:02:09 GMT
Raw View
In article <MGATES.91Jul1151949@luna.boeing.com> mgates@luna.boeing.com (Michael Gates) writes:
| In article <79633@brunix.UUCP> sdm@cs.brown.edu (Scott Meyers) writes:
| >     class Base {
| >     public:
| >       virtual void f(char * className = "Base") { cout << className << endl; }
| >     };
| >
| >     class Derived: public Base {
| >     public:
| >       virtual void f(char * className = "Derived") { cout << className << endl; }
| >     };
| >
| >     main()
| >     {
| >       Derived *d = new Derived;
| >       d->f();                  // prints "Derived"
| >
| >       Base *b = d;
| >       b->f();                  // prints "Base"
| >     }
|
|  The presence of the different default in the derived function
| is causing Derived::f to hide Base::f, rather than overriding it. G++
| 1.39.1 behaves the same way. The ARM suggests that this is incorrect:

No, this is correct behavior.  The same function (Derived::f) is being
called in both cases, a fact easily confirmed by augmenting the output
statements.  If Derived::f didn't redefine Base::f, then b->f() would call
Base::f.  So Base::f isn't hidden, it is redefined.

| Personally, I think allowing different default arguments for virtual
| functions in derived classes is very desirable.

And how do you feel about the unintuitive but correct behavior exhibited
above?


Scott

-------------------------------------------------------------------------------
What do you say to a convicted felon in Providence?  "Hello, Mr. Mayor."




Author: mgates@luna.boeing.com (Michael Gates)
Date: 3 Jul 91 18:48:45 GMT
Raw View
In article <79892@brunix.UUCP> sdm@cs.brown.edu (Scott Meyers) writes:

>   |  The presence of the different default in the derived function
>   | is causing Derived::f to hide Base::f, rather than overriding it. G++
>   | 1.39.1 behaves the same way. The ARM suggests that this is incorrect:
>
>   No, this is correct behavior.  The same function (Derived::f) is being
>   called in both cases, a fact easily confirmed by augmenting the output
>   statements.  If Derived::f didn't redefine Base::f, then b->f() would call
>   Base::f.  So Base::f isn't hidden, it is redefined.

Actually, the method you suggest is exactly how I determined that it
was doing the *wrong* thing:

  <3 mgates.luna> g++ -v
  g++ version 1.39.1 (based on GCC 1.39)

  <6 mgates.luna> cat 6.cc
  #include <stream.h>

  class Base
  {
  public:
    virtual void f(char * className  = "Base")
      { cout << "B " << className << "\n"
  ; }
  };

  class Derived: public Base
  {
  public:
    virtual void f(char * className = "Derived")
      { cout << "D " << className << "\
  n"; }
  };

  main()
  {
    Derived *d = new Derived;
    d->f();

    Base *b = d;
    b->f();
  }

  <4 mgates.luna> g++ 6.cc
  <5 mgates.luna> a.out
  D Derived
  B Base

  <7 mgates.luna> cat 7.cc
  #include <stream.h>

  class Base
  {
  public:
    virtual void f(char * className  = "Base")
      { cout << "B " << className << "\n"; }
  };

  class Derived: public Base
  {
  public:
    virtual void f(char * className)
      { cout << "D " << className << "\n"; }
  };

  main()
  {
    Derived *d = new Derived;
    d->f();

    Base *b = d;
    b->f();
  }

  <7 mgates.luna> g++ 7.cc
  <8 mgates.luna> a.out
  D Base
  D Base

--
et tu mgates?




Author: sdm@cs.brown.edu (Scott Meyers)
Date: 4 Jul 91 14:37:03 GMT
Raw View
Hmmm, I think Michael Gates and I got our wires crossed.  I was pointing
out what I believe to be a dangerous anomaly in the language definition,
and he responded with a bug report for g++.  To review:

    #include <iostream.h>

    class Base {
    public:
      virtual void f(char * className = "Base")
      { cout << "In Base::f, parameter value is " << className << endl; }
    };

    class Derived: public Base {
    public:
      virtual void f(char * className = "Derived")
      { cout << "In Derived::f, parameter value is " << className << endl; }
    };

    main()
    {
      Derived *d = new Derived;
      d->f();                  // Call #1

      Base *b = d;
      b->f();                  // Call #2
    }

According to the ARM, Call #1 should be to Derived::f with a parameter
value of "Derived."  This is correctly implemented by cfront 2.0, cfront
2.1, and g++ 1.39.0.

According to the ARM, Call#2 should be to Derived::f with a parameter value
of "Base."  This is correctly implemented by cfront 2.0 and cfront 2.1, but
is incorrectly implemented by g++ 1.39.0, which calls Derived::f with a
parameter value of "Derived."

Michael originally responded as follows:

    The presence of the different default in the derived function
    is causing Derived::f to hide Base::f, rather than overriding it.

This is true for g++, but is not supposed to be true for the language.
Derived::f isn't supposed to hide Base::f, it's supposed to redefine
(override) it, and cfront 2.X correctly implements this behavior.

I originally said:

    Both calls to f resolve to Derived::f, as expected, but the parameter
    has different values, depending on which pointer was used to invoke it.

    As I understand it, the reason that a default parameter value must be
    given in the declaration xor the definition of a function is that that
    prevents inconsistency between the two (ARM 8.2.6, pp. 144-5).  Here we
    seem to have a similar problem, except that it's between two
    declarations.

    What would be the ramifications of prohibiting the specification of a
    default value for a parameter in a redefinition of an inherited virtual
    function, i.e., requiring that derived classes use the default
    parameter values specified in the virtual functions they inherit?

I'm still interested in opinions on this.

Michael also wrote:

    Personally, I think allowing different default arguments for virtual
    functions in derived classes is very desirable.

I still put forth this question:

    And how do you feel about the unintuitive but correct behavior exhibited
    by cfront above?


Scott

-------------------------------------------------------------------------------
What do you say to a convicted felon in Providence?  "Hello, Mr. Mayor."




Author: bruce@pixar.com (...missing keyboard - hit F1 to continue.)
Date: 3 Jul 91 20:52:30 GMT
Raw View
I ran into this last year, and wrote Bjarne. He bounced back my message
with his response edited in. Here it is:


>From ucbvax!alice!alice!ucbvax!pixar!bruce Thu Aug  2 12:20:36 1990
Received: from pixar by chutzpah; 02 AUG 90 12:20:36 PDT
>From: ucbvax!alice!alice!ucbvax!pixar!bruce (Bruce Perens)
To: "bs@alice.UUCP"
Received: from ucbvax.UUCP by pixar (4.1/SMI-4.1)
 id AA01207; Thu, 2 Aug 90 12:20:35 PDT
Received: from alice.UUCP by ucbvax.Berkeley.EDU (5.63/1.42)
 id AA20373; Thu, 2 Aug 90 12:10:04 -0700
Received: from pixar.UUCP by ucbvax.Berkeley.EDU (5.63/1.42)
 id AA29473; Wed, 1 Aug 90 20:21:12 -0700
Received: from chutzpah by pixar (4.1/SMI-4.1)
 id AA04449; Wed, 1 Aug 90 19:46:59 PDT
Date: Wed, 1 Aug 90 19:46:59 PDT
Message-Id: <9008020246.AA04449@pixar>
Received: by chutzpah; 01 AUG 90 19:46:59 PDT
Subject: derived class re-defines default argument of virtual function?
Status: RO

Dr Stroustrup,

Sorry to bother you, but I can't find the answer to this question
in "Ellis and Stroustrup". Is it legal for a derived class to re-define
the default argument to a virtual function? Cfront allows it in this example
program.
***> It is legal. The information is there, but not in one place (8.2.6
 especially pg 144 comes close, though).

 (1) Default arguments are not part of the type of the function
  but a part of the declaration
 (2) declarations are scoped and can hide each other
 (3) a derived class is a scope within the scopes of its
  base clases.

 So since Base::x() and Derived::x() are different declarations
 (even for different functions they can have different default
 arguments). The default argument used is determined by the scope
 used to resolve a call.
     Many Thanks

     Bruce Perens
     pixar!bp@UCBVax.Berkeley.EDU
#include <iostream.h>

class Base {
public:
 virtual void x(int a = 1);
};
class Derived : public Base {
public:
 virtual void x(int a = 2);
};

void
Base::x(int a)
{
 cout << a << endl;
***> Here the Base:: scope is used implying default argument 1
}
void
Derived::x(int a)
{
 cout << a << endl;
***> Here the Derived:: scope is used implying default argument 2
}

main()
{
 Derived d;
 Base * b = &d;

 b->x();  /* Result is 1 */
 d.x();  /* Result is 2 */
 return 0;
}

***> Implying that cfront is doing the right thing.

 - Bjarne




Author: mgates@entiat.boeing.com (Michael Gates)
Date: 5 Jul 91 20:56:03 GMT
Raw View
In article <80057@brunix.UUCP> sdm@cs.brown.edu (Scott Meyers) writes:

>   Hmmm, I think Michael Gates and I got our wires crossed.  I was pointing
>   out what I believe to be a dangerous anomaly in the language definition,
>   and he responded with a bug report for g++.  To review:
>
>       #include <iostream.h>
>
>       class Base {
>       public:
>  virtual void f(char * className = "Base")
>  { cout << "In Base::f, parameter value is " << className << endl; }
>       };
>
>       class Derived: public Base {
>       public:
>  virtual void f(char * className = "Derived")
>  { cout << "In Derived::f, parameter value is " << className << endl; }
>       };
>
>       main()
>       {
>  Derived *d = new Derived;
>  d->f();                  // Call #1
>
>  Base *b = d;
>  b->f();                  // Call #2
>       }
>
>   According to the ARM, Call #1 should be to Derived::f with a parameter
>   value of "Derived."  This is correctly implemented by cfront 2.0, cfront
>   2.1, and g++ 1.39.0.
>
>   According to the ARM, Call#2 should be to Derived::f with a parameter value
>   of "Base."

 Could you tell me where the ARM states this?

>              This is correctly implemented by cfront 2.0 and cfront 2.1, but
>   is incorrectly implemented by g++ 1.39.0, which calls Derived::f with a
>   parameter value of "Derived."

 Actually, g++ calls Base::f with a parameter value of "Base"
(also incorrect), but I realize that is not too important.

>   I still put forth this question:
>
>      And how do you feel about the unintuitive but correct behavior exhibited
>      by cfront above?

 I think it is indeed unintuitive. I think that the type of the
object (rather than the type of the pointer) should determine the
default argument used, just as it determines the version of the
virtual function used.
--
et tu mgates?