Bug 23987 – Copy construction should not disable implicit conversion

Status
RESOLVED
Resolution
WONTFIX
Severity
enhancement
Priority
P1
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2023-06-12T13:56:18Z
Last change time
2023-06-19T14:34:00Z
Assigned to
No Owner
Creator
timon.gehr

Comments

Comment #0 by timon.gehr — 2023-06-12T13:56:18Z
DMD 2.104.0: ```d struct T { // no copy constructor } struct S { this(const ref S _){ // dummy copy constructor } } void main(){ void foo()inout{ immutable(T) t1; auto t2=t1; // ok, can convert from mutable to immutable immutable(S) s1; auto s2=s1; // error, copy constructor returns mutable, but immutable is required } } ``` The code should compile, it can just create a mutable object and convert it to immutable, as in the first case.
Comment #1 by timon.gehr — 2023-06-12T13:56:48Z
A workaround is to mark the copy constructor `pure`.
Comment #2 by razvan.nitu1305 — 2023-06-19T13:42:41Z
I don't think this bug report is valid. Since a copy constructor is defined for S, then all copies that are being made for it need to call a copy constructor. Since the copy constructor is only defined for mutable destinations, I don't see how we could safely support the automatic conversion from mutable to immutable. That would open the door for code like this one: int* s; struct S { int p; this(const ref S src) { s = &p; } } void main() { immutable(S) s1 = S(2); immutable(S) s2 = s1; writeln(*s2.p); *s = 9; writeln(*s2.p); }
Comment #3 by razvan.nitu1305 — 2023-06-19T13:48:54Z
(In reply to RazvanN from comment #2) > > void main() > { > immutable(S) s1 = S(2); > immutable(S) s2 = s1; > writeln(*s2.p); > *s = 9; > writeln(*s2.p); > } `p` is not a pointer so no need for the `*`. Sorry for the bogus test case. Correct code: ``` int* s; struct S { int p; this(const ref S src) { s = &p; } } void main() { immutable(S) s1 = S(2); immutable(S) s2 = s1; writeln(s2.p); *s = 9; writeln(s2.p); } ```
Comment #4 by timon.gehr — 2023-06-19T14:34:00Z
(This is an enhancement request, not a bug report.) Taking the address of `p` in your example is not `@safe` (and subsequently the pointer in your main function refers to a dead memory location, as the value after implicit conversion is separate from the one before implicit conversion). This enhancement request grew out of the existing behavior being confusing. In particular: ```d import std.typecons; struct T { // no copy constructor } struct S { this(const ref S _){ // dummy copy constructor } } void main(){ Nullable!T t; // ok Nullable!S s; // error } ``` The error happens here: ```d this(ref return scope inout Nullable!T rhs) inout { _isNull = rhs._isNull; if (!_isNull) _value.payload = rhs._value.payload; else _value = DontCallDestructorT.init; } ``` I had hoped we can avoid the requirement for people to provide an `inout` copy constructor if they are generating a mutable `struct` without indirections from a `const` instance. However, I agree that generating an implicit second copy constructor call to a generated copy constructor after the user has already provided their own copy constructor is probably even worse because copies/moves may get missed. Maybe requiring the copy constructor to be annotated `pure` is good enough, but the overall situation is a bit unsatisfactory. Closing this for now.