Thread overview
opCast in class prevents destroy
Mar 01, 2022
cc
Mar 01, 2022
Mike Parker
Mar 01, 2022
Mike Parker
Mar 01, 2022
bauss
Mar 01, 2022
Mike Parker
Mar 01, 2022
vit
Mar 01, 2022
bauss
Mar 01, 2022
Paul Backus
Mar 01, 2022
Paul Backus
March 01, 2022
struct A {}
class B {
	A opCast(T : A)() {
		return A();
	}
}
void main() {
	auto b = new B();
	destroy(b);
}

fails with

dmd2\windows\bin\..\..\src\druntime\import\object.d(4209): Error: template instance `opCast!(void*)` does not match template declaration `opCast(T : A)()`
main.d(9): Error: template instance `object.destroy!(true, B)` error instantiating

Looks like a similar bug has been reported: https://issues.dlang.org/show_bug.cgi?id=22635

As a workaround, adding an additional opCast:

class B {
	A opCast(T : A)() {
		return A();
	}
	auto opCast(T)() {
		return cast(T)super;
	}
}

SEEMS to work. Is that safe? Or are consequences not what I'm intending?

March 01, 2022

On Tuesday, 1 March 2022 at 04:29:56 UTC, cc wrote:

>
struct A {}
class B {
	A opCast(T : A)() {
		return A();
	}
}
void main() {
	auto b = new B();
	destroy(b);
}

fails with

dmd2\windows\bin\..\..\src\druntime\import\object.d(4209): Error: template instance `opCast!(void*)` does not match template declaration `opCast(T : A)()`
main.d(9): Error: template instance `object.destroy!(true, B)` error instantiating

Looks like a similar bug has been reported: https://issues.dlang.org/show_bug.cgi?id=22635

Is it a bug? It's not documented in the opCast documentation, but it looks like when you define an opCast it completely replaces the default behavior, i.e., whatever type you define as the target type becomes the only type to which you can attempt to cast.

It makes sense to me, and I would say the bug is that it's not documented.

>

As a workaround, adding an additional opCast:

class B {
	A opCast(T : A)() {
		return A();
	}
	auto opCast(T)() {
		return cast(T)super;
	}
}

SEEMS to work. Is that safe? Or are consequences not what I'm intending?

So what you've done here is specialized on anything convertible to A and then reenabled casts to all other types, i.e., the default behavior, but with a special exception for T:A.

You could also specialize on void*, as that's the type that was failing to compile. Then you're restricted to void* and anything convertible to A.

March 01, 2022

On Tuesday, 1 March 2022 at 04:59:49 UTC, Mike Parker wrote:

>

You could also specialize on void*, as that's the type that was failing to compile

I meant "instead", not also.

March 01, 2022

On Tuesday, 1 March 2022 at 04:59:49 UTC, Mike Parker wrote:

>

It makes sense to me, and I would say the bug is that it's not documented.

Personally it doesn't make sense to me. I don't think it should override default behaviors, but just add onto it, so you can add an additional cast.

Right now if you want to add an additional cast then you have to implement ALL the default behaviors and then add your custom cast.

That doesn't seem correct to me at least.

That's not how the behavior is in most other languages either.

March 01, 2022

On Tuesday, 1 March 2022 at 07:16:11 UTC, bauss wrote:

>

Right now if you want to add an additional cast then you have to implement ALL the default behaviors and then add your custom cast.

It's two template functions like the OP used: one for T to catch everything, and one specialization.

>

That doesn't seem correct to me at least.

Depends on your perspective I guess. For the inverse, when you want to allow only one kind of cast and prevent everything else, you only have to implement one template right now. If that were not the case, then you'd have to implement an additional catch-all template that bombs out with a static assert.

So either way makes sense, IMO. Though I totally understand how the current behavior can be a surprise when people expect it to behave like, e.g., C++.

But D is not C++. So is opCast intended to expand the list of target types (like C++), or is it intended to define it? The spec says, "To define how one type can be cast to another", which doesn't really answer the question.

March 01, 2022

On Tuesday, 1 March 2022 at 08:16:13 UTC, Mike Parker wrote:

>

On Tuesday, 1 March 2022 at 07:16:11 UTC, bauss wrote:

>

