Bug 5747 – cannot cast away shared if opCast defined

Status
RESOLVED
Resolution
WORKSFORME
Severity
normal
Priority
P2
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2011-03-17T06:36:07Z
Last change time
2020-03-21T03:56:41Z
Assigned to
No Owner
Creator
Nick Treleaven

Comments

Comment #0 by nick — 2011-03-17T06:36:07Z
struct S { int opCast() {return cast(int)this;} } shared S s; void main() { auto u = cast(S)s; } shared.d(8): Error: function shared.S.opCast () is not callable using argument types () shared.d(8): Error: cannot implicitly convert expression (s.opCast()) of type int to S Also happens if opCast returns other types, e.g. void[], etc. Removing 'shared' compiles OK. Tested with dmd v2.051. This code adapted from a newsgroup post about casting away shared from BitArray: http://www.digitalmars.com/pnews/read.php?server=news.digitalmars.com&group=digitalmars.D&artnum=132066
Comment #1 by issues.dlang — 2011-09-26T00:27:57Z
It's not just a question of shared. If you declare opCast on a struct, you can cast to that _exact_ type (so S can be cast to S and const S can be cast to const S), but you can't cast cast to any other version of that type (so, S can't be cast to const S, and shared S can't be cast to S). As a workaround, you can declare an opCast which returns those types, but it shouldn't be necessary to declare an extra opCast to do what works when you haven't declared opCast.
Comment #2 by nick — 2011-10-07T05:10:12Z
It seems this might not be a dmd bug then, possibly a Phobos issue with shared BitArray. Sorry for the noise. Marked as invalid.
Comment #3 by issues.dlang — 2011-10-07T08:28:59Z
This is most definitely a dmd bug. Once you overload opCast, you can only cast that type to the types that you've overloaded to cast to with opCast and the _exact_ type that the variable already is. So, if it's S, you can cast to S but not const S, shared S, or immutable S. If it's shared S, then you can cast to shared S, but not S, shared S, or immutable S. I don't know why you would think that this is not a dmd bug. Your original example doesn't include BitArray at all.
Comment #4 by monarchdodra — 2013-11-03T08:31:45Z
Just hit this. IMO, this is a bug: The presence of an opCast should not prevent "qualification-cast", which don't actually change the type. In particular, the problem is that *even* trying to hand write it, it's not possible: //---- import std.traits; struct S { int* p; //prevent implicit cast T opCast(T:bool)() pure const { return false; } T opCast(T)() const if (is(Unqual!T == S)) { return this; //This fails return cast(T) this; //This overflows } } void main() { immutable(S) s; auto b = cast(S)(s); //How to do this? } //---- IMO, a cast to the *same* type (but different quals) should always be defined by the compiler, even if another opCast is defined, unless the use explicitly overrides *that* opCast.
Comment #5 by k.hara.pg — 2013-12-10T17:34:52Z
I'm still not sure about this bug, because special handling for the _qualifier-only-different cast_ could cause opposite problem in generic code. template X(T, U) { ... T foo() { ... } auto u = cast(U)foo(); // if U is different qualifier of T, it won't call opCast. // I don't think it is *always* valid behavior. It depends on the T's definition. } I posted a spin-off issue: Issue 11722 - Qualifier-only casts should not invoke opCast To me, special handing only for the qualifier casts is more acceptable than this. How about?
Comment #6 by samukha — 2013-12-11T09:16:24Z
(In reply to comment #4) > { > return this; //This fails > return cast(T) this; //This overflows > } That's expected. It is easy to break the recursion: struct S { int* p; //prevent implicit cast T opCast(T:bool)() pure const { return false; } T opCast(T)() const if (is(Unqual!T == S)) { static if (is(T == const)) return this; else return cast(T)this; } } I think the fix to issue 11722 has introduced an unnecessary limitation (provided other annoyances are removed, such as opCast not being called if types match exactly).
Comment #7 by monarchdodra — 2013-12-11T12:38:23Z
(In reply to comment #6) > (In reply to comment #4) > > > { > > return this; //This fails > > return cast(T) this; //This overflows > > } > > That's expected. It is easy to break the recursion: Your example snippet still overflows for me for the initial use case: void main() { immutable(S) s; auto b = cast(S)(s); //How to do this? } > I think the fix to issue 11722 has introduced an unnecessary limitation > (provided other annoyances are removed, such as opCast not being called if > types match exactly). *What* limitations? If we *really* wanted to overload a "qualifier cast", then arguably, we could add an enhancement request for something like: auto opCast(); //Removes qualifier cast auto opCast(const); //Adds const auto opCast(shared); //Adds shared But I really don't see how a *type* cast should interfere a *qualifier* cast.
Comment #8 by issues.dlang — 2013-12-11T22:19:54Z
Like monarchdodra, I question the wisdom in overloading the cast operator for type qualifiers. Maybe it's useful and needed, but 99.99% of the time, having to have overloads to restore qualifier casts on top of what opCast was overloaded for is extremely annoying. Why should the built-in casts suddenly stop working just because I added a cast to a completely different type? That seems like a horrible idea to me. Also, if the built-in casts are hidden, how are you supposed to even create overloads to put them back? For instance, to cast from mutable to immutable requires a built-in cast. You can't duplicate that on your own, which at present means that if you overload opCast, it becomes impossible to cast from mutable to immutable. The same goes with any other qualifier conversion that can't be done implicitly. If someone defines an overload for opCast which simply does a qualifier cast - e.g. const(typeof(this)) opCast(T) if(T == typeof(this)) {...} - then that should override the built-in cast, but if there is no overload for opCast which replaces a built-in cast, then the built-in cast should still be useable. That's the only way to do this that makes sense to me.
Comment #9 by samukha — 2013-12-12T14:08:45Z
(In reply to comment #7) > (In reply to comment #6) > > (In reply to comment #4) > > > > > { > > > return this; //This fails > > > return cast(T) this; //This overflows > > > } > > > > That's expected. It is easy to break the recursion: > > Your example snippet still overflows for me for the initial use case: It doesn't with dmd 2.064. But that's irrelevant. As I said, it is trivial to avoid recursion, contrary to your claim that it is impossible. > > void main() > { > immutable(S) s; > auto b = cast(S)(s); //How to do this? > } > > > I think the fix to issue 11722 has introduced an unnecessary limitation > > (provided other annoyances are removed, such as opCast not being called if > > types match exactly). > > *What* limitations? The limitation that I won't be able to intercept the qualifier-only cast down the road if the need emerges. The limitation that you've just unnecessarily introduced. > > If we *really* wanted to overload a "qualifier cast", then arguably, we could > add an enhancement request for something like: No. That would be not an enhancement. That would be a bug report requesting the removal of an necessarily introduced limitation. > > auto opCast(); //Removes qualifier cast > auto opCast(const); //Adds const > auto opCast(shared); //Adds shared That's not necessary since the general case of type cast handles the specific case of qualifier-only cast perfectly. > > But I really don't see how a *type* cast should interfere a *qualifier* cast. Qualifier cast *is* a special case of type cast!
Comment #10 by monarchdodra — 2013-12-12T23:35:57Z
(In reply to comment #9) > (In reply to comment #7) > > > > Your example snippet still overflows for me for the initial use case: > > It doesn't with dmd 2.064. Are you sure you don't mean HEAD? Head is the only version for me that doesn't horribly die for me. ~/D$ ./HEAD/dmd/src/dmd -run main.d ~/D$ ./RELEASE/dmd.2.064.2/linux/bin64/dmd -run main.d --- killed by signal 11 ~/D$ ./RELEASE/dmd.2.064/linux/bin64/dmd -run main.d --- killed by signal 11 ~/D$ ./RELEASE/dmd.2.063.2/linux/bin64/dmd -run main.d --- killed by signal 11 ~/D$ ./RELEASE/dmd.2.063/linux/bin64/dmd -run main.d --- killed by signal 11 > But that's irrelevant. As I said, it is trivial to avoid recursion If "T" is anything *but* const, I really don't see how you'd do anything *other* than infinitely recurse. T opCast(T)() const if (is(Unqual!T == S)) { static if (is(T == const)) return this; else //Not const: Try harder? return cast(T)this; } > contrary to your claim that it is impossible. It *might* be possible (which is still yet to be proven), but it isn't tirivial.
Comment #11 by b2.temp — 2019-02-13T01:33:59Z
This issue is quite old and i don't see the limitation you talked about: --- struct S { int* p; T opCast(T)() const { static if (is(T == S)) return cast() this; static if (is(T == const(S))) return cast(const) this; } } void main() { const (S) s; auto b = cast(S)(s); auto c = cast(const)(b); auto d = cast(const(S))(b); } --- Maybe cast() didn't exist at this time ?