April 25, 2017
On Tuesday, 25 April 2017 at 07:58:43 UTC, Jack Applegame wrote:
> On Monday, 24 April 2017 at 18:48:00 UTC, Stanislav Blinov wrote:
>> Suddenly, we can't copy the pointer, or at least make a shallow copy of it, without violating const, and the latter is UB.
>
> Because transitive const/immutable is shit. And that shit had to introduce another shit - Rebindable. D const/immutable system is very big PITA. I hate it.

Don't use it then. Life is so exciting when everything may change at any time.
April 25, 2017
On Tuesday, 25 April 2017 at 08:53:22 UTC, Stanislav Blinov wrote:
> On Tuesday, 25 April 2017 at 07:58:43 UTC, Jack Applegame wrote:
>> On Monday, 24 April 2017 at 18:48:00 UTC, Stanislav Blinov wrote:
>>> Suddenly, we can't copy the pointer, or at least make a shallow copy of it, without violating const, and the latter is UB.
>>
>> Because transitive const/immutable is shit. And that shit had to introduce another shit - Rebindable. D const/immutable system is very big PITA. I hate it.
>
> Don't use it then. Life is so exciting when everything may change at any time.

I can't. I need immutable pointers to mutable data and vica versa. D const system is better then nothing.
April 26, 2017
On 25 April 2017 at 14:17, Stanislav Blinov via Digitalmars-d < digitalmars-d@puremagic.com> wrote:

> On Tuesday, 25 April 2017 at 01:59:55 UTC, Manu wrote:
>
> Ah crap, I somehow missed the single-argument move() function.
>>
>> Okay, so this pattern is definitely reliable? I haven't seen it clearly
>> documented anywhere that this is the prescribed pattern, and it's come up
>> on stack overflow a few times, but it hasn't been answered correctly.
>> I think this is poorly distributed knowledge.
>>
>
> It is definitely reliable.
>
> As Schveighoffer pointed out, this pattern requires *another* overload for
>> `ref const X`? Surely if there is no `ref X` and only a `ref const X` available, it should choose that one rather than the byval one when the argument is not const (as demonstrated in the bug report?)
>>
>
> No, the bug report is incorrect. See http://dlang.org/spec/function .html#function-overloading. Remember that, unlike C++'s '&', "ref" is not a type, it's a parameter storage class. So, given the overloads
>
> foo(const ref X);
> foo(X x);
>
> or, more precisely:
>
> foo(ref const(X));
> foo(X x);
>
> if you call foo with a non-const X lvalue *or* rvalue, the second overload will be chosen (the exact match):
>
> X x;
> foo(x);   // typeof(x) == X, so foo(X) is chosen. It's passed by value, so
> x is copied first.
> foo(X()); // also calls foo(X), this time no copy is needed
>
> const X cx;
> foo(cx); // calls foo(ref const(X)), since typeof(cx) == const(X)
>
> In C++, those foos would be ambiguous. In D, they aren't, because it first checks whether it's const or not, and then whether it's an lvalue or an rvalue.
>
> It's demonstrated that `ref const X` might inhibit an efficient copy
>> constructor implementation in some cases, but you can't have false-move's occurring when the lvalue is not const.
>>
>
> It's not about efficiency, it's about preserving type information. Once
> you slap "const" on, there's no getting rid of it without violating the
> type system.
> And there won't be any "false" moves. You either have a reference to copy
> from, or a full-blown copy/rvalue to move from.
>

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 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.


April 26, 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.
April 26, 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
April 27, 2017
On Wednesday, 26 April 2017 at 13:38:59 UTC, Steven Schveighoffer wrote:

>>     ~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.

Yeah, sorry, got carried away. Postblit would've been enough for enabling destructive move.

> 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.

That's the thing, this is more about "need" than "want". As far as I understood, Manu is talking about these ctors from the C++ perspective, where the copy constructor takes const&.
And the problem for those who come from C++ would be that in D it doesn't work that way, at least not universally.
April 27, 2017
On 4/26/17 8:17 PM, Stanislav Blinov wrote:
> On Wednesday, 26 April 2017 at 13:38:59 UTC, Steven Schveighoffer wrote:

>> 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.
>
> That's the thing, this is more about "need" than "want". As far as I
> understood, Manu is talking about these ctors from the C++ perspective,
> where the copy constructor takes const&.
> And the problem for those who come from C++ would be that in D it
> doesn't work that way, at least not universally.

My solution would be to push the duplication onto the user. I'm not a fan of implicit copying. It's also wasteful in the case of immutable data.

struct X
{
   char[] x;
   X dup() const { return X(x.dup); }
}

struct Y
{
   X x;
   inout this(inout(X) x) { this.x = x; }
}

X x;
const(X) cx;
auto y = Y(x);
auto cy = const(Y)(cx); // this could be simplified with factory
y = Y(cx.dup);

-Steve
April 28, 2017
On Thursday, 27 April 2017 at 12:28:38 UTC, Steven Schveighoffer wrote:
> My solution would be to push the duplication onto the user. I'm not a fan of implicit copying. It's also wasteful in the case of immutable data.

Isn't that an odd stance given that "struct" is supposed to be a value type?

April 28, 2017
On 4/28/17 12:56 PM, Ola Fosheim Grøstad wrote:
> On Thursday, 27 April 2017 at 12:28:38 UTC, Steven Schveighoffer wrote:
>> My solution would be to push the duplication onto the user. I'm not a
>> fan of implicit copying. It's also wasteful in the case of immutable
>> data.
>
> Isn't that an odd stance given that "struct" is supposed to be a value
> type?
>

Not really, but thanks for asking.

-Steve
April 28, 2017
On Friday, 28 April 2017 at 18:05:41 UTC, Steven Schveighoffer wrote:
> On 4/28/17 12:56 PM, Ola Fosheim Grøstad wrote:
>> Isn't that an odd stance given that "struct" is supposed to be a value
>> type?
>>
>
> Not really, but thanks for asking.

Well, it counters the very definition of a value... I guess what you are saying is that the conventions are changing for struct over time, but that puts .init in a very odd position.
1 2 3
Next ›   Last »