www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - opCast in class prevents destroy

reply cc <cc nevernet.com> writes:
```d
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:
```d
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?
Feb 28 2022
parent reply Mike Parker <aldacron gmail.com> writes:
On Tuesday, 1 March 2022 at 04:29:56 UTC, cc wrote:
 ```d
 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:
 ```d
 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`.
Feb 28 2022
next sibling parent Mike Parker <aldacron gmail.com> writes:
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.
Feb 28 2022
prev sibling next sibling parent reply bauss <jj_1337 live.dk> writes:
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.
Feb 28 2022
parent reply Mike Parker <aldacron gmail.com> writes:
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.
Mar 01 2022
next sibling parent vit <vit vit.vit> writes:
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: ```d 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: ```d 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. ```
Mar 01 2022
prev sibling parent bauss <jj_1337 live.dk> writes:
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.
Mar 01 2022
prev sibling parent reply Paul Backus <snarwin gmail.com> writes:
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:
 ```d
 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`.
Mar 01 2022
parent Paul Backus <snarwin gmail.com> writes:
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
Mar 01 2022