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.