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' argumentThis 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