digitalmars.D.learn - Struct copy constructor with inout
- dhs (31/31) Nov 14 2023 Hello D experts,
- Alexandru Ermicioi (8/11) Nov 14 2023 Seems like it isn't called at all, your copy constructor with
- dhs (4/11) Nov 14 2023 When I run test() it outputs the string "copy", so I am not sure
- Steven Schveighoffer (14/37) Nov 14 2023 `ss2 = s2` does not fail because the type is implicitly
- Paul Backus (15/22) Nov 14 2023 The error is saying that the copy constructor expects a `const`
- dhs (13/16) Nov 14 2023 Thanks you both very much for answering.
- dhs (2/3) Nov 14 2023 I meant "ss1 = s1" here, sorry.
- Paul Backus (51/60) Nov 14 2023 The real answer is that constructors are special, and constructor
- dhs (15/35) Nov 14 2023 Thanks for this explanation, it all makes perfect sense.
- Paul Backus (6/10) Nov 14 2023 There's no assignment. The value is constructed in-place, in
- Steven Schveighoffer (20/26) Nov 14 2023 This would be a much better output. Is this something you made up
- dhs (10/18) Nov 14 2023 On Tuesday, 14 November 2023 at 16:58:25 UTC, Steven
- Nick Treleaven (10/22) Nov 16 2023 Thanks for explaining, it's a confusing error.
- Paul Backus (27/44) Nov 14 2023 Because `S2` is a pure value type and contains no pointers or
Hello D experts,
I have a question regarding inout in struct copy constructors.
 From the spec:
"The copy constructor can be overloaded with different qualifiers 
applied to the parameter (copying from a qualified source) or to 
the copy constructor itself (copying to a qualified destination)"
I am using following code:
```d
struct S1
{
     this(ref const S1 s) const { writeln("copy"); }
     int i;
}
struct S2
{
     this(ref inout S2 s) inout { writeln("copy"); }
     int i;
}
void test()
{
     const(S1) s1;
     S1 ss1 = s1; // error, ss1 not qualified as const
     const(S2) s2;
     S2 ss2 = s2; // fine, why?
}
```
Isn't "inout" supposed to copy the const-ness of its parameter to 
the constructor's attribute? In other words: why doesn't ss2=s2 
fail here?
Thanks,
dhs
 Nov 14 2023
On Tuesday, 14 November 2023 at 08:50:34 UTC, dhs wrote:In other words: why doesn't ss2=s2 fail here? Thanks, dhsSeems like it isn't called at all, your copy constructor with inout. Could be a bug. My assumption is that default copy constructors are generated alongside inout one, and then picked up for your initialization instead of inout one. Best regards, Alexandru.
 Nov 14 2023
On Tuesday, 14 November 2023 at 09:07:24 UTC, Alexandru Ermicioi wrote:Seems like it isn't called at all, your copy constructor with inout. Could be a bug. My assumption is that default copy constructors are generated alongside inout one, and then picked up for your initialization instead of inout one. Best regards, Alexandru.When I run test() it outputs the string "copy", so I am not sure why you're saying this.
 Nov 14 2023
On Tuesday, 14 November 2023 at 08:50:34 UTC, dhs wrote:
 I am using following code:
 ```d
 struct S1
 {
     this(ref const S1 s) const { writeln("copy"); }
     int i;
 }
 struct S2
 {
     this(ref inout S2 s) inout { writeln("copy"); }
     int i;
 }
 void test()
 {
     const(S1) s1;
     S1 ss1 = s1; // error, ss1 not qualified as const
     const(S2) s2;
     S2 ss2 = s2; // fine, why?
 }
 ```
 Isn't "inout" supposed to copy the const-ness of its parameter 
 to the constructor's attribute? In other words: why doesn't 
 ss2=s2 fail here?
`ss2 = s2` does not fail because the type is implicitly 
convertible to non-const (a const int can be converted to a 
mutable int). Change `i` to `int *` and it will fail.
IMO, the first should succeed as well. And I will note that the 
error looks different from what you say:
```
Error: copy constructor `testinoutctor.S1.this(ref const(S1) s) 
const` is not callable using argument types `(const(S1))`
```
I'm not sure what this means. There shouldn't be a copy being 
made here, as the thing is already const. I don't understand this 
error, and it looks like a bug to me.
-Steve
 Nov 14 2023
