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

Reply via email to