September 06, 2019
On Friday, 6 September 2019 at 15:18:24 UTC, Suleyman wrote:
> On Thursday, 5 September 2019 at 22:57:27 UTC, kinke wrote:
>> [...]
>
> Seems workable. It only affects the case of moving an lvalue to an value parameter. But here are my observations:
>
> 1. `move` should always do what `forward` did in your example. It shouldn't touch the lvalue, for simply removing a call to the move constructor, because T.init is not always considered a valid state and resetting to T.init is a "move" operation but an uncontrolled one which leaves the object in an invalid state. Which why making __move behave just like rvalue ref is more sensible like your first attempt did.

The problem here is the extended lifetime of the by-value parameter. Example:

T global;

void foo(T param) { /* make T own a huge buffer */ }

void caller()
{
    foo(move(global));
    // without destructing and resetting to T.init, `param` continues to live
    // and keeps the huge buffer alive, until `global` is reassigned or destructed
}

> 2. What you have proposed so far only replaces rvalue ref parameters. You haven't tackled rvalue ref returns. What do you propose as an alternative.

I never thought about that, I don't think I've ever returned an rvalue ref in C++. Give me some time to think about it.
September 06, 2019
On Friday, 6 September 2019 at 17:46:51 UTC, Manu wrote:
> It's relevant because the calling convention needs to be defined, and
> it's volatile with respect to architecture.
> I don't think defining a custom ABI this way is a good path. It's just
> different problems, and ABI problems are always bad problems.

It'd just make the D ABI conform to the C++ ABI wrt. passing non-PODs and large PODs by value, making the compilers adhere to the D spec *and* fixing C++ interop when passing such structs by value across language barriers, soomething you're surely looking forward too.
So the proposed ABI change (pass by ref instead of on stack & let callER destruct it) is the exact opposite of what you seem to see it as - it's streamlining the D ABI with C++. And it's orthogonal to the proposed move/forward intrinsics.
September 06, 2019
On Friday, 6 September 2019 at 18:13:03 UTC, kinke wrote:
> It'd just make the D ABI conform to the C++ ABI wrt. passing non-PODs and large PODs by value, making the compilers adhere to the D spec *and* fixing C++ interop when passing such structs by value across language barriers, soomething you're surely looking forward too.
> So the proposed ABI change (pass by ref instead of on stack & let callER destruct it) is the exact opposite of what you seem to see it as - it's streamlining the D ABI with C++. And it's orthogonal to the proposed move/forward intrinsics.

Dammit, I stand corrected again, just checked the clang ABI on Posix x86_64 - it does pass large PODs on the stack. So please discard my mentionings of large PODs (those are passed by ref on Win64...) in this thread, my intention was always to firstly adopt the respective C++ ABI for each target, and secondly to exploit the low-level-by-ref-passing of non-PODs (AFAIK, what C++ does for all targets) when moving lvalue arguments to by-value params (by eliding the temporary and passing the lvalue ref directly), thirdly hoping to cover enough use cases in order NOT to have to introduce rvalue refs in the language.
September 06, 2019
On Fri, Sep 6, 2019 at 11:45 AM kinke via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>
> On Friday, 6 September 2019 at 18:13:03 UTC, kinke wrote:
> > It'd just make the D ABI conform to the C++ ABI wrt. passing
> > non-PODs and large PODs by value, making the compilers adhere
> > to the D spec *and* fixing C++ interop when passing such
> > structs by value across language barriers, soomething you're
> > surely looking forward too.
> > So the proposed ABI change (pass by ref instead of on stack &
> > let callER destruct it) is the exact opposite of what you seem
> > to see it as - it's streamlining the D ABI with C++. And it's
> > orthogonal to the proposed move/forward intrinsics.
>
> Dammit, I stand corrected again, just checked the clang ABI on Posix x86_64 - it does pass large PODs on the stack. So please discard my mentionings of large PODs (those are passed by ref on Win64...) in this thread, my intention was always to firstly adopt the respective C++ ABI for each target, and secondly to exploit the low-level-by-ref-passing of non-PODs (AFAIK, what C++ does for all targets) when moving lvalue arguments to by-value params (by eliding the temporary and passing the lvalue ref directly), thirdly hoping to cover enough use cases in order NOT to have to introduce rvalue refs in the language.