On Tuesday, 14 November 2023 at 13:41:32 UTC, Steven Schveighoffer wrote:``` Error: copy constructor `testinoutctor.S1.this(ref const(S1) s) const` is not callable using argument types `(const(S1))` ``` I'm not sure what this means. There shouldn't be a copy being made here, as the thing is already const. I don't understand this error, and it looks like a bug to me.The error is saying that the copy constructor expects a `const` `this` argument, but you're passing a mutable `this` argument. It's confusing because (a) the `this` argument is hidden and doesn't appear in the parameter list, and (b) there's no explicit "mutable" qualifier. So when it prints out the list of argument types that were actually passed, the `this` argument (`S1 ss1`) gets printed as an empty string. It's easier to see if you compare the actual and expected argument lists side-by-side Expected: (ref const(S1) s) const Actual: ( const(S1) ) ^^^^^ Mismatched 'this' argument
 Nov 14 2023
On Tuesday, 14 November 2023 at 13:58:17 UTC, Paul Backus wrote:On Tuesday, 14 November 2023 at 13:41:32 UTC, Steven The error is saying that the copy constructor expects a `const` `this` argument, but you're passing a mutable `this` argument.Thanks you both very much for answering. Just to clarify some more: isn't "s1 = ss1" similar to something like: ```d const(S1) s1; S1 ss1; // ss1 is now S1.init S1_copy_construct_const_in_const_out(ss1, s1); ``` If this is the case, the compile error is expected, but why/how/where do "implicit qualifer conversions" apply here? Thanks again, dhs
 Nov 14 2023
On Tuesday, 14 November 2023 at 14:36:57 UTC, dhs wrote:Just to clarify some more: isn't "s1 = ss1" similar toI meant "ss1 = s1" here, sorry.
 Nov 14 2023
On Tuesday, 14 November 2023 at 14:36:57 UTC, dhs wrote:
 Just to clarify some more: isn't "s1 = ss1" similar to 
 something like:
 ```d
     const(S1) s1;
     S1 ss1; // ss1 is now S1.init
     S1_copy_construct_const_in_const_out(ss1, s1);
 ```
 If this is the case, the compile error is expected, but 
 why/how/where do "implicit qualifer conversions" apply here?
The real answer is that constructors are special, and constructor 
calls follow different type-checking rules from normal function 
calls.
Here's an example:
```d
struct S
{
     int n;
     this(int n) const { this.n = n; }
     void fun() const {}
}
void main()
{
     S s;
     s.__ctor(123); // error
     s.fun(); // ok
}
```
Normally, it's fine to call a `const` method on a mutable object, 
because mutable implicitly converts to `const`. But when that 
method is a constructor, it's not allowed.
Why? Because constructors have a special "superpower" that no 
other functions in D have: they're allowed to write to `const` 
and `immutable` variables. This is documented in the spec under 
["Field initialization inside a constructor"][1].
If you could call a `const` constructor on a mutable object, it 
would be possible to use that constructor to violate the type 
system by storing a pointer to `immutable` data in a mutable 
field:
```d
struct S2
{
     int* p;
     this(const int* p) const
     {
         // Ok - counts as initialization
         this.p = p;
     }
}
immutable int answer = 42;
void main()
{
     S2 s2;
     // If this were allowed to compile...
     s2.__ctor(&answer);
     // ...then this could happen
     *s2.p = 12345;
}
```
[1]: https://dlang.org/spec/struct.html#field-init
 Nov 14 2023
On Tuesday, 14 November 2023 at 14:58:21 UTC, Paul Backus wrote:
 ```d
 struct S2
 {
     int* p;
     this(const int* p) const
     {
         // Ok - counts as initialization
         this.p = p;
     }
 }
 immutable int answer = 42;
 void main()
 {
     S2 s2;
     // If this were allowed to compile...
     s2.__ctor(&answer);
     // ...then this could happen
     *s2.p = 12345;
 }
 ```
Thanks for this explanation, it all makes perfect sense.
Regarding implicit qualifier conversion: in my code, S2 has a 
copy constructor, so it takes a "ref inout S2" as input. Since 
the copy constructor is in itself qualified as inout, the 
implicit "this" parameter is "ref inout S2" too.
We then have "const(S2) s2;" and "S2 ss2 = s2;". The implicit 
qualifier conversions *for references* do not allow conversion 
from "ref const(S2)" to "ref S2", so I assume that the "inout" in 
the copy constructor translates to "const". In that case, the 
copy constructor creates a "const S2", not an S2.
Does this "const S2" then get implicitly converted to "S2", 
before being assigned to "ss2"? I tried adding an opAssign() to 
S2 - it's not getting called. So I'm not sure what is actually 
going on here.
 Nov 14 2023
