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