July 26, 2004
But now you have turned a compile time bug into a run time bug
I have to agree with walter here (I tend to undervalue DbC because so far it's been a runtime system, and I enjoy resting (more) easy once it compiles...)  I'm never gonna test every branch in my whole code in a big enough project.


however I still support this override keyword, and I have not heard Walter's comment on that :-)



> 
> Okay, second example....
> 
> 
>>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.
> 
> 
> <sarcasm>I take it you haven't heard of something called "design by contract"
> then? It's a feature of D which C++ does not possess.</sarcasm>
> 
> 
> #    class D : B
> #    {
> #        invariant
> #        {
> #            assert(square == i * i);
> #        }
> #    }
> 
> 
> 
July 26, 2004
"Lars Ivar Igesund" <larsivar@igesund.net> wrote in message news:ce2a40$f4t$1@digitaldaemon.com...
> This is clearly bad design/programming of the class D. If you override some methods of B deliberately (as this person clearly do, he both use B.x and B.set), you damn sure need to know what's in B.

Of course - and that's the problem. It's an easy mistake to make, in my opinion, too easy.

> Whether
> programmers doing this is able to make a good OO program at all, I
> highly doubt. (I don't imply that this example show your skill level in
> OOP, Walter :), but you should find a better example.)

It's obvious what's wrong when this is boiled down to such a tiny example. It isn't quite so obvious when it is married to all the usual cruft one finds in a class. Analogously, we both know that this C++ code:

    int *p;
    *p = 6;

is bad code, and of course we wouldn't write that. But that kind of problem does crop up because it can be buried in a lot of other code. D has automatic initialization of variables to expose such problems. The same applies to the overloading/overriding issue. It's designed to make such unintentional mistakes unlikely - to overload based on methods from another scope, you have to do it intentionally (using the alias declaration).


July 26, 2004
"Arcane Jill" <Arcane_member@pathlink.com> wrote in message news:ce2d32$iso$1@digitaldaemon.com...
> In article <ce27vn$diq$1@digitaldaemon.com>, Walter says...
> >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 ?
> >}
> >-----------------------------------
>
> X1.f, obviously. It's an exact match. There is no ambiguity there.
>
> >His argument is that one can easilly miss an overload of f() somewhere in
a
> >complex class heirarchy,
>
> Then one should RTFM.

The idea is to reduce the likelihood of unintentional behavior. It doesn't really do much good to keep telling people to RTFM.

> >and argues that one should not need to understand
> >everything about a class heirarchy in order to derive from it.
> But why not?

Because we are people and we don't work that way. How many of us even read the instructions that come with things? I can't remake people, all I can do is try to design a language that will reduce the probability of unintended consequences of not thoroughly understanding classes before making use of them. (Ever looked at STL classes? <g>)  And I've myself made such mistakes even with base classes I wrote myself.


> Okay, second example....
>
> >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.
>
> <sarcasm>I take it you haven't heard of something called "design by
contract"
> then? It's a feature of D which C++ does not possess.</sarcasm>

Of course, there's always the characteristic that if one boils a complex piece of code down to its minimum, the cause of the problem jumps out at one, and one could say don't write the code that way. When it happens in the real world, it happens buried in complex code with complicated interrelationships, it can be pretty hard to spot, and a simple DbC line may be inadequate. Furthermore, isn't it better to catch a bug without needing to write additional code to check for it?


July 26, 2004
Derek Parnell wrote:

