May 09, 2013 Re: The liabilities of binding rvalues to ref | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jonathan M Davis | On Thursday, 9 May 2013 at 19:26:37 UTC, Jonathan M Davis wrote:
> On Thursday, May 09, 2013 19:45:16 Peter Alexander wrote:
>> It seems that 'auto ref' would be suitable, provided we can find
>> a way for it to work with normal functions (in a sensible way,
>> not like templates).
>
> That's trivial enough. All you have to do is lower code like
>
> auto foo(auto ref int i) {...}
>
> foo(5);
>
> to something like
>
> auto foo(ref int i) {...}
>
> auto __temp = 5;
> foo(__temp);
>
> And temporaries end up on the stack anyway, so you wouldn't really even have
> to lower it to quite like that, but that's what would be happening
> conceptually. It's also what would happen if plain ref accepted rvalues. It's
> just that we avoid certain classes of issues by having something distinct from
> plain ref for accepting rvalues by ref. The implementation itself is
> straightforward. If anything, I think that argument comes down primarily to
> two things:
>
> 1. Should plain ref accept rvlaues?
>
> 2. If plain ref shouldn't accept rvalues, then what attribute do we use to
> accept rvalues by ref?
>
> And given that the whole point of adding auto ref to the language was to solve
> exactly this problem, I think that it makes perfect sense to just use auto
> ref.
>
> - Jonathan M Davis
So, if I understand correctly, auto ref for templates will end up doing exactly the same thing as auto ref for non-template functions? That would be perfect, otherwise it'll be terribly confusing.
|
May 09, 2013 Re: The liabilities of binding rvalues to ref | ||||
---|---|---|---|---|
| ||||
Posted in reply to Rob T | On 5/9/13 3:39 PM, Rob T wrote:
> So, if I understand correctly, auto ref for templates will end up doing
> exactly the same thing as auto ref for non-template functions? That
> would be perfect, otherwise it'll be terribly confusing.
There would be clear restrictions on non-template functions, e.g. a non-template cannot return auto ref.
Andrei
|
May 09, 2013 Re: The liabilities of binding rvalues to ref | ||||
---|---|---|---|---|
| ||||
Posted in reply to Andrei Alexandrescu | On Thursday, 9 May 2013 at 19:43:25 UTC, Andrei Alexandrescu wrote:
> On 5/9/13 3:39 PM, Rob T wrote:
>> So, if I understand correctly, auto ref for templates will end up doing
>> exactly the same thing as auto ref for non-template functions? That
>> would be perfect, otherwise it'll be terribly confusing.
>
> There would be clear restrictions on non-template functions, e.g. a non-template cannot return auto ref.
>
> Andrei
OK I can understand that auto ref on return for a template function is something different, so disallowing auto ref on a regular function makes perfect sense to me.
You can also create template functions with auto ref params that behave exactly like a non-template counter part by not specifying ref on the return, so it seems consistent and a sound solution from that perspective.
D is a complex language, so stuff like this does take some getting used to, but it is very powerful and flexible, no two ways around it.
|
May 09, 2013 Re: The liabilities of binding rvalues to ref | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | On Thursday, 9 May 2013 at 18:23:12 UTC, Steven Schveighoffer wrote: > On Thu, 09 May 2013 13:45:16 -0400, Peter Alexander >> Allowing plain-old ref to bind to rvalues would be a massive mistake in my opinion. See case (2) in the original post. > > The case for it is operator overloads. Note that all operator overloads are defined WITHIN types, and all operator overloads take 'this' by reference. Without the hack currently in the compiler that 'this' is allowed to bind by ref to rvalues, D would be near useless. C++ has this "hack" as well. Is it really a hack, or just part of the language design? > Consider that today, this doesn't work for custom structs, unless you allocate the result on the heap. > > ((a + b) + (c + d)) I don't follow. You say "today", but this works for me in latest dmd. Do you mean hypothetically without the "hack", or am I misunderstanding? > The original case 2 looks bad, but not very common. With by-ref turning into by-value, you are in effect changing the API and functionality. It would just mean that you wouldn't make that change, or change the type name at the same time (so it wouldn't just compile on existing code). I'm not sure about how common it is, but I really don't like the idea of calls like swap(1, 2) being legal. Seems like a step backward from C++. |
May 09, 2013 Re: The liabilities of binding rvalues to ref | ||||
---|---|---|---|---|
| ||||
Posted in reply to Peter Alexander | On Thu, 09 May 2013 16:36:27 -0400, Peter Alexander <peter.alexander.au@gmail.com> wrote: > On Thursday, 9 May 2013 at 18:23:12 UTC, Steven Schveighoffer wrote: >> On Thu, 09 May 2013 13:45:16 -0400, Peter Alexander >>> Allowing plain-old ref to bind to rvalues would be a massive mistake in my opinion. See case (2) in the original post. >> >> The case for it is operator overloads. Note that all operator overloads are defined WITHIN types, and all operator overloads take 'this' by reference. Without the hack currently in the compiler that 'this' is allowed to bind by ref to rvalues, D would be near useless. > > C++ has this "hack" as well. Is it really a hack, or just part of the language design? I should restate: The idea that restricting rvalues from binding to refs *except* the case where it binds to 'this' is a hack. The binding to 'this' pre-dates the restriction. > > >> Consider that today, this doesn't work for custom structs, unless you allocate the result on the heap. >> >> ((a + b) + (c + d)) > > I don't follow. You say "today", but this works for me in latest dmd. Do you mean hypothetically without the "hack", or am I misunderstanding? I mean it doesn't work today if it takes the parameter by ref. >> The original case 2 looks bad, but not very common. With by-ref turning into by-value, you are in effect changing the API and functionality. It would just mean that you wouldn't make that change, or change the type name at the same time (so it wouldn't just compile on existing code). > > I'm not sure about how common it is, but I really don't like the idea of calls like swap(1, 2) being legal. Seems like a step backward from C++. Why is it so bad that that is legal? Really hard to stop people from writing incorrect code. In your case, it actually LOOKS wrong, so that's not really a case we should be concerned about. The original case 2 is worse. int i = 1; swap(i, i); That's also legal. *(cast(int *)null) = 5; That's also legal. -Steve |
May 09, 2013 Re: The liabilities of binding rvalues to ref | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | On Thursday, May 09, 2013 16:59:13 Steven Schveighoffer wrote:
> Why is it so bad that that is legal? Really hard to stop people from writing incorrect code. In your case, it actually LOOKS wrong, so that's not really a case we should be concerned about.
The problem is that while it's obvious in the swap case, it's not always obvious. And when you look at a function signature and see that it accepts its argument by ref (and therefore doesn't accept rvalues), you can reasonably assume that it's intended to alter its argument, whereas if it takes by auto ref (which then would accept rvalues), you know that the intention is for the function to accept arguments by ref for efficiency and not for mutation. You do need const to fully guarantee that, but it still gives a definite indicator of intent even without that (and const is too restrictive for some cases to require it). And if ref accepted rvalues, you'd end up with functions using ref as a matter of course for the efficiency gain (as occurs in C++ with const&) and wouldn't have a clue which ones were intended to mutate their arguments and which were just doing it for efficiency (since unlike C++, const wouldn't be required and might even be avoided given how restrictive it is in D).
- Jonathan M Davis
|
May 09, 2013 Re: The liabilities of binding rvalues to ref | ||||
---|---|---|---|---|
| ||||
Posted in reply to Rob T | On Thursday, May 09, 2013 21:39:14 Rob T wrote: > So, if I understand correctly, auto ref for templates will end up doing exactly the same thing as auto ref for non-template functions? That would be perfect, otherwise it'll be terribly confusing. auto ref on templates would be essentially unchanged from what it is now (the fact that it forwards the refness of its arguments is actually very useful for some situations, and we don't want to lose that). However, what we might be able to do as an optimization would be to make it so that auto ref on templates acts the same as auto ref on non-templated functions when the compiler is able to determine that there's no real semantic difference - i.e. when the fact that the refness is being forwarded is irrelevant. So, with a function like auto foo(T)(auto ref T t) { return t; } the non-templated solution could be used, whereas in a function like auto ref foo(T)(auto ref T t) { return t; } or auto foo(T)(auto ref T t) { return anotherFuncWhichTakesByAutoRef(t); } the compiler would have to use the normal templated solution, because the refness of the arguments would have an effect on the code. - Jonathan M Davis |
May 09, 2013 Re: The liabilities of binding rvalues to ref | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jonathan M Davis | On Thu, 09 May 2013 17:06:43 -0400, Jonathan M Davis <jmdavisProg@gmx.com> wrote:
> On Thursday, May 09, 2013 16:59:13 Steven Schveighoffer wrote:
>> Why is it so bad that that is legal? Really hard to stop people from
>> writing incorrect code. In your case, it actually LOOKS wrong, so that's
>> not really a case we should be concerned about.
>
> The problem is that while it's obvious in the swap case, it's not always
> obvious. And when you look at a function signature and see that it accepts its
> argument by ref (and therefore doesn't accept rvalues), you can reasonably
> assume that it's intended to alter its argument, whereas if it takes by auto
> ref (which then would accept rvalues), you know that the intention is for the
> function to accept arguments by ref for efficiency and not for mutation. You do
> need const to fully guarantee that, but it still gives a definite indicator of
> intent even without that (and const is too restrictive for some cases to
> require it). And if ref accepted rvalues, you'd end up with functions using
> ref as a matter of course for the efficiency gain (as occurs in C++ with const&)
> and wouldn't have a clue which ones were intended to mutate their arguments
> and which were just doing it for efficiency (since unlike C++, const wouldn't be
> required and might even be avoided given how restrictive it is in D).
I understand the point, but it's not as bad as you think. Usually you can discern from the function name what its going to do (e.g. the 'fix' function from the example). Having a storage class that accepts by ref and then *might* change them is really kind of useless. If auto ref is used, there should be no promises for that storage class.
In any case, I'm fine with auto ref if that is the solution. I just wonder if it's worth the trouble. I suppose we will know the answer only after having it implemented for a while.
-Steve
|
May 09, 2013 Re: The liabilities of binding rvalues to ref | ||||
---|---|---|---|---|
| ||||
Posted in reply to Rob T Attachments:
| On 10 May 2013 05:39, Rob T <alanb@ucora.com> wrote:
> On Thursday, 9 May 2013 at 19:26:37 UTC, Jonathan M Davis wrote:
>
>> On Thursday, May 09, 2013 19:45:16 Peter Alexander wrote:
>>
>>> It seems that 'auto ref' would be suitable, provided we can find a way for it to work with normal functions (in a sensible way, not like templates).
>>>
>>
>> That's trivial enough. All you have to do is lower code like
>>
>> auto foo(auto ref int i) {...}
>>
>> foo(5);
>>
>> to something like
>>
>> auto foo(ref int i) {...}
>>
>> auto __temp = 5;
>> foo(__temp);
>>
>> And temporaries end up on the stack anyway, so you wouldn't really even
>> have
>> to lower it to quite like that, but that's what would be happening
>> conceptually. It's also what would happen if plain ref accepted rvalues.
>> It's
>> just that we avoid certain classes of issues by having something distinct
>> from
>> plain ref for accepting rvalues by ref. The implementation itself is
>> straightforward. If anything, I think that argument comes down primarily
>> to
>> two things:
>>
>> 1. Should plain ref accept rvlaues?
>>
>> 2. If plain ref shouldn't accept rvalues, then what attribute do we use to accept rvalues by ref?
>>
>> And given that the whole point of adding auto ref to the language was to
>> solve
>> exactly this problem, I think that it makes perfect sense to just use auto
>> ref.
>>
>> - Jonathan M Davis
>>
>
> So, if I understand correctly, auto ref for templates will end up doing exactly the same thing as auto ref for non-template functions? That would be perfect, otherwise it'll be terribly confusing.
>
I don't think this is entirely true, auto ref is a template concept, that is, "automatic ref-ness", it selects the ref-ness of the argument automatically, at compile time, just like auto applied everywhere else (selects a type for instance, at compile time). This concept doesn't make any sense applied to a non-template. It *IS* a ref as specified by the programmer, there's nothing 'automatic' about it.
So to say it will do 'exactly the same thing' is a misunderstanding. I argue that 'auto ref' as applied to non-templates will only create confusion, it effectively re-enforces the type of confusion that you have just shown.
This is the reasoning for the argument behind scope ref, which to my mind
actually makes good sound sense, and should lead people to a proper
understanding of what you are actually doing.
Considering the key argument against 'scope ref' is that people don't want
to require more attributes to make use of it, I don't see how 'auto ref'
satisfies this argument either.
Thus, I am quite happy happy with 'ref', it can be made safe, satisfies the
argument above, and this seems like a very good start that we might
actually all agree on.
|
May 09, 2013 Re: The liabilities of binding rvalues to ref | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | On Thursday, 9 May 2013 at 20:59:14 UTC, Steven Schveighoffer wrote: > I should restate: The idea that restricting rvalues from binding to refs *except* the case where it binds to 'this' is a hack. The binding to 'this' pre-dates the restriction. Ah ok. I do agree that the asymmetry is quite hacky, although binding to 'this' isn't quite the same as binding to ref. >> I'm not sure about how common it is, but I really don't like the idea of calls like swap(1, 2) being legal. Seems like a step backward from C++. > > Why is it so bad that that is legal? Really hard to stop people from writing incorrect code. You could apply the same argument against static typing, e.g. "swap(apple, orange) looks wrong, so why is it so bad not to catch the type error at compile time?" I think we can agree that while it's not possible to stop people from writing incorrect code 100% of the time, there is still benefit to catching as many cases as practical. |
Copyright © 1999-2021 by the D Language Foundation