www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - hiding a class property behind a method

reply luka8088 <luka8088 owave.net> writes:
It seems to me that the following code should be illegal, but I am
uncertain of it so I am posting here for a confirmation before I post it
on bug tracker:


http://dpaste.dzfl.pl/dae728734cc6


import std.stdio;

class A {
  string x () { return "A"; };
}

class B : A {
  override string x () { return "B"; };
}

class C : A {
  string x = "C"; // should this be illegal?
}

void main () {
  A o1 = new B();
  writeln(o1.x); // B

  A o2 = new C();
  writeln(o2.x); // A
}
Feb 22 2014
next sibling parent simendsjo <simendsjo gmail.com> writes:
On 02/22/2014 06:21 PM, luka8088 wrote:
 import std.stdio;

 class A {
    string x () { return "A"; };
 }

 class B : A {
    override string x () { return "B"; };
 }

 class C : A {
    string x = "C"; // should this be illegal?
 }

 void main () {
    A o1 = new B();
    writeln(o1.x); // B

    A o2 = new C();
    writeln(o2.x); // A
 }
Just an addition. The following line shows the problem: writeln((cast(C)o2).x); // C
Feb 22 2014
prev sibling next sibling parent reply =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 02/22/2014 09:21 AM, luka8088 wrote:> It seems to me that the 
following code should be illegal, but I am
 uncertain of it so I am posting here for a confirmation before I post it
 on bug tracker:


 http://dpaste.dzfl.pl/dae728734cc6


 import std.stdio;

 class A {
    string x () { return "A"; };
 }

 class B : A {
    override string x () { return "B"; };
 }

 class C : A {
    string x = "C"; // should this be illegal?
 }

 void main () {
    A o1 = new B();
    writeln(o1.x); // B

    A o2 = new C();
    writeln(o2.x); // A
 }
The code uses the two objects through the A interface and x() is a virtual function on that interface. When the C interface is used then we get C.x, which happens to be hiding the x() function of the base class. It looks normal to me. Ali
Feb 22 2014
next sibling parent reply "Maxim Fomin" <maxim maxim-fomin.ru> writes:
On Saturday, 22 February 2014 at 17:41:58 UTC, Ali Çehreli wrote:
 The code uses the two objects through the A interface and x() 
 is a virtual function on that interface.

 When the C interface is used then we get C.x, which happens to 
 be hiding the x() function of the base class.

 It looks normal to me.

 Ali
Spec is silent on this, so this is indeed a question. Actually A is not interface, so I don't understand why you mention it. And there is neither 'taking C interface' because static type is A, so A function is called, neither it hides function of the base class because it is base class function which is called. I don't understand you completely. AFAIK this feature exists for many years, at least 3, possibly roots to D1. What happens is follows: since there is no function, base class virtual is not replaced, so virtual table of C looks like A, so A member function is called. If example is modified, then import std.stdio; class A { //string x () { return "A"; }; string x = "A"; } class B : A { //override string x () { return "B"; }; string x = "B"; } class C : A { //string x = "C"; // should this be illegal? string x () { return "C"; } } void main () { A o1 = new B(); A o2 = new C(); writeln(o1.x); // A writeln(o2.x); //A } so it appears that data member have priority over function. Probably this should be filed as a spec or compiler bug.
Feb 22 2014
parent reply =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 02/22/2014 10:00 AM, Maxim Fomin wrote:

 On Saturday, 22 February 2014 at 17:41:58 UTC, Ali Çehreli wrote:
 The code uses the two objects through the A interface and x() is a
 virtual function on that interface.

 When the C interface is used then we get C.x, which happens to be
 hiding the x() function of the base class.
Sorry. I meant "If the C interface is used", not "When the".
 It looks normal to me.

 Ali
Spec is silent on this, so this is indeed a question. Actually A is not interface, so I don't understand why you mention it.
I did not mean D's feature 'interface'. The code explicitly specifies the objects as As, comitting to A's class interface. (As in, every used defined type defines an interface.)
 And there is neither 'taking C interface' because static type is A, so A
 function is called, neither it hides function of the base class because
 it is base class function which is called. I don't understand you
 completely.
I agree with all of that.
 since there is no function, base class
 virtual is not replaced, so virtual table of C looks like A, so A member
 function is called.
Exactly. Otherwise, when faced with such a situation the compiler would have to synthesize a virtual function for C's virtual table. string x() { return member_x; }
 so it appears that data member have priority over function.
It looks like name hiding, which I am familiar from C++. Name hiding does not differentiate between functions and variables. Ali
Feb 22 2014
parent simendsjo <simendsjo gmail.com> writes:
On 02/22/2014 09:43 PM, Ali Çehreli wrote:
 It looks like name hiding, which I am familiar from C++. Name hiding
 does not differentiate between functions and variables.

 Ali
The problem is that hiding a name *is* a problem. When you are hiding a name, then a class would no longer behave as you would expect. It breaks LSP in a pretty awful way, and suddenly the *type* of a symbol changes based on what superclass you happened to use for a reference. class A { void f() {} } class B : A { int f; } A b = new B(); writeln(typeof(b.f).stringof); // void() B veryB = cast(B)b; writeln(typeof(veryB.f).stringof); // int Now suddenly, it's very important to use B as the type for a reference. This is very dangerous behavior in my opinion, and I think I've only used it *once* in C# - which requires you to be explicit about it - and I still feel dirty. Now what if a superclass implements a symbol that you are using in a subclass? I say we force it to be explicit as we finally did with `override`, which is shows some of the same issues, although not nearly as dangerous and hidden. I think member hiding is nearly always a bug, and we should be very explicit about it.
Feb 22 2014
prev sibling parent reply "Nynn007" <fopa wanadoo.fr> writes:
 The code uses the two objects through the A interface and x() 
 is a virtual function on that interface.
[...]
 Ali
The book "Programming in D" (r651) says in chapter "57.7 Using the subclass in place of the superclass", in the example about Clock and AlarmClock : void use(Clock clock) { ... } (sic) "In other words, although use() uses the object as a Clock, the actual object may be an inherited type that behaves in its own special way". Should'nt we understand that the first object is a B, the second object is a C and then should both behave like a B and a C, not like two A ?
Feb 22 2014
parent =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 02/22/2014 12:06 PM, Nynn007 wrote:

 The code uses the two objects through the A interface and x() is a
 virtual function on that interface.
[...]
 Ali
I agree. :)
 The book "Programming in D" (r651) says in chapter "57.7 Using the
 subclass in place of the superclass", in the example about Clock and
 AlarmClock :

      void use(Clock clock) { ... }
 (sic) "In other words, although use() uses the object as a Clock, the
 actual object may be an inherited type that behaves in its own special
 way".
