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

Stanislav Blinov via Digitalmars-d digitalmars-d at puremagic.com
Tue Apr 25 23:17:59 PDT 2017


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();
     }
     //...
}

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.

Note that this is actually redundant and nothing more than a 
syntactic convenience, since duplicate() already returns a 
non-const object.
In my own code, I'd rather drop the const overloads and define a 
generic duplicate template that'd forward non-consts and types 
without pointers, and expect a dup() function to be available for 
all other types (similar to arrays).

> I can imagine in some cases, copy constructing from X, and 
> making a copy of X then move constructing from X are 
> similar/same cost... so it's really just a gotcha that authors 
> need to be aware of.

> I feel this is complicated and there are a lot of particulars. 
> This needs to be documented clearly in the language docs, and 
> there should probably be some examples how it relates to C++'s 
> copy/move construction offerings, because it's significantly 
> different and anyone with C++ experience will fail to get this 
> right.

I agree, this is rather scattered, perhaps an article is in order.


More information about the Digitalmars-d mailing list