www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Re: Overloading/Inheritance issue

reply Steve Schveighoffer <schveiguy yahoo.com> writes:
Regan Heath Wrote:

 
 It's truly odd that no-one seems to have a problem with it in C++.  It's 
 not as if C++ even has 'alias' so it cannot pull symbols in, you're left 
 re-implementing in the derived class.  Perhaps everyone just does that 
 without thinking about it.

After thinking about it, my opinion is that C++ coders tend to avoid implementing everything in objects using inheritance as opposed to languages which require using objects for everything (i.e. Java). This tends to have many coders putting their "methods" in the global namespace instead of in a method, putting everything in the same scope. and BTW, Walter in his previous posts rightly points out that the "using" keyword can be used inside a C++ class much like the "alias" keyword is used in a D class to achieve the same effect.
 
 However, I think it should be changed.  Not sure if it will, as it
 seems there  is already a precedent.  In my opinion, the base class
 should be examined if the derived class does not provide a suitable
 match.  I can't see how this would cause too much of a performance
 hit

Performance isn't the reason against the Java behaviour, here is Walters explaination: <quote Walter quoting Stroustrup> ... </quote> So, as you can see it's not for performance reasons but rather to avoid obscure bugs which when they manifest do so silently. Regan

My argument is not against this, as you will note that in these examples, both base and derived classes can match the call by some sort of implicit conversion. In the case where both the base class and the derived class match the call, I could care less if the derived class was called instead of the base class. Who can say what class documentation the coder was looking at when he wrote the call? Both options seem equally likely. I am proposing a change of behavior when the derived class CANNOT match the call. At that point the compiler errors out instead of examining the base classes. When the coder writes code where there is only one match, and that match is in a base class, it makes perfect sense that the coder explicitly wants to call the base class' method. I see no reason to force the coder to explicitly call for the base class' method when there is no alternative in the derived class, the code is unambiguous. I'd bet we'd see 0 obscure bugs if this change was made ;) As I've mentioned, I've already seen one obscure bug caused by the current implementation... -Steve
Aug 02 2007
next sibling parent reply Steve Schveighoffer <schveiguy yahoo.com> writes:
Steve Schveighoffer Wrote:

 Regan Heath Wrote:

 
 Performance isn't the reason against the Java behaviour, here is Walters 
 explaination:
 
 <quote Walter quoting Stroustrup>
 ...
 </quote>
 
 So, as you can see it's not for performance reasons but rather to avoid 
 obscure bugs which when they manifest do so silently.
 
 Regan


Hm.. for some reason, I thought this would appear under the original thread. In any case here is the missing quote from above: <quote Walter quoting Stroustrup> Stroustrup gives two examples (slightly modified here): --------------------------------- class X1 { void f(int); } // chain of derivations X(n) : X(n-1) class X9: X8 { void f(double); } void g(X9 p) { p.f(1); // X1.f or X9.f ? } ----------------------------------- His argument is that one can easilly miss an overload of f() somewhere in a complex class heirarchy, and argues that one should not need to understand everything about a class heirarchy in order to derive from it. The other example involves operator=(), but since D doesn't allow overloading operator=() instead I'll rewrite it as if it were a function that needs to alter a class state, and a derived class written later that 'caches' a computation on the derived state: class B { long x; void set(long i) { x = i; } void set(int i) { x = i; } long squareIt() { return x * x; } } class D : B { long square; void set(long i) { B.set(i); square = x * x; } long squareIt() { return square; } } Now, imagine B were a complex class with a lot of stuff in it, and our optimizing programmer missed the existence of set(int). Then, one has: long foo(B b) { b.set(3); return b.squareIt(); } and we have an obscure bug. </quote>
Aug 02 2007
parent Regan Heath <regan netmail.co.nz> writes:
Steve Schveighoffer wrote:
 Hm.. for some reason, I thought this would appear under the original
 thread.  In any case here is the missing quote from above:

The web interface has ... 'issues' on occasion. I use Thunderbird myself. Opera is also good. Regan
Aug 03 2007
prev sibling parent reply Regan Heath <regan netmail.co.nz> writes:
Steve Schveighoffer wrote:
 Regan Heath Wrote:
 So, as you can see it's not for performance reasons but rather to
 avoid obscure bugs which when they manifest do so silently.

