Thread overview
Why is this allowed? Inheritance variable shadowing
Aug 13, 2019
Chris Katko
Aug 13, 2019
Mike Parker
Aug 13, 2019
a11e99z
Aug 13, 2019
a11e99z
May 26, 2021
JN
May 26, 2021
sighoya
May 26, 2021
mw
August 13, 2019
You can drop this straight into run.dlang.io:

import std.stdio;

class base{ float x=1;}
class child : base {float x=2;} //shadows base variable!

void main()
{

    base []array;
    child c = new child;
    array ~= c;

    writeln(c.x); //=2
    writeln(array[0].x); //=1  //uses BASE's interface, yes,
    //but why does the CHILD instance one exist at all?
}

It appears to be legal C++ as well but I can't imagine a situation where you'd want to allow the HUGE risk of shadowing/aliasing variables in an child class. Why is inheritance shadowing allowed? Especially when in D you have to explicitly "override" existing _methods_ but not fields/variables?

To quote a Stack Overflow comment on C++ having this "It's not a compile error, but it's certainly a design one." Is this allowed just because "C++ does it" or because it has some sort of real world use that justifies the risk?

Personally, I'd love a compile-time warning that I could turn on that flags this situation.

Thanks for your help,
--Chris
August 13, 2019
On Tuesday, 13 August 2019 at 04:40:53 UTC, Chris Katko wrote:
> You can drop this straight into run.dlang.io:
>
> import std.stdio;
>
> class base{ float x=1;}
> class child : base {float x=2;} //shadows base variable!
>
> void main()
> {
>
>     base []array;
>     child c = new child;
>     array ~= c;
>
>     writeln(c.x); //=2
>     writeln(array[0].x); //=1  //uses BASE's interface, yes,
>     //but why does the CHILD instance one exist at all?
> }
>
> It appears to be legal C++ as well but I can't imagine a situation where you'd want to allow the HUGE risk of shadowing/aliasing variables in an child class. Why is inheritance shadowing allowed? Especially when in D you have to explicitly "override" existing _methods_ but not fields/variables?
>
> To quote a Stack Overflow comment on C++ having this "It's not a compile error, but it's certainly a design one." Is this allowed just because "C++ does it" or because it has some sort of real world use that justifies the risk?
>
> Personally, I'd love a compile-time warning that I could turn on that flags this situation.
>
> Thanks for your help,
> --Chris

I don't know if I'd call that shadowing. This is how it works in Java, too. There's no such thing as a vtable for member variables -- each class gets its own set and they don't conflict. The only time it could be really be called shadowing is when the base class member is protected, as then it's accessible in the subclass scope.

Also, it's not the same thing as overriding. Overriding means that when you call base.foo(), you get sub.foo()'s implementation. But when you access base.var, you get base.var and not sub.var.

I would find it extremely annoying if it worked the way you're expecting it to.
August 13, 2019
On Tuesday, 13 August 2019 at 05:57:23 UTC, Mike Parker wrote:
> On Tuesday, 13 August 2019 at 04:40:53 UTC, Chris Katko wrote:

> I don't know if I'd call that shadowing. This is how it works in Java, too. There's no such thing as a vtable for member variables -- each class gets its own set and they don't conflict. The only time it could be really be called shadowing is when the base class member is protected, as then it's accessible in the subclass scope.
>
> Also, it's not the same thing as overriding. Overriding means that when you call base.foo(), you get sub.foo()'s implementation. But when you access base.var, you get base.var and not sub.var.
>
> I would find it extremely annoying if it worked the way you're expecting it to.

C# results:
> main.cs(8,14): warning CS0108: `B.x' hides inherited member `A.x'. Use the new keyword if hiding was intended
> main.cs(4,14): (Location of the symbol related to previous warning)
> Compilation succeeded - 1 warning(s)
> mono main.exe
> 1
> 2

with "new" keyword that is used to hide a method, property, indexer, or event of the base class into the derived class.
> class B : A {
>   public new int x = 2;
>   // I tell "I want hiding. Ensure "x exists in parent"" explicitly
>   // almost same meaning as "override"
> }

OT:
and again how to easy to google info about error/warning just with one word "CS0108"
August 13, 2019
On Tuesday, 13 August 2019 at 06:39:24 UTC, a11e99z wrote:
> On Tuesday, 13 August 2019 at 05:57:23 UTC, Mike Parker wrote:
>> On Tuesday, 13 August 2019 at 04:40:53 UTC, Chris Katko wrote:
>
> OT:
> and again how to easy to google info about error/warning just with one word "CS0108"