On Tuesday, 14 November 2023 at 16:39:42 UTC, dhs wrote:Does this "const S2" then get implicitly converted to "S2", before being assigned to "ss2"? I tried adding an opAssign() to S2 - it's not getting called. So I'm not sure what is actually going on here.There's no assignment. The value is constructed in-place, in `ss2`'s memory. The reason the compiler allows you to construct a `const(S2)` value inside of an `S2` variable is that `const(S2)` implicitly converts to `S2`.
 Nov 14 2023
On Tuesday, 14 November 2023 at 13:58:17 UTC, Paul Backus wrote:
 It's easier to see if you compare the actual and expected 
 argument lists side-by-side
     Expected: (ref const(S1) s) const
     Actual:   (    const(S1)  )
                                 ^^^^^
                      Mismatched 'this' argument
This would be a much better output. Is this something you made up 
or did you get it from one of the compilers? LDC2, which is what 
I tested with, reported in the format that I mentioned.
It might be something to add to the compiler that mismatches in 
`this` qualifiers should be reported separately, especially since 
the mutable form has no explicit qualifier. But I guess this is 
only an issue for constructors, because normal const functions 
can be called with mutable objects.
That being said, I still consider this a bug, if the inout 
version works, the const version should work as well. I don't see 
the difference.
So an interesting thing, if I change the `int i` to `int *i` in 
`S2`, instead of compiling I get the error:
```
Error: `inout` constructor `testinoutctor.S2.this` creates const 
object, not mutable
```
Which gives a much nicer error message!
-Steve
 Nov 14 2023
On Tuesday, 14 November 2023 at 16:51:07 UTC, Paul Backus wrote:There's no assignment. The value is constructed in-place, in `ss2`'s memory. The reason the compiler allows you to construct a `const(S2)` value inside of an `S2` variable is that `const(S2)` implicitly converts to `S2`.On Tuesday, 14 November 2023 at 16:58:25 UTC, Steven Schveighoffer wrote:That being said, I still consider this a bug, if the inout version works, the const version should work as well. I don't see the difference.Ok so to summarize: - "inout" and "const" behaving differently here is probably wrong, and - in structs without reference fields, a const constructor can be used to construct mutable values, due to "implicit qualifier conversions". Thanks.
 Nov 14 2023
On Tuesday, 14 November 2023 at 13:58:17 UTC, Paul Backus wrote:On Tuesday, 14 November 2023 at 13:41:32 UTC, Steven Schveighoffer wrote:Thanks for explaining, it's a confusing error. The error when the constructor is changed to be immutable is: ``` Error: `immutable` method `immutable_ctor.S1.this` is not callable using a mutable object ``` (I've made a fix to say constructor instead of method for that.) Now I've made a fix to produce a similar error for const: https://issues.dlang.org/show_bug.cgi?id=24248``` Error: copy constructor `testinoutctor.S1.this(ref const(S1) s) const` is not callable using argument types `(const(S1))` ``` I'm not sure what this means. There shouldn't be a copy being made here, as the thing is already const. I don't understand this error, and it looks like a bug to me.The error is saying that the copy constructor expects a `const` `this` argument, but you're passing a mutable `this` argument.
 Nov 16 2023
On Tuesday, 14 November 2023 at 08:50:34 UTC, dhs wrote:
 ```d
 struct S2
 {
     this(ref inout S2 s) inout { writeln("copy"); }
     int i;
 }
 void test()
 {
     const(S1) s1;
     S1 ss1 = s1; // error, ss1 not qualified as const
     const(S2) s2;
     S2 ss2 = s2; // fine, why?
 }
 ```
 Isn't "inout" supposed to copy the const-ness of its parameter 
 to the constructor's attribute? In other words: why doesn't 
 ss2=s2 fail here?
Because `S2` is a pure value type and contains no pointers or 
references, the compiler allows `const(S2)` to implicitly convert 
to `S2`. This is documented in the spec under ["Implicit 
Qualifier Conversions"][1], and you can verify it with a `static 
assert`:
```d
static assert(is(const(S2) : S2)); // ok
```
In order to prevent this conversion, you can replace `int i` with 
`int* p`:
```d
struct S3
{
     this(ref inout S2 s) inout { writeln("copy"); }
     int *p;
}
void test()
{
     const(S3) s3;
     S3 ss3 = s3;
     // Error: cannot implicitly convert expression
     // `s3` of type `const(S3)` to `S3`
}
```
[1]: 
https://dlang.org/spec/const3.html#implicit_qualifier_conversions
 Nov 14 2023








 
  
  
 
 dhs <dhs email.com>
 dhs <dhs email.com> 