My argument is not against this, as you will note that in these examples, both base and derived classes can match the call by some sort of implicit conversion. In the case where both the base class and the derived class match the call, I could care less if the derived class was called instead of the base class.

But, that's exactly the problem. In fact example 2 shows that we currently have the undesirable and buggy behaviour in the current D implementation, eg. import std.stdio; class B { long x; void set(long i) { writefln("B::set(long)"); x = i; } void set(int i) { writefln("B::set(int)"); x = i; } long squareIt() { writefln("B::squareit()"); return x * x; } } class D : B { long square; void set(long i) { writefln("D::set(long)"); B.set(i); square = x * x; } long squareIt() { writefln("D::squareit()"); return square; } } long foo(B b) { b.set(3); return b.squareIt(); } void main() { writefln(foo(new D)); } Output: B::set(int) D::squareit() 0 In the above example the object is actually of type 'D' but the method is called from a reference to a 'B'. The result is a call to B::set(int), instead of D::set(long) and then D::squareit() which fails utterly. This is an 'obscure' bug because it is happening silently. It seems example 2 either no longer behaves as it did when it was first posted (D has changed) or it was never a correct example for the problem.
 I am proposing a change of behavior when the derived class CANNOT
 match the call.  At that point the compiler errors out instead of
 examining the base classes.  When the coder writes code where there
 is only one match, and that match is in a base class, it makes
 perfect sense that the coder explicitly wants to call the base class'
 method.  I see no reason to force the coder to explicitly call for
 the base class' method when there is no alternative in the derived
 class, the code is unambiguous.

I see the distinction, and it is the case with your original example as you wanted it to call select() from the base class. Interestingly, if the derived class "EpollSelector" had to conform to the interface "ISelector" that the base class "AbstractSelector" implements then the author of "EpollSelector" would have noticed and added the 'alias' and this would never have come up. This change you suggest does violate D's current 'simple' lookup rules and go against the original reasoning of: "His argument is that one can easilly miss an overload of f() somewhere in a complex class heirarchy, and argues that one should not need to understand everything about a class heirarchy in order to derive from it." So, if you want to see this change you're going to have to post something convincing addressing this, probably with some real-life examples.
 I'd bet we'd see 0 obscure bugs if this change was made ;)   As I've
 mentioned, I've already seen one obscure bug caused by the current
 implementation...

Not to nit pick but is the bug you're referring to the problem you had with Tango's tango.io.selector.EPollSelector? If so I wouldn't say that bug was 'oscure' because presumably it gave you an error on compile, as opposed to silently failing on some customers system which is what happens in the two examples given by Walter (including example 2 above which fails in D today). Regan
Aug 03 2007
next sibling parent reply Steve Schveighoffer <schveiguy yahoo.com> writes:
Regan Heath Wrote:

 Steve Schveighoffer wrote:
  > My argument is not against this, as you will note that in these
  > examples, both base and derived classes can match the call by some
  > sort of implicit conversion.  In the case where both the base class
  > and the derived class  match the call, I could care less if the
  > derived class was called instead of the base class.
 
 But, that's exactly the problem.
 
 In fact example 2 shows that we currently have the undesirable and buggy 
 behaviour in the current D implementation, eg.
 

Oh, I understand what you are saying. However, undesirable behavior is unavoidable no matter which way we go. It all depends on what you expect and what the user of the class expects. My thought is that you have to pick one way, and just tell people that's the way it is and they have to live with it. I'm speaking only of the case where both base and derived class can match the call exactly or through some implicit conversion. On second thought, you could pick a third option for behavior, and say that if the derived class has no explicit match, but has implicit matches, and the base class has explicit or implicit matches, the compiler should error out saying it doesn't know which one you intended. That would mean even less obscure bugs, forcing the user of the class to pick the one they want by explicitly casting the arguments or the object.
 I see the distinction, and it is the case with your original example as 
 you wanted it to call select() from the base class.
 
 Interestingly, if the derived class "EpollSelector" had to conform to 
 the interface "ISelector" that the base class "AbstractSelector" 
 implements then the author of "EpollSelector" would have noticed and 
 added the 'alias' and this would never have come up.