D can use attrs for such things.

OT:
1) need to add compiler attrs that speaks with compiler - they are change code generation, compiler passes or something. like pragmas do.
2) need to separate all attrs in a separate construction like C# do
> [inline, nextOne, returns: someAttrToReturnValue]
> int meth( [argAttrCanBeToo] int x ) { }
or
> [hide]
> public int x = 2;
cuz its visually separated and easy to skip it with eye when u reading/reviewing code.

it can be mess for now:
> pure @trusted int meth( int x ) @nogc nothrow { return 5; }
NB all of this attrs is compiler attrs not user, they changes compilation.
- no possibility add attr to args or returns
- some attrs with "@" and some don't
- its hard to read when D adds 2-3 attrs more for next 5-10 years

my wishlist of new compilation attrs:
- [hiding] for subj
- [offset(N)] for explicit struct alignment without mess with unions/align(4) cuz sometimes I know exactly offset for field and I can point it with no side effects calcs adding pads, unions and etc
- [inline(bool)] instead of pragma( inline, true ) that looks like compiler attr but another way
- [deprecated(text)]
- [nodiscard] cannot discard return value
- etc
May 26, 2021
On Tuesday, 13 August 2019 at 04:40:53 UTC, Chris Katko wrote:
> You can drop this straight into run.dlang.io:
>
> import std.stdio;
>
> class base{ float x=1;}
> class child : base {float x=2;} //shadows base variable!
>
> void main()
> {
>
>     base []array;
>     child c = new child;
>     array ~= c;
>
>     writeln(c.x); //=2
>     writeln(array[0].x); //=1  //uses BASE's interface, yes,
>     //but why does the CHILD instance one exist at all?
> }
>

Just got bitten by this. When copy pasting code of a bigger class, it's easy to miss the redefinition of variable.

Is there any viable usecase for this behavior? I am not buying the "C++ does it and it's legal there" argument. There's a reason most serious C++ projects use static analysis tools anyway. D should be better and protect against dangerous code by default. I think a warning in this case would be warranted.

May 26, 2021
On Wednesday, 26 May 2021 at 18:58:47 UTC, JN wrote:
> Is there any viable usecase for this behavior? I am not buying the "C++ does it and it's legal there" argument. There's a reason most serious C++ projects use static analysis tools anyway. D should be better and protect against dangerous code by default. I think a warning in this case would be warranted.

There are certainly many usecases fo static members, maybe that is why designers feel it should be allowed for instance members too?

I think this is a clear case of something that should produce a warning and provide a silencing annotation fo the cases where you really want it.


May 26, 2021
On Wednesday, 26 May 2021 at 18:58:47 UTC, JN wrote:
> I am not buying the "C++ does it and it's legal there" argument.

A point for it is the consistency with methods which also redefine super methods as default strategy.
The question is if the default strategy needs to be changed?
I wouldn't argue so as overriding super methods/fields as default is much more dangerous as it might destroy the super class's semantics.

What about explicitly tagging fields with override instead, then it would be a compile error if the base class hasn't the tagged fields.

May 26, 2021
On Wednesday, 26 May 2021 at 18:58:47 UTC, JN wrote:
> On Tuesday, 13 August 2019 at 04:40:53 UTC, Chris Katko wrote:
>> You can drop this straight into run.dlang.io:
>>
>> import std.stdio;
>>
>> class base{ float x=1;}
>> class child : base {float x=2;} //shadows base variable!
>>
>> void main()
>> {
>>
>>     base []array;
>>     child c = new child;
>>     array ~= c;
>>
>>     writeln(c.x); //=2
>>     writeln(array[0].x); //=1  //uses BASE's interface, yes,
>>     //but why does the CHILD instance one exist at all?
>> }
>>
>
> Just got bitten by this. When copy pasting code of a bigger class, it's easy to miss the redefinition of variable.
>
> Is there any viable usecase for this behavior? I am not buying the "C++ does it and it's legal there" argument. There's a reason most serious C++ projects use static analysis tools anyway. D should be better and protect against dangerous code by default. I think a warning in this case would be warranted.

Agree, at least a warning message, a PR someone?