www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Passing a derived class where base class is defined as ref parameter

reply chopchop <bullshtttt gmail.com> writes:
Hi guys,

below a small piece of code, which does not compile because "b" 
of type B can not be passed as ref parameter to incr(ref A a).

If I remove the ref, it works as expected, that is to say I can 
give a derived class as parameter. I have an idea why it does not 
work, but I think a c++ reference would work, ie incr(A& console) 
would accept a B as parameter. What the logic here?

Thanks

```
void main()
{
     import std.stdio: write, writeln, writef, writefln;

     class A
     {
     	int i = 2;
     }

     class B : A
     {
     	int j= 3;
     }

     void incr(ref A a)
     {
     	writeln(a.i);
     }

     B b = new B();
     incr(b);

}
```
Dec 13 2021
next sibling parent reply Adam D Ruppe <destructionator gmail.com> writes:
On Monday, 13 December 2021 at 22:06:45 UTC, chopchop wrote:
 If I remove the ref, it works as expected, that is to say I can 
 give a derived class as parameter.
Why are you using the ref to begin with?
 What the logic here?
Consider this: class C : A {} void incr(ref A a) { a = new C; } B b = new B; incr(b); // oops b now got rebound to a C instead of to a B, which breaks everything
Dec 13 2021
next sibling parent reply Tejas <notrealemail gmail.com> writes:
On Monday, 13 December 2021 at 22:30:59 UTC, Adam D Ruppe wrote:
 On Monday, 13 December 2021 at 22:06:45 UTC, chopchop wrote:
 If I remove the ref, it works as expected, that is to say I 
 can give a derived class as parameter.
Why are you using the ref to begin with?
 What the logic here?
Consider this: class C : A {} void incr(ref A a) { a = new C; } B b = new B; incr(b); // oops b now got rebound to a C instead of to a B, which breaks everything
But `B` is not a child of `A`, why should it be accepted in a function that accepts `A` as a parameter? It's not implicitly convertible to `A`
Dec 13 2021
parent chopchop <bullshiiii gmail.com> writes:
On Tuesday, 14 December 2021 at 05:38:17 UTC, Tejas wrote:
 On Monday, 13 December 2021 at 22:30:59 UTC, Adam D Ruppe wrote:
 On Monday, 13 December 2021 at 22:06:45 UTC, chopchop wrote:
 If I remove the ref, it works as expected, that is to say I 
 can give a derived class as parameter.
Why are you using the ref to begin with?
 What the logic here?
Consider this: class C : A {} void incr(ref A a) { a = new C; } B b = new B; incr(b); // oops b now got rebound to a C instead of to a B, which breaks everything
But `B` is not a child of `A`, why should it be accepted in a function that accepts `A` as a parameter? It's not implicitly convertible to `A`
Tejas, I think you should not read Adam's example as standalone, obviously he is implicitly reusing the definition of B in my first post, so B is indeed a child of A.
Dec 14 2021
prev sibling parent reply chopchop <bullshiiii gmail.com> writes:
On Monday, 13 December 2021 at 22:30:59 UTC, Adam D Ruppe wrote:
 On Monday, 13 December 2021 at 22:06:45 UTC, chopchop wrote:
 If I remove the ref, it works as expected, that is to say I 
 can give a derived class as parameter.
Why are you using the ref to begin with?
 What the logic here?
Consider this: class C : A {} void incr(ref A a) { a = new C; } B b = new B; incr(b); // oops b now got rebound to a C instead of to a B, which breaks everything
Hi Adam, I am using the "ref" here (I put tinyurl to avoid over-referencing the post instead of the github page itself): https://tinyurl.com/bdddkmub I would like to be able to pass any kind of console to updateFoodToken ( Console c ), ie either a winconsole or a nixconsole, which are derived from Console. I mean I probably have a cognitive bias of being a c++ dev. Let me explain. If I was coding in C++ I would pass "A&". Not "const A&", because I do two function calls with console - for example console.gotoxy(...) - but those 2 members functions are not suffixed "const". Since it is work in progress, I dont even know if I am going to modify those functions at the end or do something else with console... But well, what do you guys think? The documentation does not even say if an "in" param is passed by value or reference ("may be passed by reference" it says). I don't really see how a copy of a console would work... Kind of eery in my minde. Adam, I am actually using your code :) You and anyone else are welcome to review / send some Pull Request, deal with the console code, comment, etc
Dec 14 2021
next sibling parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Tuesday, 14 December 2021 at 17:20:18 UTC, chopchop wrote:

 I am using the "ref" here (I put tinyurl to avoid 
 over-referencing the post instead of the github page itself):
 https://tinyurl.com/bdddkmub

 I would like to be able to pass any kind of console to 
 updateFoodToken ( Console c ), ie either a winconsole or a 
 nixconsole, which are derived from Console.

 I mean I probably have a cognitive bias of being a c++ dev. Let 
 me explain. If I was coding in C++ I would pass "A&". Not 
 "const A&", because I do two function calls with console - for 
 example console.gotoxy(...) - but those 2 members functions are 
 not suffixed "const". Since it is work in progress, I dont even 
 know if I am going to modify those functions at the end or do 
 something else with console...