One of the problems is that you would most likely re-use the unit test code for AbstractSelector, which means you would cast the EPollSelector instance to the AbstractSelector test code, hiding the actual problem. The only way to find this is for the author to write test code that specifically uses the derived class to call the base class' functions that he expects to still exist. I think this is an unnecessary requirement for finding this problem, especially when there is no reason to want it the other way.
 
 This change you suggest does violate D's current 'simple' lookup rules 
 and go against the original reasoning of:
 
 "His argument is that one can easilly miss an overload of f() somewhere 
 in a complex class heirarchy, and argues that one should not need to 
 understand everything about a class heirarchy in order to derive from it."

So by this argument, Walter is saying that the desired behavior of the author is to only make available the overloaded methods that the derived class overrides? I totally disagree. Why would an author want to do this? If it is to hide the other overloads, he has failed, because the user of the class can just cast to a base class in order to call the method they want. If the author desires to hide this from the user of the class, then his class can be used in ways he didn't intend. An author who overrides a class ignorant of all the functionality the class provides is IMHO a poor coder. Even with your example, with the "squareIt" bug, I would never accept code like that as correct. The argument above seems to be defending that type of behavior as desired. In my opinion, a class should behave the same no matter how you look at it, i.e. how you cast it.
 
 So, if you want to see this change you're going to have to post 
 something convincing addressing this, probably with some real-life examples.

I've posted one. Evidently others have had similar problems. I'm not sure I need any more examples, I think the fundamental argument against is flawed, at least in this specific case where the base class can be used but the derived class cannot. I'm also leaning towards my further argument that the compiler should error out if the desired behavior of the coder is not obvious.
 
  > I'd bet we'd see 0 obscure bugs if this change was made ;)   As I've
  > mentioned, I've already seen one obscure bug caused by the current
  > implementation...
 
 Not to nit pick but is the bug you're referring to the problem you had 
 with Tango's tango.io.selector.EPollSelector?  If so I wouldn't say that 
 bug was 'oscure' because presumably it gave you an error on compile, as 
 opposed to silently failing on some customers system which is what 
 happens in the two examples given by Walter (including example 2 above 
 which fails in D today).

It took me several hours of poking to figure out why the selector thing didn't compile, then several hours of writing test code to demonstrate the issue, researching whether this was desired behavior, and even then, I couldn't find any definite answer in the D spec, which led me to post this thread. I'd say that's a pretty obscure problem :) Not to mention that it's not MY bug. The author probably still doesn't know there is a problem, so from his point of view, the bug is still at large without him noticing. -Steve
Aug 03 2007
parent Christopher Wright <dhasenan gmail.com> writes:
Steve Schveighoffer wrote:
 Regan Heath Wrote:
 
 Steve Schveighoffer wrote:
  > My argument is not against this, as you will note that in these
  > examples, both base and derived classes can match the call by some
  > sort of implicit conversion.  In the case where both the base class
  > and the derived class  match the call, I could care less if the
  > derived class was called instead of the base class.

 But, that's exactly the problem.

 In fact example 2 shows that we currently have the undesirable and buggy 
 behaviour in the current D implementation, eg.

Oh, I understand what you are saying. However, undesirable behavior is unavoidable no matter which way we go. It all depends on what you expect and what the user of the class expects. My thought is that you have to pick one way, and just tell people that's the way it is and they have to live with it. I'm speaking only of the case where both base and derived class can match the call exactly or through some implicit conversion.

Which one saves more work? I think the common case is to want all members of the base class in the derived class. Perhaps the problem is not having a way to hide methods from a base class? Except, as you point out, you can always cast to the base class and retrieve those methods. The Java version seems simpler to use and understand. Perhaps this is a place where compiler warnings are appropriate -- it's certainly not an error to override only one overload of a function, but it's a source of potential bugs no matter how you cut it. Though Walter is quite opposed to compiler warnings, and I appreciate the cleanness of DMD's output.
 On second thought, you could pick a third option for behavior, and say that if
the derived class has no explicit match, but has implicit matches, and the base
class has explicit or implicit matches, the compiler should error out saying it
doesn't know which one you intended.  That would mean even less obscure bugs,
forcing the user of the class to pick the one they want by explicitly casting
the arguments or the object.

