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.

Reply via email to