Thread overview
std.typecons.Proxy + inheritance breaks cast'ing to inherited type
Mar 16, 2015
Lukasz Wrzosek
Mar 16, 2015
John Colvin
Mar 16, 2015
Lukasz Wrzosek
Mar 16, 2015
Ali Çehreli
Mar 16, 2015
Lukasz Wrzosek
Mar 17, 2015
Ali Çehreli
Mar 17, 2015
Meta
Mar 17, 2015
Ali Çehreli
Mar 17, 2015
anonymous
March 16, 2015
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
}
March 16, 2015
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?
March 16, 2015
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.
March 16, 2015
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

March 16, 2015
Bug reported as
https://issues.dlang.org/show_bug.cgi?id=14298
March 17, 2015
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

March 17, 2015
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;
}
March 17, 2015
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

March 17, 2015
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.