True. But the original problem would still exist, unless D's behavior changed to Java's. -cbw
Aug 04 2007
prev sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
Regan Heath wrote:
 In fact example 2 shows that we currently have the undesirable and buggy 
 behaviour in the current D implementation, eg.
 
 import std.stdio;
 
 class B
 {    long x;
      void set(long i) { writefln("B::set(long)"); x = i; }
      void set(int i)  { writefln("B::set(int)"); x = i; }
      long squareIt()  { writefln("B::squareit()"); return x * x; }
 }
 class D : B
 {
      long square;
      void set(long i) { writefln("D::set(long)"); B.set(i); square = x * 
 x; }
      long squareIt()  { writefln("D::squareit()"); return square; }
 }
 
 long foo(B b)
 {
     b.set(3);
     return b.squareIt();
 }
 
 void main()
 {
     writefln(foo(new D));
 }
 
 Output:
 B::set(int)
 D::squareit()
 0
 
 In the above example the object is actually of type 'D' but the method 
 is called from a reference to a 'B'.  The result is a call to 
 B::set(int), instead of D::set(long) and then D::squareit() which fails 
 utterly.
 
 This is an 'obscure' bug because it is happening silently.

I agree that this example is a problem. There's no way to detect it at compile time, so it should throw a runtime exception. The way to accomplish that is to stuff D's vtbl[] entry for B.set(int) with a dummy function that throws the exception.
Aug 03 2007
next sibling parent reply Sean Kelly <sean f4.ca> writes:
Walter Bright wrote:
 Regan Heath wrote:
 In fact example 2 shows that we currently have the undesirable and 
 buggy behaviour in the current D implementation, eg.

 import std.stdio;

 class B
 {    long x;
      void set(long i) { writefln("B::set(long)"); x = i; }
      void set(int i)  { writefln("B::set(int)"); x = i; }
      long squareIt()  { writefln("B::squareit()"); return x * x; }
 }
 class D : B
 {
      long square;
      void set(long i) { writefln("D::set(long)"); B.set(i); square = x 
 * x; }
      long squareIt()  { writefln("D::squareit()"); return square; }
 }

 long foo(B b)
 {
     b.set(3);
     return b.squareIt();
 }

 void main()
 {
     writefln(foo(new D));
 }

 Output:
 B::set(int)
 D::squareit()
 0

 In the above example the object is actually of type 'D' but the method 
 is called from a reference to a 'B'.  The result is a call to 
 B::set(int), instead of D::set(long) and then D::squareit() which 
 fails utterly.

 This is an 'obscure' bug because it is happening silently.

I agree that this example is a problem. There's no way to detect it at compile time, so it should throw a runtime exception. The way to accomplish that is to stuff D's vtbl[] entry for B.set(int) with a dummy function that throws the exception.

Certain C++ compilers have done something like this in the past, if I remember correctly. I'd have to do some googling to remember the details, but I'm sure it was either the MS or Sun compiler. Sean
Aug 03 2007
parent reply Walter Bright <newshound1 digitalmars.com> writes:
Sean Kelly wrote:
 Walter Bright wrote:
 I agree that this example is a problem. There's no way to detect it at 
 compile time, so it should throw a runtime exception. The way to 
 accomplish that is to stuff D's vtbl[] entry for B.set(int) with a 
 dummy function that throws the exception.

Certain C++ compilers have done something like this in the past, if I remember correctly. I'd have to do some googling to remember the details, but I'm sure it was either the MS or Sun compiler.

I didn't know that. This particular problem may not come up so often with C++ because functions are not virtual by default - but not virtual by default produces its own set of very hard to find bugs that I've wasted many hours tracking down.
Aug 03 2007
parent Sean Kelly <sean f4.ca> writes:
Walter Bright wrote:
 Sean Kelly wrote:
 Walter Bright wrote:
 I agree that this example is a problem. There's no way to detect it 
 at compile time, so it should throw a runtime exception. The way to 
 accomplish that is to stuff D's vtbl[] entry for B.set(int) with a 
 dummy function that throws the exception.

Certain C++ compilers have done something like this in the past, if I remember correctly. I'd have to do some googling to remember the details, but I'm sure it was either the MS or Sun compiler.

