www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Why can't I init a new var from const var?

reply Random D user <no email.com> writes:
I can init a variable from mutable source without defining any 
constructor or assignment operators, but not if the source is 
const. I would imagine the behavior to be the same with mutable 
and const source, since it's just reading the source and copying 
it.

Is there a reason for this? Or is this a bug?
I can workaround this by making copies or casting, that just 
creates ugly code everywhere.

Here's an example (with dmd 2.073):

struct Foo
{
     this( Foo source )
     {
         buf = source.buf.dup;
     }

     this( const Foo source )
     {
         buf = source.buf.dup;
     }

     this( const ref Foo source )
     {
         buf = source.buf.dup;
     }

     void opAssign( Foo source )
     {
         buf = source.buf.dup;
     }

     void opAssign( const Foo source )
     {
         buf = source.buf.dup;
     }

     void opAssign( const ref Foo source )
     {
         buf = source.buf.dup;
     }

     char[] buf;
}

Foo fun(const ref Foo foo, Foo foo2)
{
     Foo bar  = foo;                     // Error: cannot 
implicitly convert expression (foo) of type const(Foo) to Foo
     Foo baz  = foo2;                    // Ok, No need for 
constructors or opAssign
     Foo baz2 = cast(const Foo)foo2;     // Error: cannot 
implicitly convert expression (Foo(null).this(foo2)) of type 
const(Foo) to Foo
     Foo bar2;
     bar2     = foo;                     // uses opAssing( const 
Foo ) / opAssign( const ref Foo )
     Foo bar3;
     bar3     = foo2;                    // uses opAssign( const 
Foo ) / opAssign( Foo )
     Foo bar4;
     bar4     = cast(const Foo)foo2;     // uses opAssing( const 
Foo )

     //Foo bar = Foo(foo);     // This works provided there is 
non-const opAssign defined.
     //Foo bar = cast(Foo)foo; // This seems to work as well
     return bar;
}

Foo foo;
foo = fun(foo, foo);
Feb 11 2017
next sibling parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 02/11/2017 03:15 PM, Random D user wrote:
 I can init a variable from mutable source without defining any
 constructor or assignment operators, but not if the source is const. I
 would imagine the behavior to be the same with mutable and const source,
 since it's just reading the source and copying it.

 Is there a reason for this? Or is this a bug?
 I can workaround this by making copies or casting, that just creates
 ugly code everywhere.
Just to make sure, you don't need to define all those overloads. This is sufficient but you have to use a different (and idiomatic construction syntax.): struct Foo { this( const(Foo) source ) { buf = source.buf.dup; } void opAssign( const(Foo) source ) { buf = source.buf.dup; } char[] buf; } Foo fun(const ref Foo foo, Foo foo2) { auto bar = Foo(foo); // Foo bar = foo; // Error: cannot implicitly convert expression (foo) of type const(Foo) to Foo Foo baz = foo2; // Ok, No need for constructors or opAssign auto baz2 = const(Foo)(foo2); Foo bar2; bar2 = foo; // uses opAssing( const Foo ) / opAssign( const ref Foo ) Foo bar3; bar3 = foo2; // uses opAssign( const Foo ) / opAssign( Foo ) Foo bar4; bar4 = cast(const Foo)foo2; // uses opAssing( const Foo ) //Foo bar = Foo(foo); // This works provided there is non-const opAssign defined. //Foo bar = cast(Foo)foo; // This seems to work as well return bar; } void main() { } When you do this: Foo bar = foo; There is an implicit conversion in place, which D does not allow during construction. The idiomatic syntax is the following: auto bar = Foo(foo); The construction on the right-hand side is now explicit. Likewise, prefer the following syntax: auto baz2 = const(Foo)(foo2); Notes: When you want to treat lvalue sources differently from rvalue sources, you can define the following overloads as well (note const(Foo) syntax, which is preferred): this( ref const(Foo) source ) { buf = source.buf.dup; } void opAssign( ref const(Foo) source ) { buf = source.buf.dup; } Alternatively, you can define an 'auto ref' function that automatically takes both lvalues and rvalues (lvalue by-ref, rvalues by-move). Note the empty template parentheses: struct Foo { this()( auto ref const(Foo) source ) { buf = source.buf.dup; } void opAssign()( auto ref const(Foo) source ) { buf = source.buf.dup; } char[] buf; } Ali
Feb 11 2017
prev sibling parent ag0aep6g <anonymous example.com> writes:
On 02/12/2017 12:15 AM, Random D user wrote:
 I can init a variable from mutable source without defining any
 constructor or assignment operators, but not if the source is const. I
 would imagine the behavior to be the same with mutable and const source,
 since it's just reading the source and copying it.
It works as long as your type has no mutable indirections (pointers, array, class objects). You can't just copy an indirection from const to mutable, because the referenced (possibly immutable) data would be incorrectly typed as mutable.
 Is there a reason for this? Or is this a bug?
 I can workaround this by making copies or casting, that just creates
 ugly code everywhere.

 Here's an example (with dmd 2.073):

 struct Foo
 {
[...]
     this( const Foo source )
     {
         buf = source.buf.dup;
     }
[...]
     char[] buf;
 }

 Foo fun(const ref Foo foo, Foo foo2)
 {
     Foo bar  = foo;                     // Error: cannot implicitly
 convert expression (foo) of type const(Foo) to Foo
Initializations like this don't call the constructor. Instead, they do "copy construction" and call a "postblit function" if it's defined. See https://dlang.org/spec/struct.html#struct-postblit So, if you want to call a function whenever your struct gets copied, you define a postblit function. Like so: this(this) { buf = buf.dup; } Or if you want to do the dup-ing in specific places only, call the constructor explicitly: Foo bar = Foo(foo);
     Foo baz  = foo2;                    // Ok, No need for constructors
 or opAssign
Note that this does not call the constructor, and does not do the dup. If you want this to do the dup, postblit is the way. [...]
     //Foo bar = Foo(foo);     // This works provided there is non-const
 opAssign defined.
     //Foo bar = cast(Foo)foo; // This seems to work as well
I wouldn't have expected the cast to call the constructor, but apparently it does. It's in the spec here: https://dlang.org/spec/expression.html#CastExpression "Casting a value v to a struct S, when value is not a struct of the same type, is equivalent to: S(v)" So a cast like that is really just a fancy way of calling the constructor. This seems a bit inconsistent, though. On postblit the spec says that copy construction happens with "the same type". The compiler apparently sees Foo and const(Foo) as the "same type" in that context. But with the cast, the qualifier mismatch is enough to trigger the constructor call which is supposed to happen when the types are not the same.
     return bar;
 }

 Foo foo;
 foo = fun(foo, foo);
Feb 11 2017