March 28, 2018 Re: rvalues -> ref (yup... again!) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Timon Gehr | On 28.03.2018 13:34, Timon Gehr wrote:
> On 27.03.2018 20:14, Manu wrote:
>> That's exactly what I've been saying. For like, 9 years..
>> It looks like this:
>> https://github.com/TurkeyMan/DIPs/blob/ref_args/DIPs/DIP1xxx-rval_to_ref.md
>>
>> (contribution appreciated)
>>
>> As far as I can tell, it's completely benign, it just eliminates the
>> annoying edge cases when interacting with functions that take
>> arguments by ref. There's no spill-over affect anywhere that I'm aware
>> of, and if you can find a single wart, I definitely want to know about
>> it.
>
> ???
> >> I've asked so many times for a technical destruction, nobody will
>> present any opposition that is anything other than a rejection *in
>> principle*. This is a holy war, not a technical one.
>
> That's extremely unfair. It is just a bad idea to overload D const for this purpose. Remove the "const" requirement and I'm on board.
"The proposal could be amended to accept mutable ref's depending on the value-judgement balancing these 2 use cases. Sticking with const requires no such value judgement to be made at this time, and it's much easier to relax the spec in the future with emergence of evidence to do so."
Just get it right the first time. "const" is a serious API restriction, and it shouldn't be forced on anyone, even intermittently until they figure out that it is too restrictive (as well as viral).
|
March 28, 2018 Re: rvalues -> ref (yup... again!) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Manu | On 27.03.2018 20:14, Manu wrote: > That's exactly what I've been saying. For like, 9 years.. > It looks like this: > https://github.com/TurkeyMan/DIPs/blob/ref_args/DIPs/DIP1xxx-rval_to_ref.md > (contribution appreciated) "Temporary destruction Destruction of any temporaries occurs naturally at the end of the scope, as usual." This is actually unusual. --- import std.stdio; struct S{ ~this(){ writeln("destroyed!"); } } void foo(S s){} void main(){ foo(S()); writeln("end the scope"); } --- prints: destroyed! end the scope "Overload resolution ... This follows existing language rules. No change is proposed here." Yes, you _are_ proposing a change. Right now rvalues "prefer" the by-value overload because the ref overload _does not match at all_. Now you make it match both, so you are adding additional disambiguation rules. You need to be more explicit about those. Note that lvalues prefer the ref overload because the ref overload is more specialized. The new rule is the only instance where a less specialized overload is preferred. You also need to specify the interactions with matching levels (https://dlang.org/spec/function.html#function-overloading): E.g., your DIP is compatible with the following behavior: --- import std.stdio; struct S{} void fun(S){ writeln("A"); } void fun(ref const(S)){ writeln("B"); } void main(){ fun(S()); // calls A S s; fun(s); // calls A const(S) t; fun(t); // calls B fun(const(S)()); // calls B } --- The first example will cause friction when people try to add an explicit rvalue overload alongside a previous catch-it-all overload, the second example shows a breaking language change. You cannot "fix" the first example without introducing breaking language changes. The above code compiles and runs in current D. This just smells bad. Remove the "const" requirement. |
March 28, 2018 Re: rvalues -> ref (yup... again!) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Rubn | On 27.03.2018 22:25, Rubn wrote:
>
> D already has move semantics, an easy solution to this is to just use another keyword. It doesn't have to bind to const ref to get what is desired:
>
> // what was suggested in the original DIP, since scope is being used for something else now
> void foo(@temp ref value)
> {
> }
>
> Now you don't have this problem. You only get this behavior when you basically say you don't care whether it is a temporary or not.
Another benefit of this solution is that the overload resolution rules are obvious. foo(@temp ref T value) is less specialized than both foo(T value) and foo(ref T value).
@Manu: Consider this.
|
March 28, 2018 Re: rvalues -> ref (yup... again!) | ||||
---|---|---|---|---|
| ||||
Attachments:
| On 28 Mar. 2018 4:35 am, "Timon Gehr via Digitalmars-d" < digitalmars-d@puremagic.com> wrote: On 27.03.2018 20:14, Manu wrote: > That's exactly what I've been saying. For like, 9 years.. > It looks like this: > https://github.com/TurkeyMan/DIPs/blob/ref_args/DIPs/DIP1xxx > -rval_to_ref.md > (contribution appreciated) > > As far as I can tell, it's completely benign, it just eliminates the annoying edge cases when interacting with functions that take arguments by ref. There's no spill-over affect anywhere that I'm aware of, and if you can find a single wart, I definitely want to know about it. > ??? I've asked so many times for a technical destruction, nobody will > present any opposition that is anything other than a rejection *in principle*. This is a holy war, not a technical one. > That's extremely unfair. It is just a bad idea to overload D const for this purpose. Remove the "const" requirement and I'm on board. I discussed that in that document. I'm happy to remove const, but it requires a value judgement on the meaning of non-const in this case. It becomes controversial without const, but I'm personally happy to remove it if you can make The argument in favour. Can you give me some ideas where it would be useful? |
March 28, 2018 Re: rvalues -> ref (yup... again!) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Timon Gehr | On 28 March 2018 at 05:22, Timon Gehr via Digitalmars-d <digitalmars-d@puremagic.com> wrote: > On 27.03.2018 20:14, Manu wrote: >> >> That's exactly what I've been saying. For like, 9 years.. It looks like this: >> >> https://github.com/TurkeyMan/DIPs/blob/ref_args/DIPs/DIP1xxx-rval_to_ref.md >> (contribution appreciated) > > > "Temporary destruction > Destruction of any temporaries occurs naturally at the end of the scope, as > usual." > > This is actually unusual. > > --- > import std.stdio; > struct S{ > ~this(){ > writeln("destroyed!"); > } > } > > void foo(S s){} > > void main(){ > foo(S()); > writeln("end the scope"); > } > --- > > prints: > > destroyed! > end the scope Right, exactly... So, what's wrong? > "Overload resolution > ... > This follows existing language rules. No change is proposed here." > > Yes, you _are_ proposing a change. Right now rvalues "prefer" the by-value overload because the ref overload _does not match at all_. Now you make it match both, so you are adding additional disambiguation rules. You need to be more explicit about those. Oh right... yeah okay, I'll tweak the language. > Note that lvalues prefer the ref overload because the ref overload is more specialized. The new rule is the only instance where a less specialized overload is preferred. I've never heard any discussion involving the term 'specialised', or seen any definition where overloading prefers a "more specialised' version... is that a thing? Given: void f(int); void f(const(int)); f(10); That calls the 'int' one, but it could call either one... that's definitely not choosing a 'more specialised' match. > You also need to specify the interactions with matching levels (https://dlang.org/spec/function.html#function-overloading): > > E.g., your DIP is compatible with the following behavior: > > --- > import std.stdio; > > struct S{} > void fun(S){ writeln("A"); } > void fun(ref const(S)){ writeln("B"); } > > void main(){ > fun(S()); // calls A > S s; > fun(s); // calls A > > const(S) t; > fun(t); // calls B > fun(const(S)()); // calls B > } > --- > > The first example will cause friction when people try to add an explicit rvalue overload alongside a previous catch-it-all overload, the second example shows a breaking language change. > > You cannot "fix" the first example without introducing breaking language changes. The above code compiles and runs in current D. > > This just smells bad. Remove the "const" requirement. This is very compelling reason to remove the const. |
March 28, 2018 Re: rvalues -> ref (yup... again!) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Timon Gehr | On 28 March 2018 at 05:38, Timon Gehr via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
> On 27.03.2018 22:25, Rubn wrote:
>>
>>
>> D already has move semantics, an easy solution to this is to just use another keyword. It doesn't have to bind to const ref to get what is desired:
>>
>> // what was suggested in the original DIP, since scope is being used for
>> something else now
>> void foo(@temp ref value)
>> {
>> }
>>
>> Now you don't have this problem. You only get this behavior when you basically say you don't care whether it is a temporary or not.
>
> Another benefit of this solution is that the overload resolution rules are
> obvious. foo(@temp ref T value) is less specialized than both foo(T value)
> and foo(ref T value).
>
> @Manu: Consider this.
This defeats the entire point to me. I want symmetrical calling code in all cases... the current edges are a massive pain in the arse. In the event of yet-another-attribute, then we just shift the set of edge cases onto that attribute instead, and it makes no practical difference in the end.
|
March 28, 2018 Re: rvalues -> ref (yup... again!) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Manu | On 28.03.2018 20:20, Manu wrote: > On 28 March 2018 at 05:22, Timon Gehr via Digitalmars-d > <digitalmars-d@puremagic.com> wrote: >> On 27.03.2018 20:14, Manu wrote: >>> >>> That's exactly what I've been saying. For like, 9 years.. >>> It looks like this: >>> >>> https://github.com/TurkeyMan/DIPs/blob/ref_args/DIPs/DIP1xxx-rval_to_ref.md >>> (contribution appreciated) >> >> >> "Temporary destruction >> Destruction of any temporaries occurs naturally at the end of the scope, as >> usual." >> >> This is actually unusual. >> ... > ... > So, what's wrong? > ... In my example, the temporary is destroyed after/at the end of the function call, not at the end of the scope. Re-reading the DIP, I think you meant the right thing, but the wording is a bit confusing. Maybe just clarify that "the scope" is the one your rewrite introduces implicitly, or explicitly state that the lifetime ends at the end of the function call. > >> "Overload resolution >> ... > >> Note that lvalues prefer the ref overload because the ref overload is more >> specialized. The new rule is the only instance where a less specialized >> overload is preferred. > > I've never heard any discussion involving the term 'specialised', or > seen any definition where overloading prefers a "more specialised' > version... is that a thing? > Given: > void f(int); > void f(const(int)); > f(10); > > That calls the 'int' one, but it could call either one... The overload resolution rules in D have four different matching levels: - exact match - match with type qualifier conversion - match with general implicit conversion - no match The matching level for one overload is the minimal matching level for any argument. In your example f(int) matches exactly, but f(const(int)) matches with type qualifier conversion, therefore f(int) is chosen as it is the unique function that matches best. Only if two overloads match with the same best level is specialization used. An overload A is more specialized than another overload B if we can call B with all arguments with which we can call A. As it is possible to call a by-value function with an lvalue or an rvalue, but ref cannot be called with an rvalue, ref is more specialized. > that's definitely not choosing a 'more specialised' match. > ... Implicit conversions are ignored when checking for specialization so, yes, here both functions are equally specialized. However, f(int*) is more specialized than f(const(int)*): --- import std.stdio; void f(int* a){ writeln("A"); } void f(const(int)* b){ writeln("B"); } void main(){ f(new immutable(int)); // guess what this prints. :) } --- |
March 29, 2018 Re: rvalues -> ref (yup... again!) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Manu | On Saturday, 24 March 2018 at 17:34:09 UTC, Manu wrote:
> You mutate a temporary that times out at the end of the statement...
> your statement is never assigned to anything, and has no effect.
That is solved by having the ref function return its argument (so it can be chained):
struct S;
ref S modify(return ref S s);
S(data).modify.writeln;
This rvalue pattern would be disallowed if argument `s` was const. Why not make `modify` just return a copy then, maybe the optimizer could remove the copy? So that you can also use it with lvalues:
S s;
modify(s);
|
March 29, 2018 Re: rvalues -> ref (yup... again!) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Manu | Just to be sure it does not got los: You know that you can avoid the temp/copy if you add one method to your struct, yes? ---- import std.stdio; struct Big { string name; float[1000] values; this(string name) { this.name = name; } @disable this(this); ref auto byRef() inout { return this; } } void foo(ref const Big b) { writeln(b.name); } void main() { Big b = Big("#1"); foo(b); foo(Big("#2").byRef); } ---- That works like a charm and avoids any need for rvalue references. Just add it as mixin template in Phobos or something like that. |
March 29, 2018 Re: rvalues -> ref (yup... again!) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Dgame | On Thursday, 29 March 2018 at 19:11:30 UTC, Dgame wrote:
> Just to be sure it does not got los: You know that you can avoid the temp/copy if you add one method to your struct, yes?
>
> ----
> import std.stdio;
>
> struct Big {
> string name;
> float[1000] values;
>
> this(string name) {
> this.name = name;
> }
>
> @disable
> this(this);
>
> ref auto byRef() inout {
> return this;
> }
> }
>
> void foo(ref const Big b) {
> writeln(b.name);
> }
>
> void main() {
> Big b = Big("#1");
> foo(b);
> foo(Big("#2").byRef);
> }
> ----
>
> That works like a charm and avoids any need for rvalue references. Just add it as mixin template in Phobos or something like that.
Doesn't work with built-in types like float.
Just adds bloat for operators like opBinary if you want that to be ref.
foo((a.byRef + b.byRef * c.byRef).byRef)
// vs
foo(a + b * c);
It's kind of funny all this talk about allowing temporaries to bind to refs being messy, yet you can already bind a temporary to a ref in a messy way using that.
|
Copyright © 1999-2021 by the D Language Foundation