I didn't know that. This particular problem may not come up so often with C++ because functions are not virtual by default - but not virtual by default produces its own set of very hard to find bugs that I've wasted many hours tracking down.

I stumbled across the information while trying to diagnose a bizarre linker error. It turned out that the function that couldn't be found was the dummy routine. And that reminds me which compiler it is--it's the MS one. Their explanation made it seem like the situation the routine was there for wasn't likely to ever actually happen and I regarded it as a curiosity at the time. It's kind of neat to see the idea resurface here though :-) Sean
Aug 03 2007
prev sibling parent reply Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
Walter Bright wrote:
 Regan Heath wrote:
 In fact example 2 shows that we currently have the undesirable and 
 buggy behaviour in the current D implementation, eg.

 import std.stdio;

 class B
 {    long x;
      void set(long i) { writefln("B::set(long)"); x = i; }
      void set(int i)  { writefln("B::set(int)"); x = i; }
      long squareIt()  { writefln("B::squareit()"); return x * x; }
 }
 class D : B
 {
      long square;
      void set(long i) { writefln("D::set(long)"); B.set(i); square = x 
 * x; }
      long squareIt()  { writefln("D::squareit()"); return square; }
 }

 long foo(B b)
 {
     b.set(3);
     return b.squareIt();
 }

 void main()
 {
     writefln(foo(new D));
 }

 Output:
 B::set(int)
 D::squareit()
 0

 In the above example the object is actually of type 'D' but the method 
 is called from a reference to a 'B'.  The result is a call to 
 B::set(int), instead of D::set(long) and then D::squareit() which 
 fails utterly.

 This is an 'obscure' bug because it is happening silently.

I agree that this example is a problem. There's no way to detect it at compile time, so it should throw a runtime exception. The way to accomplish that is to stuff D's vtbl[] entry for B.set(int) with a dummy function that throws the exception.

There is a problem the moment the D subclass doesn't override all overloads, so that can be detected at compile time and an error issued right there. -- Bruno Medeiros - MSc in CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Aug 04 2007
parent reply Walter Bright <newshound1 digitalmars.com> writes:
Bruno Medeiros wrote:
 Walter Bright wrote:
 I agree that this example is a problem. There's no way to detect it at 
 compile time, so it should throw a runtime exception. The way to 
 accomplish that is to stuff D's vtbl[] entry for B.set(int) with a 
 dummy function that throws the exception.

There is a problem the moment the D subclass doesn't override all overloads, so that can be detected at compile time and an error issued right there.

I'm not sure that's the right thing to do.
Aug 04 2007
parent reply James Dennett <jdennett acm.org> writes:
Walter Bright wrote:
 Bruno Medeiros wrote:
 Walter Bright wrote:
 I agree that this example is a problem. There's no way to detect it
 at compile time, so it should throw a runtime exception. The way to
 accomplish that is to stuff D's vtbl[] entry for B.set(int) with a
 dummy function that throws the exception.

There is a problem the moment the D subclass doesn't override all overloads, so that can be detected at compile time and an error issued right there.

I'm not sure that's the right thing to do.

