Move construction from !is(T == typeof(this))

Steven Schveighoffer via Digitalmars-d digitalmars-d at puremagic.com
Wed Apr 26 06:38:59 PDT 2017


On 4/26/17 2:17 AM, Stanislav Blinov wrote:
> On Wednesday, 26 April 2017 at 02:19:03 UTC, Manu wrote:
>
>> Right, yeah I see. So, basically, you admit that it is required to
>> have 3 overloads; foo(X), foo(ref X) and foo(ref const X), in the
>> event that I want to avoid needlessly copying X prior to constructing
>> from it in the non-const case...
>
> I admit nothing! (c) :)
>
> Well, yes and no. If we're talking explicit overloads, it would seem
> that the three overloads are needed. But why bother writing all three
> when the compiler can do it for you? As Andrei demonstrated, you can get
> away with two:
>
> struct Y
> {
>     X x;
>     this()(auto ref X x)
>     {
>         // assuming corrected implementation of std.functional.forward:
>         this.x = forward!x;
>     }
>
>     this(ref const X x)
>     {
>         this.x = x;
>     }
> }
>
> The first will handle ref/rvalue cases, the second only the ref const
> case. That way it's similar to what you'd have in C++:
>
> struct Y
> {
>     X x;
>     Y(const X& x) : x(x) {}
>     Y(X&& x) : x(move(x)) {}
> };
>
> Or you could even have just one:
>
> struct Y
> {
>     X x;
>
>     // any T that converts to const(X)
>     this(T : const(X))(auto ref T x)
>     {
>         this.x = forward!x;
>     }
> }
>
> which is actually awfully similar to C++, except with better constraints:
>
> struct Y
> {
>     X x;
>     template <typename T> Y(T&& x) : x(forward<T>(x)) {}
> };
>
> There is a big "however". The above is not a general case. Once pointers
> come into play, you lose the ability to make non-const copies of const
> objects. So if X is, say, some custom string type (to keep in the spirit
> of your original C++ example):
>
> struct X
> {
>     char[] data;
>
>     this(string s)
>     {
>         data = s.dup;
>     }
>
>     this(this)
>     {
>         data = data.dup;
>     }
>
>     ~this()
>     {
>         data.destroy();
>     }

Don't do this. It's not a good idea, since data could be invalid at this 
point. In this case, destroy does nothing (it just sets the array to 
null), so I would just leave the destructor out of it.

>     //...
> }
>
> then neither constructor will compile in this case:
>
> const X cx;
> Y y = cx;          // cannot convert const(X) to X
> Y y = const(X)();  // cannot convert const(X) to X
>
> In this scenario, you'll need:
>
> a) define conversion from const X to non-const X
> b) either drop the ref const overload altogether and use the conversion
> explicitly, or define all four overloads (X, ref X, const X, ref const
> X) with const overloads using the conversion under the hood, or get the
> four from two templates:
>
> X duplicate()(auto ref const X x)
> {
>     return X(x.data.dup);
> }
>
> struct Y
> {
>     X x;
>
>     // X, ref X
>     this()(auto ref X x)
>     {
>         this.x = forward!x;
>     }
>
>     // const(X), ref const(X)
>     this()(auto ref const X x)
>     {
>         this.x = x.duplicate();
>     }
> }
>
> Steven mentioned inout, but it will be of no help here because the
> semantics of const/mutable copying are different.

inout was to help with the double indirection problem. That is, if you 
want to handle both const/mutable with one ref function, you need to use 
inout ref and not const ref, as X which contains indirections does not 
bind to ref const(X).

If you want to duplicate const data, but just shallow-copy mutable data, 
you are correct in that you need two separate constructors, and inout 
doesn't come into play.

-Steve


More information about the Digitalmars-d mailing list