'enough' is a subjective quantity; and I respect an effort to explore
avoiding rval ref's, but also don't be xenophobic about it. It's a
really good solution, and if we don't arrive at a distinctly better
one, then I think we should accept the state of the art, and not do
something different for the sake of doing something different.
I've thought about this a lot, I personally think C++'s rval ref's
were really well thought through, and I can't imagine a simple/better
design. I also see that the only major oddity in C++ (T&&, so called,
universal references) are addressed by `auto ref`, which feels much
less surprising and more obvious for users to me.

My email way up the feed listing all the cases feels robust to me. There's synergy with Andrei's `-preview`, and there are no changes to existing semantics, it's purely additive.
September 06, 2019
On Fri, Sep 6, 2019 at 11:00 AM kinke via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>
> On Friday, 6 September 2019 at 15:18:24 UTC, Suleyman wrote:
> > On Thursday, 5 September 2019 at 22:57:27 UTC, kinke wrote:
> >> [...]
> >
> > Seems workable. It only affects the case of moving an lvalue to an value parameter. But here are my observations:
> >
> > 1. `move` should always do what `forward` did in your example. It shouldn't touch the lvalue, for simply removing a call to the move constructor, because T.init is not always considered a valid state and resetting to T.init is a "move" operation but an uncontrolled one which leaves the object in an invalid state. Which why making __move behave just like rvalue ref is more sensible like your first attempt did.
>
> The problem here is the extended lifetime of the by-value parameter. Example:
>
> T global;
>
> void foo(T param) { /* make T own a huge buffer */ }
>
> void caller()
> {
>      foo(move(global));
>      // without destructing and resetting to T.init, `param`
> continues to live
>      // and keeps the huge buffer alive, until `global` is
> reassigned or destructed
> }
>
> > 2. What you have proposed so far only replaces rvalue ref parameters. You haven't tackled rvalue ref returns. What do you propose as an alternative.
>
> I never thought about that, I don't think I've ever returned an rvalue ref in C++. Give me some time to think about it.

There's another case where you can attribute a method with `@rvalue` to apply to the `this` reference. This should naturally work in D under the `@rvalue` proposal.

Ie:

struct S
{
  Thing member;
  ref Thing fun() { return member; }
  Thing fun() @rvalue { return move(member); }
}

This can be very valuable, and even more so in D where we use a lot of UFCS chains.
September 06, 2019
On Friday, 6 September 2019 at 19:25:57 UTC, Manu wrote:
> There's another case where you can attribute a method with `@rvalue` to apply to the `this` reference. This should naturally work in D under the `@rvalue` proposal.
>
> Ie:
>
> struct S
> {
>   Thing member;
>   ref Thing fun() { return member; }
>   Thing fun() @rvalue { return move(member); }
> }
>
> This can be very valuable, and even more so in D where we use a lot of UFCS chains.

Oh wow, this might be able to convince me, as returning an rvalue ref from a method invoked on an rvalue instance would allow absolutely perfect forwarding of members (no copy/move and preserving rvalue-ness):

struct S
{
  Thing member;
  ref Thing fun() { return member; }
  @rvalue ref Thing fun() @rvalue { return member; }
  // maybe we could even represent both at once via something like:
  // auto ref Thing fun()() { return member; }
}
September 06, 2019
On Friday, 6 September 2019 at 17:59:11 UTC, kinke wrote:
> The problem here is the extended lifetime of the by-value parameter. Example:
>
> T global;
>
> void foo(T param) { /* make T own a huge buffer */ }
>
> void caller()
> {
>     foo(move(global));
>     // without destructing and resetting to T.init, `param` continues to live
>     // and keeps the huge buffer alive, until `global` is reassigned or destructed
> }

I see no problem with that. That is valid behavior with rvalue ref. You're trying to get the benefit of rvalue ref you can't get the full benefit without the draw backs. There is no in middle solution, either pass by ref or call the move constructor.

