www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - std.typecons.Proxy + inheritance breaks cast'ing to inherited type

reply "Lukasz Wrzosek" <wrzoski gmail.com> writes:
Hello
I was just exploring possibility to mimic multiple inheritance 
from C++ (do not ask why, just for fun).
I've stumbled on below issue (let's say corner case) and most 
likely this is bug in implementation of template Proxy, isn't it ?


import std.typecons;
class IA {}
class IB {}
class C : IB {
   IA a;
   mixin Proxy!a;

   public this() {
     a = new IA;
   }
}

void main() {
   C c = new C;
   IA a = cast(IA)c;
   IB b_ok = c;
   IB b_not_ok = cast(IB)c;
   assert(c !is null);
   assert(a !is null);
   assert(b_ok !is null);
   assert(b_not_ok !is null); //fails
}
Mar 16 2015
parent reply "John Colvin" <john.loughran.colvin gmail.com> writes:
On Monday, 16 March 2015 at 09:03:18 UTC, Lukasz Wrzosek wrote:
 Hello
 I was just exploring possibility to mimic multiple inheritance 
 from C++ (do not ask why, just for fun).
 I've stumbled on below issue (let's say corner case) and most 
 likely this is bug in implementation of template Proxy, isn't 
 it ?


 import std.typecons;
 class IA {}
 class IB {}
 class C : IB {
   IA a;
   mixin Proxy!a;

   public this() {
     a = new IA;
   }
 }

 void main() {
   C c = new C;
   IA a = cast(IA)c;
   IB b_ok = c;
   IB b_not_ok = cast(IB)c;
   assert(c !is null);
   assert(a !is null);
   assert(b_ok !is null);
   assert(b_not_ok !is null); //fails
 }
What behaviour would you expect if both IA and C inherited from IB?
Mar 16 2015
parent reply "Lukasz Wrzosek" <wrzoski gmail.com> writes:
On Monday, 16 March 2015 at 12:03:12 UTC, John Colvin wrote:
 What behaviour would you expect if both IA and C inherited from 
 IB?
This case should assert at compile time. But my example shows that case with implicit case is working, but the explicit cast is not. That seems to be incorrect IMO.
Mar 16 2015
parent reply =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 03/16/2015 08:28 AM, Lukasz Wrzosek wrote:
 On Monday, 16 March 2015 at 12:03:12 UTC, John Colvin wrote:
 What behaviour would you expect if both IA and C inherited from IB?
This case should assert at compile time. But my example shows that case with implicit case is working, but the explicit cast is not. That seems to be incorrect IMO.
Yes, it's a bug because Proxy mixes in an opCast that always casts to T (IA in your case): auto ref opCast(T, this X)() { return cast(T)a; } Please file an issue for Phobos at https://issues.dlang.org/enter_bug.cgi Thank you, Ali
Mar 16 2015
parent reply "Lukasz Wrzosek" <wrzoski gmail.com> writes:
Bug reported as
https://issues.dlang.org/show_bug.cgi?id=14298
Mar 16 2015
parent reply =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 03/16/2015 04:59 PM, Lukasz Wrzosek wrote:
 Bug reported as
 https://issues.dlang.org/show_bug.cgi?id=14298
Thanks... I have carried the discussion over to the main newsgroup: http://forum.dlang.org/thread/me8e0a$1kp6$1 digitalmars.com As I mention there, there is a workaround: Add a catch-all opCast to the class in question, which can simply forward to the all-powerful std.conv.to: T opCast(T)() { import std.conv; return this.to!T; } Now it compiles and works as expected. Ali
Mar 16 2015
parent reply "Meta" <jared771 gmail.com> writes:
On Tuesday, 17 March 2015 at 05:32:49 UTC, Ali Çehreli wrote:
 On 03/16/2015 04:59 PM, Lukasz Wrzosek wrote:
 Bug reported as
 https://issues.dlang.org/show_bug.cgi?id=14298
Thanks... I have carried the discussion over to the main newsgroup: http://forum.dlang.org/thread/me8e0a$1kp6$1 digitalmars.com As I mention there, there is a workaround: Add a catch-all opCast to the class in question, which can simply forward to the all-powerful std.conv.to: T opCast(T)() { import std.conv; return this.to!T; } Now it compiles and works as expected. Ali
That will throw an exception if the conversion can't be done instead of just returning null, won't it? Why can't you do this instead? t opCast(t)() if (is(typeof(cast(T)this))) { return cast(T)this; }
Mar 17 2015
parent reply =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 03/17/2015 12:14 AM, Meta wrote:

 On Tuesday, 17 March 2015 at 05:32:49 UTC, Ali Çehreli wrote:
 On 03/16/2015 04:59 PM, Lukasz Wrzosek wrote:
 Bug reported as
 https://issues.dlang.org/show_bug.cgi?id=14298
Thanks... I have carried the discussion over to the main newsgroup: http://forum.dlang.org/thread/me8e0a$1kp6$1 digitalmars.com As I mention there, there is a workaround: Add a catch-all opCast to the class in question, which can simply forward to the all-powerful std.conv.to: T opCast(T)() { import std.conv; return this.to!T; } Now it compiles and works as expected. Ali
That will throw an exception if the conversion can't be done instead of just returning null, won't it?
You are right. I did not think it through but we can add template constraints to make it impossible to throw. (Still, we shouldn't need to do that for a cast to base types.)
 Why can't you do this instead?

 t opCast(t)()
 if (is(typeof(cast(T)this)))
 {
      return cast(T)this;
 }
That has the same problem: 'cast(T)this' happens to be an explicit cast, which is disabled by the opCast overload for int. Ali
Mar 17 2015
parent "anonymous" <anonymous example.com> writes:
On Tuesday, 17 March 2015 at 07:56:19 UTC, Ali Çehreli wrote:
 Why can't you do this instead?

 t opCast(t)()
 if (is(typeof(cast(T)this)))
 {
      return cast(T)this;
 }
That has the same problem: 'cast(T)this' happens to be an explicit cast, which is disabled by the opCast overload for int. Ali
For classes, `T opCast(T)() {return cast(T) super;}` may be viable. For structs, it's seems more difficult to me. `return this.to!T;` may try a cast(T), so it has the same problem as `return cast(T) this;`: infinite recursion. Example: ---- struct StdConvTo { int* p; int opCast(T : int)() {return 42;} T opCast(T)() {import std.conv; return this.to!T;} } unittest { assert((cast(int) StdConvTo()) == 42); /* ok */ auto i = cast(immutable StdConvTo) StdConvTo(); /* infinite recursion */ } ---- This applies to classes, too. A reinterpret cast would allow casts that should be rejected: ---- struct Reinterpet { int* p; int opCast(T : int)() {return 42;} T opCast(T)() {return * cast(T*) &this;} } unittest { assert((cast(int) Reinterpet()) == 42); /* ok */ auto s = cast(float) Reinterpet(); /* compiles, but shouldn't */ } ---- Here's something that might work. Construct a "tuple" of the struct's fields (i.e. same data but no opCast). Then cast that to T. ---- struct TupleOf { int* p; int opCast(T : int)() {return 42;} T opCast(T)() { static struct Tuple(S) {S stuff;} static Tuple!S tuple(S)(S stuff) {return Tuple!S(stuff);} return cast(T) tuple(this.tupleof); } } unittest { assert((cast(int) TupleOf()) == 42); /* ok */ auto i = cast(immutable TupleOf) TupleOf(); /* ok */ static assert(!__traits(compiles, cast(float) TupleOf())); /* ok */ } ---- This is probably not perfect either.
Mar 17 2015