> This example has now convinced me the both Stroustrup and Walter have got
> it wrong. At first, I thought that all I need do is make some explicit
> casts, but...
> 
> Here is some D code to show it's madness...
> 
> <code>
> class X1     { void f(int x)   {printf("1\n");} }
> class X2: X1 { void f(long x)  {printf("2\n");} }
> class X3: X2 { void f(uint x)  {printf("3\n");} }
> class X4: X3 { void f(char x)  {printf("4\n");} }
> class X5: X4 { void f(double x){printf("5\n");} }
> void main()
> {
>   X5 p = new X5;
>   p.f(cast(int)1);
>   p.f(cast(long)1);
>   p.f(cast(uint)1);
>   p.f(cast(char)1);
>   p.f(cast(double)1);
>   p.f(1);
> }
> </code>
> 
> And the result...
> 
>  c:\temp>dmd test
>  C:\DPARNELL\DMD\BIN\..\..\dm\bin\link.exe test,,,user32+kernel32/noi;
> 
>  c:\temp>test
>  5
>  5
>  5
>  5
>  5
>  5
> 
>  c:\temp>
> 
> 
> WHAT!?!? The coder has *explicitly* said he wanted an
> 'int'/'long'/'uint'... argument but D just went and converted to 'double'
> anyhow. Even the non-cast call did *not* use a double as the argument
> value; 1 is an integer not any form of floating point number.
<snip>
> This current method matching rule is starting to seem more like lunacy than
> sanity. I thought compilers were supposed to help coders. This is just
> making more work rather than less work. Here is the code to make it work
> the 'intuitive' way...

So, Walter, are you saying that alias is necessary to protect us from this?  I, for one do not need to be protected from superclasses, and I think quite a few people are saying that.

Why can't the scoping rules search EVERYWHERE for an exact match (since 99.999999% of the time that's what you intend), and then use your current scoping rules if you can't find an exact match?  I agree with Derek: 'This current method matching rule is starting to seem more like lunacy than sanity'.

Are you saying that I didn't know what I was doing as a programmer when I inherited from a base class, and I MUST use alias to show my intentions?  This is one of the most counter-intuitive ideas I've seen in quite a while, and will send many budding library writers to an early D death, which is not what you are looking for.

This is not anywhere near "The Principle of Least Surprise"

Scott Sanders
July 26, 2004
Derek Parnell wrote:

> Here is some D code to show it's madness...
> 
> <code>
> class X1     { void f(int x)   {printf("1\n");} }
> class X2: X1 { void f(long x)  {printf("2\n");} }
> class X3: X2 { void f(uint x)  {printf("3\n");} }
> class X4: X3 { void f(char x)  {printf("4\n");} }
> class X5: X4 { void f(double x){printf("5\n");} }
> void main()
> {
>   X5 p = new X5;
>   p.f(cast(int)1);
>   p.f(cast(long)1);
>   p.f(cast(uint)1);
>   p.f(cast(char)1);
>   p.f(cast(double)1);
>   p.f(1);
> }
> </code>
> 
> And the result...
> 
>  c:\temp>dmd test
>  C:\DPARNELL\DMD\BIN\..\..\dm\bin\link.exe test,,,user32+kernel32/noi;
> 
>  c:\temp>test
>  5
>  5
>  5
>  5
>  5
>  5
> 
>  c:\temp>
> 
> 
> WHAT!?!? The coder has *explicitly* said he wanted an
> 'int'/'long'/'uint'... argument but D just went and converted to 'double'
> anyhow. Even the non-cast call did *not* use a double as the argument
> value; 1 is an integer not any form of floating point number.

C++ and C# do exactly the same thing, which suggests that overloading and inheritance have never been good bedfellows, if for no other reason than because the need for it rarely arises.

That being said, extending a class is a conceptual joining of everything in the base class, plus everything defined in the subclass.  It seems logical that the language reflect this behaviour and resolve names using the full class, not just the part of it defined in the current class block.

This also inhibits things like template classes which generate class heirarchies from typelists, but this might be a good thing. ;)

 -- andy