September 06, 2019
On Friday, 6 September 2019 at 19:50:30 UTC, kinke wrote:
> On Friday, 6 September 2019 at 19:25:57 UTC, Manu wrote:
>> There's another case where you can attribute a method with `@rvalue` to apply to the `this` reference. This should naturally work in D under the `@rvalue` proposal.
>>
>> Ie:
>>
>> struct S
>> {
>>   Thing member;
>>   ref Thing fun() { return member; }
>>   Thing fun() @rvalue { return move(member); }
>> }
>>
>> This can be very valuable, and even more so in D where we use a lot of UFCS chains.
>
> Oh wow, this might be able to convince me, as returning an rvalue ref from a method invoked on an rvalue instance would allow absolutely perfect forwarding of members (no copy/move and preserving rvalue-ness):
>
> struct S
> {
>   Thing member;
>   ref Thing fun() { return member; }
>   @rvalue ref Thing fun() @rvalue { return member; }
>   // maybe we could even represent both at once via something like:
>   // auto ref Thing fun()() { return member; }
> }

I'm sorry but this looks like a bad idea. Moving selective parts of S simply because it's all marked for move is bad. What happens when the member is moved separately thus leaving an "empty" spot inside S then S itself is moved. That would result is a half valid object.

September 06, 2019
On Fri, Sep 6, 2019 at 2:15 PM Suleyman via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>
> On Friday, 6 September 2019 at 19:50:30 UTC, kinke wrote:
> > On Friday, 6 September 2019 at 19:25:57 UTC, Manu wrote:
> >> There's another case where you can attribute a method with `@rvalue` to apply to the `this` reference. This should naturally work in D under the `@rvalue` proposal.
> >>
> >> Ie:
> >>
> >> struct S
> >> {
> >>   Thing member;
> >>   ref Thing fun() { return member; }
> >>   Thing fun() @rvalue { return move(member); }
> >> }
> >>
> >> This can be very valuable, and even more so in D where we use a lot of UFCS chains.
> >
> > Oh wow, this might be able to convince me, as returning an rvalue ref from a method invoked on an rvalue instance would allow absolutely perfect forwarding of members (no copy/move and preserving rvalue-ness):
> >
> > struct S
> > {
> >   Thing member;
> >   ref Thing fun() { return member; }
> >   @rvalue ref Thing fun() @rvalue { return member; }
> >   // maybe we could even represent both at once via something
> > like:
> >   // auto ref Thing fun()() { return member; }
> > }
>
> I'm sorry but this looks like a bad idea. Moving selective parts of S simply because it's all marked for move is bad. What happens when the member is moved separately thus leaving an "empty" spot inside S then S itself is moved. That would result is a half valid object.

S is an rvalue; there are no other references to S. That's the point.
September 06, 2019
On Fri, Sep 6, 2019 at 3:32 PM Manu <turkeyman@gmail.com> wrote:
>
> On Fri, Sep 6, 2019 at 2:15 PM Suleyman via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
> >
> > On Friday, 6 September 2019 at 19:50:30 UTC, kinke wrote:
> > > On Friday, 6 September 2019 at 19:25:57 UTC, Manu wrote:
> > >> There's another case where you can attribute a method with `@rvalue` to apply to the `this` reference. This should naturally work in D under the `@rvalue` proposal.
> > >>
> > >> Ie:
> > >>
> > >> struct S
> > >> {
> > >>   Thing member;
> > >>   ref Thing fun() { return member; }
> > >>   Thing fun() @rvalue { return move(member); }
> > >> }
> > >>
> > >> This can be very valuable, and even more so in D where we use a lot of UFCS chains.
> > >
> > > Oh wow, this might be able to convince me, as returning an rvalue ref from a method invoked on an rvalue instance would allow absolutely perfect forwarding of members (no copy/move and preserving rvalue-ness):
> > >
> > > struct S
> > > {
> > >   Thing member;
> > >   ref Thing fun() { return member; }
> > >   @rvalue ref Thing fun() @rvalue { return member; }
> > >   // maybe we could even represent both at once via something
> > > like:
> > >   // auto ref Thing fun()() { return member; }
> > > }
> >
> > I'm sorry but this looks like a bad idea. Moving selective parts of S simply because it's all marked for move is bad. What happens when the member is moved separately thus leaving an "empty" spot inside S then S itself is moved. That would result is a half valid object.
>
> S is an rvalue; there are no other references to S. That's the point.

This doesn't come up very often, but when you need it, it has been a massive performance opportunity for us.
1 2 3 4 5 6 7 8 9 10 11 12