Jump to page: 1 2
Thread overview
Struct copy constructor with inout
Nov 14
dhs
Nov 14
dhs
Nov 14
dhs
Nov 14
dhs
Nov 14
dhs
Nov 14
dhs
November 14

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:

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

November 14

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.

November 14

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.

November 14

On Tuesday, 14 November 2023 at 08:50:34 UTC, dhs wrote:

>

I am using following code:

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

November 14

On Tuesday, 14 November 2023 at 08:50:34 UTC, dhs wrote:

>
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", and you can verify it with a static assert:

static assert(is(const(S2) : S2)); // ok

In order to prevent this conversion, you can replace int i with int* p:

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`
}
November 14

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
November 14

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:

    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

November 14

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.

November 14

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:

    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:

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".

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:

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;
}
November 14

On Tuesday, 14 November 2023 at 14:58:21 UTC, Paul Backus wrote:

>
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.

« First   ‹ Prev
1 2