July 26, 2004
"Derek Parnell" <derek@psych.ward> wrote in message news:ce2ce5$ib8$1@digitaldaemon.com...
> Here is some D code to show it's madness...
>
> <code>
> class X1     { void f(int x)   {printf("1\n");} }
> class X2: X1 { void f(long x)  {printf("2\n");} }
> class X3: X2 { void f(uint x)  {printf("3\n");} }
> class X4: X3 { void f(char x)  {printf("4\n");} }
> class X5: X4 { void f(double x){printf("5\n");} }
> void main()
> {
>   X5 p = new X5;
>   p.f(cast(int)1);
>   p.f(cast(long)1);
>   p.f(cast(uint)1);
>   p.f(cast(char)1);
>   p.f(cast(double)1);
>   p.f(1);
> }
> </code>
>
> And the result...
>
>  c:\temp>dmd test
>  C:\DPARNELL\DMD\BIN\..\..\dm\bin\link.exe test,,,user32+kernel32/noi;
>
>  c:\temp>test
>  5
>  5
>  5
>  5
>  5
>  5
>
>  c:\temp>
>
>
> WHAT!?!? The coder has *explicitly* said he wanted an 'int'/'long'/'uint'... argument but D just went and converted to 'double' anyhow.

That's normal behavior, it's the integral argument promotion rules in C and C++ which survives intact in D.

> Even the non-cast call did *not* use a double as the argument value; 1 is an integer not any form of floating point number.

But it's implicitly convertible to a double.


> Then I changed X5 to include the 'alias X1.f f;' line and get this message...
>
>   test.d(10): function f overloads void(double x) and void(int x) both
>   match argument list for f
>
> WHAT?!?!? Since when is (double x) and (int x) the same? This is *not* a
> match.

That's a compiler bug :-(

> This current method matching rule is starting to seem more like lunacy
than
> sanity. I thought compilers were supposed to help coders. This is just making more work rather than less work. Here is the code to make it work the 'intuitive' way...
>
> <code>
> class X1     { void f(int x)   {printf("1\n");} }
> class X2: X1 { void f(long x)  {printf("2\n");}
>                alias X1.f f;
>              }
> class X3: X2 { void f(uint x)  {printf("3\n");}
>                alias X1.f f;
>                alias X2.f f;
>              }
> class X4: X3 { void f(char x)  {printf("4\n");}
>                alias X1.f f;
>                alias X2.f f;
>                alias X3.f f;
>              }
> class X5: X4 { void f(double x){printf("5\n");}
>                alias X1.f f;
>                alias X2.f f;
>                alias X3.f f;
>                alias X4.f f;
>              }
> void main()
> {
>   X5 p = new X5;
>
>   p.f(cast(int)1);
>   p.f(cast(long)1);
>   p.f(cast(uint)1);
>   p.f(cast(char)1);
>   p.f(cast(double)1);
>   p.f(1);
> }
> </code>
>
> <output>
> c:\temp>dmd test
> C:\DPARNELL\DMD\BIN\..\..\dm\bin\link.exe test,,,user32+kernel32/noi;
>
> c:\temp>test
> 1
> 2
> 3
> 4
> 5
> 1
> </output>
> And that's just for *one* method.

It's not that bad. The following should work:

class X1     { void f(int x)   {printf("1\n");} }
class X2: X1 { void f(long x)  {printf("2\n");}
               alias X1.f f;
             }
class X3: X2 { void f(uint x)  {printf("3\n");}
                alias X2.f f;
             }
class X4: X3 { void f(char x)  {printf("4\n");}
               alias X3.f f;
             }
class X5: X4 { void f(double x){printf("5\n");}
                alias X4.f f;
             }

as the aliases become part of the names in a scope, and so get carried along with the other names with a subsequent alias. And I'd add that if one actually wrote such an inheritance graph like that, the aliases are a good thing because they show you *intended* to overload them based on names from another scope.


> This type of bug is analogous to a spelling mistake. Its similar to the situation where two methods are spelled almost identically and the coder either chooses the wrong one or mistypes it.