In general, overloading virtual functions in the first place is a mistake: in practice it means that in order for a derived class to implement consistent behavior it must override all of them. A hard error on overloading virtuals would be fair; the fix is to have non-virtual overloads dispatching to a single virtual which has the most general form, and provides a single point of customization for derived classes. This might not sit well with those who think that virtual is a good default though. I'm not one of them; I've never seen problems caused by non-virtual as default, but plenty caused by virtual as default (mostly relating to programmers not thinking deeply enough about what contracts mean for virtual functions, where it's necessary to specify both what the default implementation guarantees, and what derived class implementations must guarantee, including which other functions on the same object can legitimately be called from overrides). -- James
Aug 04 2007
parent reply Walter Bright <newshound1 digitalmars.com> writes:
James Dennett wrote:
 This might not sit well with those who think that virtual is
 a good default though.  I'm not one of them; I've never seen
 problems caused by non-virtual as default,

I, for one, have had several severe and very hard to find bugs caused by non-virtual as the default. It's something that defies visual inspection looking for them.
Aug 04 2007
parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Walter Bright wrote:
 James Dennett wrote:
 This might not sit well with those who think that virtual is
 a good default though.  I'm not one of them; I've never seen
 problems caused by non-virtual as default,

I, for one, have had several severe and very hard to find bugs caused by non-virtual as the default. It's something that defies visual inspection looking for them.

Is there some way currently to force methods to be non-virtual? Does 'final' do that? --bb
Aug 07 2007
parent reply Sean Kelly <sean f4.ca> writes:
Bill Baxter wrote:
 Walter Bright wrote:
 James Dennett wrote:
 This might not sit well with those who think that virtual is
 a good default though.  I'm not one of them; I've never seen
 problems caused by non-virtual as default,

I, for one, have had several severe and very hard to find bugs caused by non-virtual as the default. It's something that defies visual inspection looking for them.

Is there some way currently to force methods to be non-virtual? Does 'final' do that?

Yes, that's what 'final' is for. In my D code, it's rare that I'll write a function that isn't final. Sean
Aug 07 2007
next sibling parent Derek Parnell <derek nomail.afraid.org> writes:
On Tue, 07 Aug 2007 18:03:43 -0700, Sean Kelly wrote:

 Bill Baxter wrote:
 Walter Bright wrote:
 James Dennett wrote:
 This might not sit well with those who think that virtual is
 a good default though.  I'm not one of them; I've never seen
 problems caused by non-virtual as default,

I, for one, have had several severe and very hard to find bugs caused by non-virtual as the default. It's something that defies visual inspection looking for them.

Is there some way currently to force methods to be non-virtual? Does 'final' do that?

Yes, that's what 'final' is for. In my D code, it's rare that I'll write a function that isn't final.

I'm about to make a fool of myself again, but in my defence, I'm not a frequent O-O practitioner. So can someone remind me ... "virtual" means that a member can possibly be overridden by a derived class? "pure virtual" means that a member *must* be overridden by a derived class? "final" means that a member can not be overridden by a derived class? And by default members are "virtual" in D. -- Derek (skype: derek.j.parnell) Melbourne, Australia 8/08/2007 11:19:00 AM
Aug 07 2007
prev sibling parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Sean Kelly wrote:
 Bill Baxter wrote:
 Walter Bright wrote:
 James Dennett wrote:
 This might not sit well with those who think that virtual is
 a good default though.  I'm not one of them; I've never seen
 problems caused by non-virtual as default,

I, for one, have had several severe and very hard to find bugs caused by non-virtual as the default. It's something that defies visual inspection looking for them.

Is there some way currently to force methods to be non-virtual? Does 'final' do that?

Yes, that's what 'final' is for. In my D code, it's rare that I'll write a function that isn't final. Sean

So does that mean you can't do non-virtual overloads in D? I.e. have a situation where: x.foo() and (cast(Base)x).foo() call different member functions? If not, probably it's good riddance, but I do vaguely recall having a use for it once in C++. --bb
Aug 07 2007
parent reply Walter Bright <newshound1 digitalmars.com> writes:
Bill Baxter wrote:
 So does that mean you can't do non-virtual overloads in D?

Right, you cannot.
 I.e. have a situation where:
                 x.foo()
 and
     (cast(Base)x).foo()
 call different member functions?
 
 If not, probably it's good riddance, but I do vaguely recall having a 
 use for it once in C++.

Good riddance <g>.
Aug 07 2007
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Walter Bright" <newshound1 digitalmars.com> wrote in message 
news:f9bcvt$1vqb$1 digitalmars.com...
 Bill Baxter wrote:
 So does that mean you can't do non-virtual overloads in D?

Right, you cannot.
 I.e. have a situation where:
                 x.foo()
 and
     (cast(Base)x).foo()
 call different member functions?


This is kind of cheating, but it does show a possibility where the lines of code you give do call 2 different functions :) Of course, it's completely off topic, but a fun exercise. import tango.io.Stdout; class Base { void foo(char[] x = "blah") { Stdout("in Base::foo").newline; } } class Derived : Base { void foo(int x = 5) { Stdout("in Derived::foo").newline; } } int main(char[][] args) { Derived x = new Derived; x.foo(); (cast(Base)x).foo(); return 0; } output: in Derived::foo in Base::foo -Steve
Aug 08 2007