I obviously agree with that as well. :)
 Should'nt we understand that the first object is a B, the second object
 is a C and then should both behave like a B and a C, not like two A ?
You are correct. What I meant above is that the code uses the two object as two As, which involves the "interface" of A. The behaviors may be different. Ali
Feb 22 2014
prev sibling parent reply "Francesco Cattoglio" <francesco.cattoglio gmail.com> writes:
On Saturday, 22 February 2014 at 17:21:50 UTC, luka8088 wrote:
 It seems to me that the following code should be illegal, but I 
 am
 uncertain of it so I am posting here for a confirmation before 
 I post it
 on bug tracker:
[snip] Nice find. I guess we could add this to the list of "ugly code caused by calling functions without parenthesis. If parenthesis were not optional, I don't think that the code would behave in such a horrible way, right?
Feb 22 2014
parent reply simendsjo <simendsjo gmail.com> writes:
On 02/22/2014 11:33 PM, Francesco Cattoglio wrote:
 On Saturday, 22 February 2014 at 17:21:50 UTC, luka8088 wrote:
 It seems to me that the following code should be illegal, but I am
 uncertain of it so I am posting here for a confirmation before I post it
 on bug tracker:
[snip] Nice find. I guess we could add this to the list of "ugly code caused by calling functions without parenthesis. If parenthesis were not optional, I don't think that the code would behave in such a horrible way, right?
The problem isn't about optional parenthesis or properties. It's the fact that you can redefine a symbol to be something entierly different, and that this difference will only be seen if you are looking at the symbol through the "correct" type. C# also allows has this feature, but you have to state explicitly that you are hiding an existing symbol. D as a safe-by-default language should also require this.
Feb 22 2014
next sibling parent "francesco cattoglio" <francesco.cattoglio gmail.com> writes:
On Saturday, 22 February 2014 at 22:42:24 UTC, simendsjo wrote:
 The problem isn't about optional parenthesis or properties. 
 It's the fact that
 you can redefine a symbol to be something entierly different, 
 and that this
 difference will only be seen if you are looking at the symbol 
 through the
 "correct" type.
You are right. I thought that if we had forced parenthesis, the compiler would at least be able to understand what symbol you were referring to, but this is actually not the case.
Feb 22 2014
prev sibling parent reply luka8088 <luka8088 owave.net> writes:
On 22.2.2014. 23:43, simendsjo wrote:
 On 02/22/2014 11:33 PM, Francesco Cattoglio wrote:
 The problem isn't about optional parenthesis or properties. It's the
 fact that
 you can redefine a symbol to be something entierly different, and that this
 difference will only be seen if you are looking at the symbol through the
 "correct" type.
That is exactly what I wanted to point out! I reduced the example code to make that point more obvious: import std.stdio; class A { string x = "A"; } class B : A { // should it be legal to hide a symbol and redefine it to a new type? int x = 2; } //static assert (is(typeof(A.x) == typeof(B.x))); //Error: static assert (is(string == int)) is false void main () { B o = new B(); writeln((cast (A)o).x); // A writeln((cast (B)o).x); // 2 } http://dpaste.dzfl.pl/6ae4ac5de1bc
Feb 23 2014
parent luka8088 <luka8088 owave.net> writes:
On 23.2.2014. 13:51, luka8088 wrote:
 That is exactly what I wanted to point out! I reduced the example code
 to make that point more obvious:
Just one note. I know how this works currently in D and how it works in C++. What I am asking here is not how it works but is this a good idea as I just see it as a possible source of bugs without any benefit.
Feb 23 2014