You're right. But spelling mistakes tend to get picked up by the compiler and show up as "undefined identifier" messages. Languages that implicitly declare misspelled identifiers tend to be very hard languages to debug code in. To extend the analogy to the example, I'd rather have to proactively say I want to include the declarations from another scope (with an alias declaration) than have it happen implicitly with no way to turn it off, and subtle problems result. Think of the alias declaration like an explicit cast - it tells the compiler (and the maintenance programmer) that "yes, I intended to do this" and the compiler says "yes sir, three bags full sir" and adds the methods into the scope and does the cast.


July 26, 2004
"Roberto Mariottini" <Roberto_member@pathlink.com> wrote in message news:ce2gc7$kr2$1@digitaldaemon.com...
> In article <ce27vn$diq$1@digitaldaemon.com>, Walter says...
> >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:
>
> I translated your example in Java, and it shows the "wrong" (non-C++)
behaviour.
> The attached Test.java prints:
>
> The square is: 0

Thank-you. I expected Java to do that, it's nice to have confirmation.


July 26, 2004
In article <ce3c7j$18du$1@digitaldaemon.com>, Walter says...
>
>
>"Derek Parnell" <derek@psych.ward> wrote in message news:ce2ce5$ib8$1@digitaldaemon.com...
>> Here is some D code to show it's madness...
>>
>> <code>
>> class X1     { void f(int x)   {printf("1\n");} }
>> class X2: X1 { void f(long x)  {printf("2\n");} }
>> class X3: X2 { void f(uint x)  {printf("3\n");} }
>> class X4: X3 { void f(char x)  {printf("4\n");} }
>> class X5: X4 { void f(double x){printf("5\n");} }
>> void main()
>> {
>>   X5 p = new X5;
>>   p.f(cast(int)1);
>>   p.f(cast(long)1);
>>   p.f(cast(uint)1);
>>   p.f(cast(char)1);
>>   p.f(cast(double)1);
>>   p.f(1);
>> }
>> </code>
>>
>> And the result...
>>
>>  c:\temp>dmd test
>>  C:\DPARNELL\DMD\BIN\..\..\dm\bin\link.exe test,,,user32+kernel32/noi;
>>
>>  c:\temp>test
>>  5
>>  5
>>  5
>>  5
>>  5
>>  5
>>
>>  c:\temp>
>>
>> WHAT!?!? The coder has *explicitly* said he wanted an 'int'/'long'/'uint'... argument but D just went and converted to 'double' anyhow.
>
>That's normal behavior, it's the integral argument promotion rules in C and C++ which survives intact in D.
>
>> Even the non-cast call did *not* use a double as the argument value; 1 is an integer not any form of floating point number.
>
>But it's implicitly convertible to a double.

I like the current D scheme.  In a sense, every class defines its own interface and calls are evaluated against this interface before walking the inheritance tree.  This safety is a good thing.  The alternative would be code that subtly breaks if a function prototype is added or altered somewhere up the inheritance tree.  I'm surprised that folks can argue that "override" should be mandatory and then argue that the current D lookup scheme is broken, since the goal of both things is to protect the programmer from subtle inheritance-based coding errors.

As far as the above example is concerned, perhaps this is an argument for the "explicit" keyword.  It would certainly reduce or eliminate much of the confusion about D's lookup rules.


Sean


July 26, 2004
In article <ce3abq$1778$1@digitaldaemon.com>, Andy Friesen says...

>>  c:\temp>test
>>  5
>>  5
>>  5
>>  5
>>  5
>>  5
>> 
>>  c:\temp>

>C++ and C# do exactly the same thing, which suggests that overloading and inheritance have never been good bedfellows, if for no other reason than because the need for it rarely arises.

I actually got caught out with this one a few years back - in C++. I thought the behavior was wrong then, and I still think it's wrong now. At the time, I didn't realize that C++ was defined that way, I just assumed that the compiler was crap. I had to workaround it by overriding the base class function in the derived class with an exact signature match, and defining the overridden function to call the base class function. That was a very time consuming bug to find, and the workaround/fix was not very satisfying. C++ got this one wrong. Java seems to have got it right. (In my humble opinion).