Right now if you want to add an additional cast then you have to implement ALL the default behaviors and then add your custom cast.

It's two template functions like the OP used: one for T to catch everything, and one specialization.

>

That doesn't seem correct to me at least.

Depends on your perspective I guess. For the inverse, when you want to allow only one kind of cast and prevent everything else, you only have to implement one template right now. If that were not the case, then you'd have to implement an additional catch-all template that bombs out with a static assert.

So either way makes sense, IMO. Though I totally understand how the current behavior can be a surprise when people expect it to behave like, e.g., C++.

But D is not C++. So is opCast intended to expand the list of target types (like C++), or is it intended to define it? The spec says, "To define how one type can be cast to another", which doesn't really answer the question.

Now is possible this:

import std.stdio;

    struct Foo{
        int i;

        this(int i)@safe{
        	this.i = i;
            writeln("ctor(", i, "): ", cast(void*)&this);
        }

        Foo opCast(T, this This)()@safe
        if(is(immutable T == immutable This)){
            return Foo(2);
        }

        ~this()@safe{
        	writeln("dtor(", i, "): ", cast(void*)&this);
        }
    }

    struct Bar{
        const Foo foo;

        this(int i)@safe{
        	this.foo = Foo(i);
        }
    }

    void main()@safe{
        Bar bar =  Bar(1);
    }

Result:

ctor(1): 7FFE0D5A94A8  //dtor for Foo(1) is never called.
ctor(2): 7FFE0D5A9410
dtor(2): 7FFE0D5A9470
dtor(2): 7FFE0D5A9470  //dtor for Foo(2) is called twice.
March 01, 2022

On Tuesday, 1 March 2022 at 08:16:13 UTC, Mike Parker wrote:

>

On Tuesday, 1 March 2022 at 07:16:11 UTC, bauss wrote:

>

Right now if you want to add an additional cast then you have to implement ALL the default behaviors and then add your custom cast.

It's two template functions like the OP used: one for T to catch everything, and one specialization.

>

That doesn't seem correct to me at least.

Depends on your perspective I guess. For the inverse, when you want to allow only one kind of cast and prevent everything else, you only have to implement one template right now. If that were not the case, then you'd have to implement an additional catch-all template that bombs out with a static assert.

So either way makes sense, IMO. Though I totally understand how the current behavior can be a surprise when people expect it to behave like, e.g., C++.

But D is not C++. So is opCast intended to expand the list of target types (like C++), or is it intended to define it? The spec says, "To define how one type can be cast to another", which doesn't really answer the question.

Yes of course it's a matter of perspective.

I think the solution would be to have two functions for opCast, maybe something like opAdditionalCast, idk, not to break current behavior I guess.

March 01, 2022

On Tuesday, 1 March 2022 at 04:59:49 UTC, Mike Parker wrote:

>

On Tuesday, 1 March 2022 at 04:29:56 UTC, cc wrote:

>
struct A {}
class B {
	A opCast(T : A)() {
		return A();
	}
}
void main() {
	auto b = new B();
	destroy(b);
}

fails with

dmd2\windows\bin\..\..\src\druntime\import\object.d(4209): Error: template instance `opCast!(void*)` does not match template declaration `opCast(T : A)()`
main.d(9): Error: template instance `object.destroy!(true, B)` error instantiating

Looks like a similar bug has been reported: https://issues.dlang.org/show_bug.cgi?id=22635

Is it a bug? It's not documented in the opCast documentation, but it looks like when you define an opCast it completely replaces the default behavior, i.e., whatever type you define as the target type becomes the only type to which you can attempt to cast.

It's a bug in druntime. destroy needs to reinterpret the class reference as a void* to pass it to rt_finalize:

https://github.com/dlang/druntime/blob/v2.098.1/src/object.d#L4209

However, cast(void*) is not the correct way to do this, because it fails in the presence of opCast.

March 01, 2022

On Tuesday, 1 March 2022 at 16:40:50 UTC, Paul Backus wrote:

>

It's a bug in druntime. destroy needs to reinterpret the class reference as a void* to pass it to rt_finalize:

https://github.com/dlang/druntime/blob/v2.098.1/src/object.d#L4209

However, cast(void*) is not the correct way to do this, because it fails in the presence of opCast.

https://github.com/dlang/druntime/pull/3766