Simple. Don't take a `ref`. Just take a `Console`. Classes in D are reference types, you're not making a copy as you would in C++ if you were to write `updateFoodToken(Console c)`.
Dec 14 2021
parent chopchop <sautooauuaeuahusahouh gmail.com> writes:
On Tuesday, 14 December 2021 at 17:27:13 UTC, Stanislav Blinov 
wrote:
 Simple. Don't take a `ref`. Just take a `Console`. Classes in D 
 are reference types, you're not making a copy as you would in 
 C++ if you were to write `updateFoodToken(Console c)`.
Ah, ok, Reference types! That's great. Definitely my C++ bias working against me, good you read my mind :)
Dec 14 2021
prev sibling parent reply Adam D Ruppe <destructionator gmail.com> writes:
On Tuesday, 14 December 2021 at 17:20:18 UTC, chopchop wrote:
 I am using the "ref" here (I put tinyurl to avoid 
 over-referencing the post instead of the github page itself):
 https://tinyurl.com/bdddkmub
yeah D classes are automatically ref unlike c++ so you don't need the second level of it most often. Using plain `Object` is what you want most the time, maybe `in Object` sometimes. But `ref Object` or `Object*` both very rare in D.
 Adam, I am actually using your code :)
nice. You might want to look at my terminal.d (arsd-official:terminal on dub) too which has pretty comprehensive functionality for tons of things. Prolly more than you need looking at your interface tho but it includes the output functions then mouse and real time key inputs, get line input, even scrollable areas and more.
Dec 14 2021
parent chopchop <sautooauuaeuahusahouh gmail.com> writes:
On Tuesday, 14 December 2021 at 20:58:21 UTC, Adam D Ruppe wrote:
 nice. You might want to look at my terminal.d 
 (arsd-official:terminal on dub) too which has pretty 
 comprehensive functionality for tons of things. Prolly more 
 than you need looking at your interface tho but it includes the 
 output functions then mouse and real time key inputs, get line 
 input, even scrollable areas and more.
Always a pleasure to check your great libs, I will have a look and learn probably a lot. But I wont use an external lib because it's a demo where all the code must be under contract / covered, to meet some strict coding standard. So in worst case I copy the code and add unittest / contract / invariant, etc. But original authors are always quoted and licences respected ;)
Dec 14 2021
prev sibling parent Mike Parker <aldacron gmail.com> writes:
On Monday, 13 December 2021 at 22:06:45 UTC, chopchop wrote:

 If I remove the ref, it works as expected, that is to say I can 
 give a derived class as parameter. I have an idea why it does 
 not work, but I think a c++ reference would work, ie incr(A& 
 console) would accept a B as parameter. What the logic here?
TL:DR it's because there are two levels of indirection. What's happening here is that `ref A` in D *is not* equivalent to `A&` in C++. That's because D classes are reference types like Java classes, not value types like C++ classes. Your `b` is a handle to an instance, not an instance itself. It's more akin to `B*` in C++. So that means that your `ref A` is like `A**` in C++. And I believe you'll find that `B**` in C++ is not implicitly convertible to `A**`. Since `ref` in D is just a pointer under the hood, we can be more explicit like so: ```d void incr(A* a) { writeln(a.i); } B b = new B(); incr(&b); ``` In this case, compilation also fails. `B*` is not implicitly convertible to `A*`. Again, this is equivalent to `B**` and `A**` in C++. In this case, you can explicitly do the conversion with a cast: `incr(cast(A*)&b);` But consider what happens in this case: ```d void incr(A* a) { *a = new A; } B b = new B(); incr(cast(A*)&b); writeln(b.j); ``` Your `b` is no longer a `B`, but still thinks it is. This is my understanding of why implicit conversion is disallowed when multiple levels of indirection are involved.
Dec 13 2021