www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Struct copy constructor with inout

reply dhs <dhs email.com> writes:
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
next sibling parent reply Alexandru Ermicioi <alexandru.ermicioi gmail.com> writes:
On Tuesday, 14 November 2023 at 08:50:34 UTC, dhs wrote:
 In other words: why doesn't ss2=s2 fail here?

 Thanks,
 dhs
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.
Nov 14 2023
parent dhs <dhs email.com> writes:
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
prev sibling next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
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
parent reply Paul Backus <snarwin gmail.com> writes:
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
next sibling parent reply dhs <dhs email.com> writes:
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
next sibling parent dhs <dhs email.com> writes:
On Tuesday, 14 November 2023 at 14:36:57 UTC, dhs wrote:
 Just to clarify some more: isn't "s1 = ss1" similar to
I meant "ss1 = s1" here, sorry.
Nov 14 2023
prev sibling parent reply Paul Backus <snarwin gmail.com> writes:
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
parent reply dhs <dhs email.com> writes:
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
parent Paul Backus <snarwin gmail.com> writes:
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
prev sibling next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
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
parent dhs <dhs email.com> writes:
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
prev sibling parent Nick Treleaven <nick geany.org> writes:
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:
 ```
 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.
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
Nov 16 2023
prev sibling parent Paul Backus <snarwin gmail.com> writes:
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