This use of alias (in D) looks just like #define to me. That's obfuscation, not
clarity.

Jill


July 26, 2004
Once again, I find myself in the middle.  Theoretically, I agree with you, Kris; the current rules don't make any sense.  However, I think that I can use the same argument you used to support "override" to support Walter's position here.  Here's the argument:

You implement a class like this, where "Base" is a base class from some library:

class Child : Base {
  void foo(int x) {...}
}

Then you write this code:

extern (C) void call_foo(void *ptr, char c) {
  printf("I'm about to call foo(int)\n");
  Child ref = cast(Child)ptr;
  assert(ref !== null);
  ref.foo(c);
  printf("I'm done calling foo(int)\n");
}

This all works ok.  Your code correctly does the implicit cast of char to int.  However, later, the library adds a member function:

class Base {
  void foo(char c) {...}
}

Base's foo() has nothing to do with your version of foo().  Should the code call Base.foo(char) or Child.foo(int)?

Kris wrote:
> "Walter" <newshound@digitalmars.com> wrote in message
> news:cdvhgv$1nf1$1@digitaldaemon.com...
> 
>>"Kris" <someidiot@earthlink.dot.dot.dot.net> wrote in message
>>news:cdu9ov$162i$1@digitaldaemon.com...
>>
>>>Sean; would you mind explaining the benefits of those particular
>>
>>resolution
>>
>>>rules to me please? While I may be both blind and stupid, I completely
>>
>>fail
>>
>>>to see where the value lies, and no-one has specified what they are.
>>
>>Stroustrup has laid them out in chapter 13.1 of the "The Annotated C++
>>Reference Manual." The general rule for overloading is that a name is
> 
> looked
> 
>>up first, and then overload resolution is applied. Stroustrup rejects the
>>idea of ignoring scope issues when doing overload resolution. He argues
> 
> that
> 
>>it would be surprising in a deeply nested heirarchy of class derivation,
>>that a user of a derived class would have to know too much detail about a
>>base class that may be hidden. Also, he shows how surprising errors can
>>creep in, such as if a function in base class B copies only the B part of
> 
> an
> 
>>object, but was inadvertantly called with an instance of D that didn't
>>override each of those B functions properly.
>>
>>
> 
> 
> That is indeed the C++ view. However you forget a number of things:
> 
> 1) D is better in so many ways than C++. It says so right there in the
> comparison charts ... <g>
> 
> 2) C++ had to deal with multiple inheritance ~ I imagine that's a big factor
> in the above statement. D (and Java) do not. Notice how Java dispensed with
> the 'alias' notion completely, and did not pay this 'Stroustrup Tax' in any
> shape or form.
> 
> 3) Aliasing method names is truly non-intuitive. It stands out like a sore
> thumb within D as a kludge. It does not matter from whence it came, or what
> supposed pedigree it has ~ so many other things in D are completely natural.
> 
> 4) You may find that a required use of  "override" would alleviate some of
> those Stroustrup concerns.
> 
> 5) You are at least as smart as B.S., and can do much better than this. I
> can't help but be entirely suspicious about the B.S. Gospel applying in the
> same manner.
> 
> I challenge you, Walter, to exceed C++ in this respect also.
> 
> I further challenge you to provide a reasonable D example showing where this
> kind of alias thing is actually beneficial to the D programmer. If you
> cannot do that, then this Stroustrup argument holds no water whatsoever.
> Since we're all required to spend a bunch of time whittling down code in bug
> reports for you, I think it's only fair you do the same in return with
> respect to issues like this. I mean, if the value of alias is so obvious,
> how much effort would a few good examples really take?
> 
> Regards;
> 
> - Kris
> 
> 
> 
> 
>