www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Move Constructors - Converting Lvalues to Rvalues

reply Walter Bright <newshound2 digitalmars.com> writes:
I've been implementing move constructors. They are working, at least on my test 
cases so far. They are distinguished by a copy constructor takes its argument
by 
ref, and a move constructor by value:

```
struct S
{
     this(ref S); // copy constructor
     this(S);     // move constructor
}
```
So far, so good. Consider:
```
void phone(S s)
{
     S t = s;  // copy constructor
}
```
But what if we want to initialize `t` via a move constructor? Somehow, `s` has 
to be converted from an lvalue to an rvalue. This is done via the function
rvalue():
```
S rvalue(ref S s) { return s; }

S t = rvalue(s);
```
Unfortunately, rvalue() gives us this:
```
S __copytmp = 0;
this(&__copytmp,&s);  // copy construction of s
*__H1D1 = __copytmp;
return __H1D1;
```
which is correct, but not what we want, which is:
```
return &s;
```
and also not pass an extra hidden parameter for the return value, aka __H1D1.

I have thought of several ways to do this, none of which seem particularly 
attractive as they are all not expressible in conventional D. I won't say what 
they are in order to not bias anyone.

So, does anyone have any brilliant ideas for how to make the compiler treat an 
lvalue as an rvalue?

P.S. C++ uses the std::move function to do it:

https://learn.microsoft.com/en-us/cpp/standard-library/utility-functions?view=msvc-170#move

which relies on rvalue references:

https://learn.microsoft.com/en-us/cpp/cpp/rvalue-reference-declarator-amp-amp?view=msvc-170

which is a major feature which I prefer to avoid.
Sep 30
next sibling parent reply Bastiaan Veelo <Bastiaan Veelo.net> writes:
On Monday, 30 September 2024 at 16:05:16 UTC, Walter Bright wrote:
[...]
 P.S. C++ uses the std::move function to do it:

 https://learn.microsoft.com/en-us/cpp/standard-library/utility-functions?view=msvc-170#move

 which relies on rvalue references:

 https://learn.microsoft.com/en-us/cpp/cpp/rvalue-reference-declarator-amp-amp?view=msvc-170

 which is a major feature which I prefer to avoid.
I suppose `-preview=rvaluerefparam` is not relevant here, right? -- Bastiaan.
Sep 30
next sibling parent Guillaume Piolat <guillaume.piolat gmail.com> writes:
On Monday, 30 September 2024 at 17:27:25 UTC, Bastiaan Veelo 
wrote:
 I suppose `-preview=rvaluerefparam` is not relevant here, right?

 -- Bastiaan.
Yes, I kinda expected something like std::move explicit thing.
Sep 30
prev sibling next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 9/30/2024 10:27 AM, Bastiaan Veelo wrote:
 I suppose `-preview=rvaluerefparam` is not relevant here, right?
It should be irrelevant here.
Sep 30
prev sibling parent Manu <turkeyman gmail.com> writes:
On Tue, 1 Oct 2024, 03:31 Bastiaan Veelo via Digitalmars-d, <
digitalmars-d puremagic.com> wrote:

 On Monday, 30 September 2024 at 16:05:16 UTC, Walter Bright wrote:
 [...]
 P.S. C++ uses the std::move function to do it:
https://learn.microsoft.com/en-us/cpp/standard-library/utility-functions?view=msvc-170#move
 which relies on rvalue references:
https://learn.microsoft.com/en-us/cpp/cpp/rvalue-reference-declarator-amp-amp?view=msvc-170
 which is a major feature which I prefer to avoid.
I suppose `-preview=rvaluerefparam` is not relevant here, right?
It's essential for this design to work; the logic in that preview allows appropriate selection of copy/move overloads.
Oct 02
prev sibling next sibling parent reply Dukc <ajieskola gmail.com> writes:
On Monday, 30 September 2024 at 16:05:16 UTC, Walter Bright wrote:
 So, does anyone have any brilliant ideas for how to make the 
 compiler treat an lvalue as an rvalue?
Doesn't `core.lifetime.move` already do this?
Sep 30
next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 9/30/2024 10:49 AM, Dukc wrote:
 Doesn't `core.lifetime.move` already do this?
Since the compiler doesn't know about it, the potential for optimizations, lifetime analysis, etc., is lost.
Sep 30
next sibling parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 01/10/2024 4:22 PM, Walter Bright wrote:
 On 9/30/2024 10:49 AM, Dukc wrote:
 Doesn't `core.lifetime.move` already do this?
Since the compiler doesn't know about it, the potential for optimizations, lifetime analysis, etc., is lost.
If only there was a way that was backwards compatible, for us to annotate on a parameter that it invalidates the input in the form of a move. Perhaps an attribute, like `` move`` in core.attributes would be sufficient for this task ;)
Sep 30
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 9/30/2024 8:29 PM, Richard (Rikki) Andrew Cattermole wrote:
 Perhaps an attribute, like `` move`` in core.attributes would be sufficient
for 
 this task ;)
It's an interesting idea, but a lot of details would need to be worked out. For instance, the core.attributes.move is a version of the move assignment operator, not move construction. Then there is the problem of distinguishing between a move constructor and a copy constructor - how do they overload against each other? There's what a default move constructor should be. There's what happens when a field of a struct has a move constructor. And so on. BTW, I took a look at core.lifetime.move. It's hard to figure out just what it does, as there are vacuous forwardings to other templates. For example: ``` void move(T)(ref T source, ref T target) { moveImpl(target, source); } ``` Why?
Sep 30
next sibling parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 01/10/2024 7:36 PM, Walter Bright wrote:
 On 9/30/2024 8:29 PM, Richard (Rikki) Andrew Cattermole wrote:
 Perhaps an attribute, like `` move`` in core.attributes would be 
 sufficient for this task ;)
It's an interesting idea, but a lot of details would need to be worked out. For instance, the core.attributes.move is a version of the move assignment operator, not move construction.
Yes!
 Then there is the problem of 
 distinguishing between a move constructor and a copy constructor - how 
 do they overload against each other?
Why do you need to? Are they not the same constructor, differing only for the side effects to the caller? It is an assumption that the object you pass in by-ref will be the same object after the function call. ```d int* someObject = ...; int* obj = someObject; callMe(obj); // Does obj == someObject? ``` If you know that ``callMe`` moved the value into it, but never assigned to it, you can elide cleanup on ``obj``. A question I ask myself in any sort of proving topics is what assumptions are being held, and can under normal usage of `` safe`` code can these assumptions be invalidated, potentially desirably. I had a look at C++, it seems the biggest difference between a copy constructor and a move constructor is that a move constructor is mutable by-ref, whereas a copy is read only. Which could indeed be overloaded for in D. And yes, we could infer the difference in this regards in D too. It is what I have been calling "effectively-const". A rather useful thing for owner escape analysis, as it means methods on the owner that do not mutate are callable even if they cannot or have not been annotated ``const``!
Oct 01
next sibling parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
Now to get annoying again:

What is the difference between move assignment  move + reference 
counting, and move constructors?

```d
void caller() {
	T thing = ...; // rc: 1
	call1(thing);
	call2(thing);
	// ?reachable thing
	thing.opRCSub(); // elided due to having been moved
	thing.__dtor; // elided due to reachable type state
}

void call1(T thing) {
	thing.opRCAdd(); // elided due to pair
	thing.opRCSub(); // elided due to pair
	thing.__dtor;
}

void call2( move T thing) {
	thing.opRCAdd(); // elided due to  move
	thing.opRCSub();
	thing.__dtor;
}
```
Oct 01
prev sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 10/1/2024 1:59 AM, Richard (Rikki) Andrew Cattermole wrote:
 Why do you need to?
A move constructor just has to initialize the object. A move assignment has to destroy the target first, then move. They are fundamentally different.
Oct 01
prev sibling parent Manu <turkeyman gmail.com> writes:
On Tue, 1 Oct 2024, 17:03 Walter Bright via Digitalmars-d, <
digitalmars-d puremagic.com> wrote:

 On 9/30/2024 8:29 PM, Richard (Rikki) Andrew Cattermole wrote:
 Perhaps an attribute, like `` move`` in core.attributes would be
sufficient for
 this task ;)
It's an interesting idea, but a lot of details would need to be worked out. For instance, the core.attributes.move is a version of the move assignment operator, not move construction. Then there is the problem of distinguishing between a move constructor and a copy constructor - how do they overload against each other? There's what a default move constructor should be. There's what happens when a field of a struct has a move constructor. And so on. BTW, I took a look at core.lifetime.move. It's hard to figure out just what it does, as there are vacuous forwardings to other templates. For example: ``` void move(T)(ref T source, ref T target) { moveImpl(target, source); } ``` Why?
Because D is so fundamentally broken in this department. Work your way through a clear understanding of core.lifetime, and you should become convinced how important this work you are doing actually it's!
Oct 02
prev sibling parent reply Dukc <ajieskola gmail.com> writes:
On Tuesday, 1 October 2024 at 03:22:33 UTC, Walter Bright wrote:
 On 9/30/2024 10:49 AM, Dukc wrote:
 Doesn't `core.lifetime.move` already do this?
Since the compiler doesn't know about it, the potential for optimizations, lifetime analysis, etc., is lost.
You can make it to work with a compiler instrinsic. The intrinsic would either be an UDA applied to `move` that allows the compiler to make those assumptions, or an intrinsic function that works along the lines of `nove` and `moveEmplace`. The public DRuntime functions would forward or alias to the instrinsic function.
Oct 01
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 10/1/2024 12:25 AM, Dukc wrote:
 You can make it to work with a compiler instrinsic. The intrinsic would either 
 be an UDA applied to `move` that allows the compiler to make those
assumptions, 
 or an intrinsic function that works along the lines of `nove` and
`moveEmplace`. 
 The public DRuntime functions would forward or alias to the instrinsic
function.
move() is an unhappy maze of templates. We need something a lot simpler.
Oct 01
parent Dukc <ajieskola gmail.com> writes:
On Tuesday, 1 October 2024 at 17:41:34 UTC, Walter Bright wrote:
 On 10/1/2024 12:25 AM, Dukc wrote:
 You can make it to work with a compiler instrinsic. The 
 intrinsic would either be an UDA applied to `move` that allows 
 the compiler to make those assumptions, or an intrinsic 
 function that works along the lines of `nove` and 
 `moveEmplace`. The public DRuntime functions would forward or 
 alias to the instrinsic function.
move() is an unhappy maze of templates. We need something a lot simpler.
If we'll have a compiler intrinsic function, `move` can be made much simpler by having it simply call the compiler intrinsic, or alias to it.
Oct 02
prev sibling parent Manu <turkeyman gmail.com> writes:
On Tue, 1 Oct 2024, 03:51 Dukc via Digitalmars-d, <
digitalmars-d puremagic.com> wrote:

 On Monday, 30 September 2024 at 16:05:16 UTC, Walter Bright wrote:
 So, does anyone have any brilliant ideas for how to make the
 compiler treat an lvalue as an rvalue?
Doesn't `core.lifetime.move` already do this?
That entire file is a malignant cancer and it must be murdered in the fires of hell.

Oct 02
prev sibling next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 9/30/24 18:05, Walter Bright wrote:
 
 So, does anyone have any brilliant ideas for how to make the compiler 
 treat an lvalue as an rvalue?
Well, with DIP1040 this is automatically the case for last uses. So in your code example: ``` void phone(S s) { S t = s; // copy constructor } ``` The comment is actually inaccurate with DIP1040. With DIP1040, this is a move. Otherwise, maybe expose explicit moves. This is useful generally. Can just be `move(x)`, where `move` is either special, or is a function with a "move by default" attribute on its parameter, as we discussed previously.
Sep 30
next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 9/30/2024 12:37 PM, Timon Gehr wrote:
 On 9/30/24 18:05, Walter Bright wrote:
 So, does anyone have any brilliant ideas for how to make the compiler treat an 
 lvalue as an rvalue?
Well, with DIP1040 this is automatically the case for last uses.
The trouble with last uses is detecting them. It's a variation on live variable DFA, which the optimizer does for variables. Indirections cannot be reliably tracked, and variables which have their address taken similarly can't reliably have DFA done on them. In any case, I decided for at least the first implementation to require an explicit "convert to rvalue" operation to get the benefits of a move construction. I'm pretty sure it will deliver most of what we want.
Sep 30
prev sibling parent reply Per =?UTF-8?B?Tm9yZGzDtnc=?= <per.nordlow gmail.com> writes:
On Monday, 30 September 2024 at 19:37:04 UTC, Timon Gehr wrote:
 Otherwise, maybe expose explicit moves. This is useful 
 generally. Can just be `move(x)`, where `move` is either 
 special, or is a function with a "move by default" attribute on 
 its parameter, as we discussed previously.
Considering the template-complexity of the current implementation of `core.lifetime.move` it will beneficial compile-time wise to make `move` become a builtin. Ideally I would like to have a special syntax for converting an l-value to an r-value.
Sep 30
next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 9/30/2024 9:55 PM, Per Nordlöw wrote:
 Ideally I would like to have a special syntax for converting an 
 l-value to an r-value.
Manu suggested an intrinsic, something like: ``` __rvalue(s) ``` where __rvalue is a keyword.
Sep 30
parent Per =?UTF-8?B?Tm9yZGzDtnc=?= <per.nordlow gmail.com> writes:
On Tuesday, 1 October 2024 at 06:39:21 UTC, Walter Bright wrote:
 Manu suggested an intrinsic, something like:

 ```
 __rvalue(s)
 ```
 where __rvalue is a keyword.
Nice!
Oct 01
prev sibling next sibling parent reply Dukc <ajieskola gmail.com> writes:
On Tuesday, 1 October 2024 at 04:55:43 UTC, Per Nordlöw wrote:
 Ideally I would like to have a special syntax for converting an 
 l-value to an r-value.
Why special syntax? `move` is a DRuntime function so it's expected that it can have special semantics, even without special syntax.
Oct 01
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 10/1/2024 12:27 AM, Dukc wrote:
 Why special syntax? `move` is a DRuntime function so it's expected that it can 
 have special semantics, even without special syntax.
Converting to an rvalue is an enabler of other functions, too, not just move. __rvalue is a building block, not a complete function.
Oct 01
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 10/1/24 19:37, Walter Bright wrote:
 On 10/1/2024 12:27 AM, Dukc wrote:
 Why special syntax? `move` is a DRuntime function so it's expected 
 that it can have special semantics, even without special syntax.
Converting to an rvalue is an enabler of other functions, too, not just move. __rvalue is a building block, not a complete function.
`move` currently accepts an lvalue and moves it into the return value, leaving `.init` in the argument. I guess the new implementation you have in mind is something like the following? ```d auto move(T)(ref T arg)=>__rvalue(arg); ```
Oct 01
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 10/1/2024 1:15 PM, Timon Gehr wrote:
 I guess the new implementation you have in mind is something like the
following?
 
 ```d
 auto move(T)(ref T arg)=>__rvalue(arg);
 ```
Not exactly. __rvalue would also convert an rvalue to an rvalue.
Oct 02
parent reply Manu <turkeyman gmail.com> writes:
On Thu, 3 Oct 2024 at 03:21, Walter Bright via Digitalmars-d <
digitalmars-d puremagic.com> wrote:

 On 10/1/2024 1:15 PM, Timon Gehr wrote:
 I guess the new implementation you have in mind is something like the
following?
 ```d
 auto move(T)(ref T arg)=>__rvalue(arg);
 ```
Not exactly. __rvalue would also convert an rvalue to an rvalue.
-preview=rvaluerefparams addresses this; it allows an rvalue to be supplied to the lvalue there. It's actually an essential mechanic to this whole thing, because it will allow the appropriate selection of copy/move overloads where a type may define either one, or both. If a copy and/or move constructor exists, it needs to select the proper one, and the -preview handles that properly as is.
Oct 02
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 10/3/24 01:07, Manu wrote:
 On Thu, 3 Oct 2024 at 03:21, Walter Bright via Digitalmars-d 
 <digitalmars-d puremagic.com <mailto:digitalmars-d puremagic.com>> wrote:
 
     On 10/1/2024 1:15 PM, Timon Gehr wrote:
      > I guess the new implementation you have in mind is something like
     the following?
      >
      > ```d
      > auto move(T)(ref T arg)=>__rvalue(arg);
      > ```
 
     Not exactly. __rvalue would also convert an rvalue to an rvalue.
 
 
 -preview=rvaluerefparams addresses this; it allows an rvalue to be 
 supplied to the lvalue there. It's actually an essential mechanic to 
 this whole thing, because it will allow the appropriate selection of 
 copy/move overloads where a type may define either one, or both. If a 
 copy and/or move constructor exists, it needs to select the proper one, 
 and the -preview handles that properly as is.
`-preview=rvaluerefparam` allows you to treat an rvalue as an lvalue implicitly in some contexts. `__rvalue` allows you to treat an lvalue explicitly as an rvalue, so it will be moved. I fail to see how those are related.
Oct 02
parent reply Manu <turkeyman gmail.com> writes:
On Thu, 3 Oct 2024 at 10:06, Timon Gehr via Digitalmars-d <
digitalmars-d puremagic.com> wrote:

 On 10/3/24 01:07, Manu wrote:
 On Thu, 3 Oct 2024 at 03:21, Walter Bright via Digitalmars-d
 <digitalmars-d puremagic.com <mailto:digitalmars-d puremagic.com>>
wrote:
     On 10/1/2024 1:15 PM, Timon Gehr wrote:
      > I guess the new implementation you have in mind is something like
     the following?
      >
      > ```d
      > auto move(T)(ref T arg)=>__rvalue(arg);
      > ```

     Not exactly. __rvalue would also convert an rvalue to an rvalue.


 -preview=rvaluerefparams addresses this; it allows an rvalue to be
 supplied to the lvalue there. It's actually an essential mechanic to
 this whole thing, because it will allow the appropriate selection of
 copy/move overloads where a type may define either one, or both. If a
 copy and/or move constructor exists, it needs to select the proper one,
 and the -preview handles that properly as is.
`-preview=rvaluerefparam` allows you to treat an rvalue as an lvalue implicitly in some contexts. `__rvalue` allows you to treat an lvalue explicitly as an rvalue, so it will be moved. I fail to see how those are related.
__rvalue() needs to be callable with an lvalue or an rvalue. That's the only specific interaction on this matter, but beyond that with respect to move semantics in practice; in your application, you will encounter types with copy and/or move constructors/assign operators, general overloads, etc... you need to be able to pass the values that you have and your code needs to work without friction. If there is a copy constructor and no move constructor for instance (very common situation) and you pass an rvalue, the code needs to compile. Later if you add a move constructor, it will automatically select that as the appropriate choice.
Oct 02
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 10/3/24 02:22, Manu wrote:
 On Thu, 3 Oct 2024 at 10:06, Timon Gehr via Digitalmars-d <digitalmars- 
 d puremagic.com <mailto:digitalmars-d puremagic.com>> wrote:
 
     On 10/3/24 01:07, Manu wrote:
      > On Thu, 3 Oct 2024 at 03:21, Walter Bright via Digitalmars-d
      > <digitalmars-d puremagic.com <mailto:digitalmars-d puremagic.com>
     <mailto:digitalmars-d puremagic.com <mailto:digitalmars-
     d puremagic.com>>> wrote:
      >
      >     On 10/1/2024 1:15 PM, Timon Gehr wrote:
      >      > I guess the new implementation you have in mind is
     something like
      >     the following?
      >      >
      >      > ```d
      >      > auto move(T)(ref T arg)=>__rvalue(arg);
      >      > ```
      >
      >     Not exactly. __rvalue would also convert an rvalue to an rvalue.
      >
      >
      > -preview=rvaluerefparams addresses this; it allows an rvalue to be
      > supplied to the lvalue there. It's actually an essential mechanic to
      > this whole thing, because it will allow the appropriate selection of
      > copy/move overloads where a type may define either one, or both.
     If a
      > copy and/or move constructor exists, it needs to select the
     proper one,
      > and the -preview handles that properly as is.
 
     `-preview=rvaluerefparam` allows you to treat an rvalue as an lvalue
     implicitly in some contexts.
 
     `__rvalue` allows you to treat an lvalue explicitly as an rvalue, so it
     will be moved.
 
     I fail to see how those are related.
 
 
 __rvalue() needs to be callable with an lvalue or an rvalue.
 That's the only specific interaction on this matter, but beyond that 
 with respect to move semantics in practice; in your application, you 
 will encounter types with copy and/or move constructors/assign 
 operators, general overloads, etc... you need to be able to pass the 
 values that you have and your code needs to work without friction.
 If there is a copy constructor and no move constructor for instance 
 (very common situation) and you pass an rvalue, the code needs to 
 compile. Later if you add a move constructor, it will automatically 
 select that as the appropriate choice.
Well, I think you can interpret this as overload resolution between a `ref` and non-`ref` overload as well, which is a bit more powerful because the callee can actually determine which one it was. Don't get me wrong, I like `-preview=rvaluerefparam`, but I don't think it helps or hurts us here.
Oct 02
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 10/3/24 02:42, Timon Gehr wrote:
 Later if you add a move constructor, it will automatically select that 
 as the appropriate choice.
About this, actually it might not do so with `-preview=rvaluerefparam` sometimes because the rvalue-ness of the argument is not known statically. Basically, let's assume S has both a copy constructor and a move constructor ```d void byref(ref S s){ // ABI: passed by reference, caller cleanup S t = s; // calls copy constructor // ... } ``` ```d void byval(S s){ // ABI: passed by value, callee cleanup S t = s; // calls move constructor (with DIP1040) // ... } ``` ```d void bymove( move S s){ // ABI: passed by reference, callee cleanup S t = s; // calls move constructor (with DIP1040) // ... } ``` ```d byref(s); // passes &s, move constructor called 0 times overall byval(move(s)); // moves s, has to call move constructor, move constructor called twice overall bymove(s); // semantically moves, actually passes &s. move constructor is only called once, in the function body ``` Of course, you can do: ```d void explicitmove(ref S s){ S t = move(s); } ``` That would have similar semantics in practice as the `bymove` (though the caller will always call the destructor with `explicitmove`, while with `bymove`, the caller may be able to elide it). However, you had to be explicit about it. It is not true that the code is upgraded for free, because `byref` actually assumes that the caller is responsible for cleaning up the parameter. This is true whether it is an rvalue or an lvalue.
Oct 02
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 10/3/24 02:57, Timon Gehr wrote:
 On 10/3/24 02:42, Timon Gehr wrote:
 Later if you add a move constructor, it will automatically select that 
 as the appropriate choice. 
(Sry, Thunderbird did not properly cite this, this was written by Manu, not me.)
Oct 02
prev sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
One thing that is a bit annoying is:

```d
void byval(S s);
void bymove( move S s);

byval(move(s)); // has to move on call, caller can in principle elide 
the .init blit

bymove(s); // does not have to move on call, but callee has to leave 
argument in `.init` state and this reinitialization cannot be elided 
without inlining
```

Of course, it would be even better if `.init` were not required at all...
Oct 02
prev sibling next sibling parent Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Tuesday, 1 October 2024 at 17:37:37 UTC, Walter Bright wrote:
 On 10/1/2024 12:27 AM, Dukc wrote:
 Why special syntax? `move` is a DRuntime function so it's 
 expected that it can have special semantics, even without 
 special syntax.
Converting to an rvalue is an enabler of other functions, too, not just move. __rvalue is a building block, not a complete function.
The __rvalue route makes sense and is intuitive imo.
Oct 01
prev sibling parent Dukc <ajieskola gmail.com> writes:
On Tuesday, 1 October 2024 at 17:37:37 UTC, Walter Bright wrote:
 On 10/1/2024 12:27 AM, Dukc wrote:
 Why special syntax? `move` is a DRuntime function so it's 
 expected that it can have special semantics, even without 
 special syntax.
Converting to an rvalue is an enabler of other functions, too, not just move. __rvalue is a building block, not a complete function.
`move` _is_ the `__rvalue` building block, at least in principle. It accepts an lvalue and returns a rvalue, without executing the destructor or move constructor of the type in question. It does set the lvalue to it's `.init` value, but this can be elided by the optimiser if the lvalue is not used again, assuming the destructor doesn't side-effect in any way.
Oct 02
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 10/1/24 06:55, Per Nordlöw wrote:
 On Monday, 30 September 2024 at 19:37:04 UTC, Timon Gehr wrote:
 Otherwise, maybe expose explicit moves. This is useful generally. Can 
 just be `move(x)`, where `move` is either special, or is a function 
 with a "move by default" attribute on its parameter, as we discussed 
 previously.
Considering the template-complexity of the current implementation of `core.lifetime.move` it will beneficial compile-time wise to make `move` become a builtin. Ideally I would like to have a special syntax for converting an l-value to an r-value.
I think in case we did go the function route, I think any implementation of `move` that is much more complex than the following is a failure: ```d auto move(T)( moved T arg)=>arg; ``` Of course, going with a built-in makes sense too.
Oct 01
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 10/1/2024 1:06 PM, Timon Gehr wrote:
 I think in case we did go the function route, I think any implementation of 
 `move` that is much more complex than the following is a failure:
 
 ```d
 auto move(T)( moved T arg)=>arg;
 ```
The major difference between move and __rvalue is the former is attached to the parameter, and the latter is attached to the argument. This might seem a distinction without a difference, but this has large implications with how overloading works. For example, how do we distinguish a move constructor from a copy constructor? ``` this(ref S); // copy constructor this(S); // move constructor S s = t; // calls copy constructor (lvalue) S s = f(); // calls move constructor (rvalue) ``` The current overloading rules work out of the box, an rvalue goes for the move constructor, and an lvalue goes to the copy constructor. The problem here is when I want to move t into s. How do I get it to call the move constructor? ``` S move(ref S s) { return s; } // convert argument from lvalue to rvalue S s = move(t); ``` This works, however, it creates a copy of s and then moves the copy! There needs to be a way to tell the compiler to use the move construct, hence: ``` S s = __rvalue(t); ``` All __rvalue does is flag the expression in ( ) as an rvalue. Then the rest of the semantics go from there. Note that a struct parameter with a move constructor will always pass by ref regardless, which is what we want here. Also, move semantics will only work on structs. Not classes, integers, pointers, arrays, etc. If move semantics are desired for them, they'll need to be wrapped in a struct, or use a template to wrap it for you. Consider: ``` this(ref S); // copy constructor this( move S); // move constructor ``` I don't know how to make overloading work with this.
Oct 02
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 10/2/24 20:00, Walter Bright wrote:
 On 10/1/2024 1:06 PM, Timon Gehr wrote:
 I think in case we did go the function route, I think any 
 implementation of `move` that is much more complex than the following 
 is a failure:

 ```d
 auto move(T)( moved T arg)=>arg;
 ```
The major difference between move and __rvalue is the former is attached to the parameter, and the latter is attached to the argument. This might seem a distinction without a difference, but this has large implications with how overloading works. ...
I don't think it affects how overloading works. You just treat it as a by-value argument as far as overloading and lifetime handling is concerned. In terms of ABI however, you pass the argument by reference. Some sort of NRVO would make the occurrence of `arg` above a move. This would be useful in general. Consider this program: ```d import std.stdio; struct S{ this(ref S){ writeln("copy"); } ~this(){ writeln("destructor"); } } S foo(S s)=>s; void main(){ S s=foo(S()); // ... } ``` This prints: ``` copy destructor destructor ``` There is actually no reason for this to make a copy, and the compiler would in principle be able to figure it out.
 For example, how do we distinguish a move constructor from a copy 
 constructor?
 ```
 this(ref S); // copy constructor
 this(S);     // move constructor
 S s = t;     // calls copy constructor (lvalue)
 S s = f();   // calls move constructor (rvalue)
 ```
 The current overloading rules work out of the box, an rvalue goes for 
 the move constructor, and an lvalue goes to the copy constructor.
 
 The problem here is when I want to move t into s. How do I get it to 
 call the move constructor?
 ```
 S move(ref S s) { return s; } // convert argument from lvalue to rvalue
 S s = move(t);
 ```
 This works, however, it creates a copy of s and then moves the copy! 
 There needs to be a way to tell the compiler to use the move construct, 
 hence:
 ```
 S s = __rvalue(t);
 ```
 All __rvalue does is flag the expression in ( ) as an rvalue. Then the 
 rest of the semantics go from there. Note that a struct parameter with a 
 move constructor will always pass by ref regardless, which is what we 
 want here. Also, move semantics will only work on structs. Not classes, 
 integers, pointers, arrays, etc. If move semantics are desired for them, 
 they'll need to be wrapped in a struct, or use a template to wrap it for 
 you.
 ...
`__rvalue` makes sense and is compatible with something like ` move`. However, I think it is in principle not needed if there is ` move`, as an lvalue `x` that is passed to a ` move` parameter would be treated just like `__rvalue(x)`.
 Consider:
 ```
 this(ref S);   // copy constructor
 this( move S); // move constructor
 ```
 I don't know how to make overloading work with this.
Well, those are constructors. You use one of them for copies and the other for moves. In general, ` move` would be incompatible with `ref`. If `ref` is overloaded with ` move`, lvalues go to the `ref` overload and rvalues go to the ` move` overload. (So overloading works just as if there was no ` move` on the parameter.) If you have an lvalue that should go to the ` move` overload, you explicitly call `move`. Of course, with DIP1040, in principle the last use of an lvalue could prefer the ` move` overload instead. Of course another question one might ask is what happens if you have: ```d void foo(T t); void foo( move T t); T t; foo(t); ``` I think that would just be ambiguous, just as if there were no ` move`. A benefit of explicit ` move` is that the move constructor ABI is not an invisible special case, and is also not magic. A drawback of explicit ` move` is that people might do: ```d this(ref S); this(S); // (no move) ``` And it is a priori not so clear what this should do. Maybe it should be disallowed, like it is now.
Oct 02
next sibling parent reply Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Wednesday, 2 October 2024 at 18:57:24 UTC, Timon Gehr wrote:
 On 10/2/24 20:00, Walter Bright wrote:
 [...]
I don't think it affects how overloading works. You just treat it as a by-value argument as far as overloading and lifetime handling is concerned. In terms of ABI however, you pass the argument by reference. [...]
What is the amount we need to pay to not get another attribute?
Oct 03
next sibling parent ryuukk_ <ryuukk.dev gmail.com> writes:
On Thursday, 3 October 2024 at 14:27:37 UTC, Imperatorn wrote:
 On Wednesday, 2 October 2024 at 18:57:24 UTC, Timon Gehr wrote:
 On 10/2/24 20:00, Walter Bright wrote:
 [...]
I don't think it affects how overloading works. You just treat it as a by-value argument as far as overloading and lifetime handling is concerned. In terms of ABI however, you pass the argument by reference. [...]
What is the amount we need to pay to not get another attribute?
Attributes are fine, as long as i don't need to put it on every functions because this random one has an attribute I actually would like more attributes, for builtin functionalities, like cDefine on imports to avoid having to pollute my makefile with duplicates Move constructor in interesting tho, it only appeal to C++ developers, a dangerous species that leads to a language nobody wants to use Look at these C like languages gaining momentum, they embrace features that doesn't require one to fall into RAII/OOP traps, D will accumulate yet another feature that only appeal to C++ crowd that lead to stupid APIs, yet, even Andrei went back to C++
Oct 03
prev sibling parent Dukc <ajieskola gmail.com> writes:
On Thursday, 3 October 2024 at 14:27:37 UTC, Imperatorn wrote:
 On Wednesday, 2 October 2024 at 18:57:24 UTC, Timon Gehr wrote:
 On 10/2/24 20:00, Walter Bright wrote:
 [...]
I don't think it affects how overloading works. You just treat it as a by-value argument as far as overloading and lifetime handling is concerned. In terms of ABI however, you pass the argument by reference. [...]
What is the amount we need to pay to not get another attribute?
This probably wouldn't be a language attribute like ` nogc` or ` trusted`. It would be a simple UDA that is defined as a compiler intrinsic. It would likely be defined either at [core.attribute](https://dlang.org/phobos/core_attribute.html), or somewhere at `core.internal`. And in any case it wouldn't be anything the D programmer has to deal with. It'd be used to define DRuntime functions, and maybe some low-level Phobos / utility library functions. But not at application code, nor even at normal library code.
Oct 04
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
What if you have:
```
void foo(S);
void foo(ref S);
void foo( move S);
```
? The overloading rules are already much more complex than I intended.


```
S foo(S s)=>s;
```

 There is actually no reason for this to make a copy, and the compiler would 
in principle be able to figure it out. I did consider it (actually `S foo(ref S s)=>s;`), but didn't like it because it would require major changes in multiple places in some already complex code. It was too high risk of introducing all kinds of unexpected interactions and problems. And, in the end, __lvalue(s) does the same thing and is fairly simple. I'm also currently struggling with: ``` this(S); this(ref S); ``` and then trying to get "is it copyable" to work. The trouble is that a move constructor not only has an S for an rvalue, that S must also be the same as the struct name. This cannot be determined by the parser, it needs semantic analysis. This means that when the constructor is searched for, it has to exclude matching on a move constructor, that cannot be detected before semantic analysis. The solution will likely be adding another parameter to the name lookup. Sigh, more slowdowns and complexity. The __rvalue thing, on the other hand, requires (I think) very little disruption to the compiler and language.
Oct 03
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 10/3/24 17:35, Walter Bright wrote:
 What if you have:
 ```
 void foo(S);
 void foo(ref S);
 void foo( move S);
 ```
 ? The overloading rules are already much more complex than I intended.
 ...
Well, as I said elsewhere, I think the first and third overload are just ambiguous with each other. They both prefer rvalues, of the same type.
 
 ```
 S foo(S s)=>s;
 ```
 
  > There is actually no reason for this to make a copy, and the compiler 
 would in principle be able to figure it out.
 
 I did consider it (actually `S foo(ref S s)=>s;`),
Well, with `ref` it does not work because ownership remains with the caller.
 but didn't like it 
 because it would require major changes in multiple places in some 
 already complex code. It was too high risk of introducing all kinds of 
 unexpected interactions and problems. And, in the end, __lvalue(s) does 
 the same thing and is fairly simple.
 
 I'm also currently struggling with:
 ```
 this(S);
 this(ref S);
 ```
 and then trying to get "is it copyable" to work. The trouble is that a 
 move constructor not only has an S for an rvalue, that S must also be 
 the same as the struct name. This cannot be determined by the parser, it 
 needs semantic analysis. This means that when the constructor is 
 searched for, it has to exclude matching on a move constructor, that 
 cannot be detected before semantic analysis.
 ...
Sounds annoying, but I guess this is a general issue, as metaprogramming can be involved in the declaration of constructors.
 The solution will likely be adding another parameter to the name lookup. 
 Sigh, more slowdowns and complexity.
 
 The __rvalue thing, on the other hand, requires (I think) very little 
 disruption to the compiler and language.
Well, `__rvalue` is fine. The question is just is it really sensible to tie move semantics to pass by value, and likely the answer is "not really". It is already obvious with the move constructor design.
Oct 03
prev sibling parent reply RazvanN <razvan.nitu1305 gmail.com> writes:
On Thursday, 3 October 2024 at 15:35:36 UTC, Walter Bright wrote:
 What if you have:
 I'm also currently struggling with:
 ```
 this(S);
 this(ref S);
 ```
 and then trying to get "is it copyable" to work. The trouble is 
 that a move constructor not only has an S for an rvalue, that S 
 must also be the same as the struct name. This cannot be 
 determined by the parser, it needs semantic analysis. This 
 means that when the constructor is searched for, it has to 
 exclude matching on a move constructor, that cannot be detected 
 before semantic analysis.
You can avoid this by simply moving the move and copy constructor to different overload sets during semantic analysis (__ctor, __cpctor, __mvctor). This will simplify the code immensely and it also has the added benefit that move and copy constructor will not be considered for manual constructor calls, which makes sense because mv and cp ctor are not meant for that.
Oct 04
parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 04/10/2024 8:15 PM, RazvanN wrote:
 On Thursday, 3 October 2024 at 15:35:36 UTC, Walter Bright wrote:
 What if you have:
 I'm also currently struggling with:
 ```
 this(S);
 this(ref S);
 ```
 and then trying to get "is it copyable" to work. The trouble is that a 
 move constructor not only has an S for an rvalue, that S must also be 
 the same as the struct name. This cannot be determined by the parser, 
 it needs semantic analysis. This means that when the constructor is 
 searched for, it has to exclude matching on a move constructor, that 
 cannot be detected before semantic analysis.
You can avoid this by simply moving the move and copy constructor to different overload sets during semantic analysis (__ctor, __cpctor, __mvctor). This will simplify the code immensely and it also has the added benefit that move and copy constructor will not be considered for manual constructor calls, which makes sense because mv and cp ctor are not meant for that.
In this vain, we could use an attribute to differentiate a copy constructor and move constructor. Same signature apart from the attribute. There is no reason why a move constructor couldn't do some cleanup of the old value, to make it safe for a destructor call on it.
Oct 04
prev sibling next sibling parent Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Wednesday, 2 October 2024 at 18:00:29 UTC, Walter Bright wrote:
 On 10/1/2024 1:06 PM, Timon Gehr wrote:
 [...]
The major difference between move and __rvalue is the former is attached to the parameter, and the latter is attached to the argument. This might seem a distinction without a difference, but this has large implications with how overloading works. [...]
The intrinsic is clearly best ```d struct S { this(ref S) { /* Copy */ } this(S) { /* Move */ } } void main() { S t; S s1 = t; // Copy S s2 = __rvalue(t); // Move ```
Oct 02
prev sibling parent reply Manu <turkeyman gmail.com> writes:
On Thu, 3 Oct 2024 at 04:06, Walter Bright via Digitalmars-d <
digitalmars-d puremagic.com> wrote:

 On 10/1/2024 1:06 PM, Timon Gehr wrote:
 I think in case we did go the function route, I think any implementation
of
 `move` that is much more complex than the following is a failure:

 ```d
 auto move(T)( moved T arg)=>arg;
 ```
The major difference between move and __rvalue is the former is attached to the parameter, and the latter is attached to the argument. This might seem a distinction without a difference, but this has large implications with how overloading works. For example, how do we distinguish a move constructor from a copy constructor? ``` this(ref S); // copy constructor this(S); // move constructor S s = t; // calls copy constructor (lvalue) S s = f(); // calls move constructor (rvalue) ``` The current overloading rules work out of the box, an rvalue goes for the move constructor, and an lvalue goes to the copy constructor. The problem here is when I want to move t into s. How do I get it to call the move constructor? ``` S move(ref S s) { return s; } // convert argument from lvalue to rvalue S s = move(t); ``` This works, however, it creates a copy of s and then moves the copy! There needs to be a way to tell the compiler to use the move construct, hence: ``` S s = __rvalue(t); ``` All __rvalue does is flag the expression in ( ) as an rvalue. Then the rest of the semantics go from there. Note that a struct parameter with a move constructor will always pass by ref regardless, which is what we want here. Also, move semantics will only work on structs. Not classes, integers, pointers, arrays, etc. If move semantics are desired for them, they'll need to be wrapped in a struct, or use a template to wrap it for you. Consider: ``` this(ref S); // copy constructor this( move S); // move constructor ``` I don't know how to make overloading work with this.
It's always some weird little detail that changes when I feel like we discussed/designed a thing and then you implement it! :P your __rvalue() is essentially the `T move(ref T)` intrinsic we discussed at length; so why not just make the move intrinsic rather than this thing? It seems trivial, but the reason I say this (as we discussed at length), is that in addition to the trivial act of stripping away the ref to make it an rvalue, the move expression will inevitably need to be enhanced with lifetime tracking semantics. In the future, you will want to put logic in that intrinsic to mark the end of lifetime of the memory region, and possibly mark the transition of ownership for ownership tracking... 'move' is the operation we seek, and it has lifetime related semantics involved with the operation. __rvalue() doesn't feel like the right abstraction for that set of semantics; you're gonna find yourself wondering where to pin the lifetime semantics in the future... Timon: Do you have any comment? Am I wrong?
Oct 02
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 10/3/24 01:17, Manu wrote:
 On Thu, 3 Oct 2024 at 04:06, Walter Bright via Digitalmars-d 
 <digitalmars-d puremagic.com <mailto:digitalmars-d puremagic.com>> wrote:
 
     On 10/1/2024 1:06 PM, Timon Gehr wrote:
      > I think in case we did go the function route, I think any
     implementation of
      > `move` that is much more complex than the following is a failure:
      >
      > ```d
      > auto move(T)( moved T arg)=>arg;
      > ```
 
     The major difference between  move and __rvalue is the former is
     attached to the
     parameter, and the latter is attached to the argument. This might
     seem a
     distinction without a difference, but this has large implications
     with how
     overloading works.
 
     For example, how do we distinguish a move constructor from a copy
     constructor?
     ```
     this(ref S); // copy constructor
     this(S);     // move constructor
     S s = t;     // calls copy constructor (lvalue)
     S s = f();   // calls move constructor (rvalue)
     ```
     The current overloading rules work out of the box, an rvalue goes
     for the move
     constructor, and an lvalue goes to the copy constructor.
 
     The problem here is when I want to move t into s. How do I get it to
     call the
     move constructor?
     ```
     S move(ref S s) { return s; } // convert argument from lvalue to rvalue
     S s = move(t);
     ```
     This works, however, it creates a copy of s and then moves the copy!
     There needs
     to be a way to tell the compiler to use the move construct, hence:
     ```
     S s = __rvalue(t);
     ```
     All __rvalue does is flag the expression in ( ) as an rvalue. Then
     the rest of
     the semantics go from there. Note that a struct parameter with a move
     constructor will always pass by ref regardless, which is what we
     want here.
     Also, move semantics will only work on structs. Not classes,
     integers, pointers,
     arrays, etc. If move semantics are desired for them, they'll need to
     be wrapped
     in a struct, or use a template to wrap it for you.
 
     Consider:
     ```
     this(ref S);   // copy constructor
     this( move S); // move constructor
     ```
     I don't know how to make overloading work with this.
 
 
 It's always some weird little detail that changes when I feel like we 
 discussed/designed a thing and then you implement it! :P
 your __rvalue() is essentially the `T move(ref T)` intrinsic we 
 discussed at length; so why not just make the move intrinsic rather than 
 this thing? It seems trivial, but the reason I say this (as we discussed 
 at length), is that in addition to the trivial act of stripping away the 
 ref to make it an rvalue, the move expression will inevitably need to be 
 enhanced with lifetime tracking semantics. In the future, you will want 
 to put logic in that intrinsic to mark the end of lifetime of the memory 
 region, and possibly mark the transition of ownership for ownership 
 tracking... 'move' is the operation we seek, and it has lifetime related 
 semantics involved with the operation. __rvalue() doesn't feel like the 
 right abstraction for that set of semantics; you're gonna find yourself 
 wondering where to pin the lifetime semantics in the future...
 
 Timon: Do you have any comment? Am I wrong?
I don't think you are wrong, there are just a few different ways to go about this and to me it seems Walter opened a discussion to explore the design space a bit further. I think `__rvalue` indeed boils down to a `move` intrinsic, just by a less nice name. As far as I understand, Walter's current intention is to keep the memory region alive by default after a move, where killing the memory region is a potential optimization enabled by alias analysis. In any case, I think if it is not possible to implement a `move` function that internally uses `__rvalue` and behaves just like `__rvalue` from the outside, that would indicate an issue with the type system and it in particular precludes perfect forwarding for moves without unnecessary intermediate memory operations and move constructor calls. This is why I also discuss enabling the functionality via parameter attributes instead.
Oct 02
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 10/2/2024 4:17 PM, Manu wrote:
 your __rvalue() is essentially the `T move(ref T)` intrinsic we discussed at 
 length; so why not just make the move intrinsic rather than this thing?
My original plan was to do just that: ``` S move(ref S s) => s; ``` would be recognized and replaced with the `__lvalue(s)`. What bothered me about it was the magic behavior of it, such as what happens with: ``` S move(ref S s, int i) => s; ``` which will behave completely differently, and may be quite surprising to users. I expect somebody will inevitably set at store on: ``` S move(ref S s) => s; S move(ref S s, int i) => s; ``` doing the same thing, but they don't, and the user will be baffled and I will get bug reports and the language will become more complicated is in the ugly way C++ became.
 'move' is the operation we seek, and it has 
 lifetime related semantics involved with the operation. __rvalue() doesn't
feel 
 like the right abstraction for that set of semantics; you're gonna find
yourself 
 wondering where to pin the lifetime semantics in the future...
__rvalue() does nothing more than say its argument is not an lvalue, so it will not match with a `ref` parameter. Lifetime analysis occurs after overload resolution, so I don't think it will impact it. __rvalue() is not a move, it just guides the overload resolution to the move constructor/assignment. It's a hint.
Oct 03
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 10/3/24 17:47, Walter Bright wrote:
 
 __rvalue() is not a move, it just guides the overload resolution to the 
 move constructor/assignment. It's a hint.
It has to be a move though. An rvalue is owned, an lvalue is not. Whenever an lvalue goes into an rvalue, it is either copy or move. If `__rvalue` is just some sort of ad-hoc type-punning operation exclusively for overload resolution, it will often copy the argument, unless your intention is to force it to be consumed by a function that is implicitly ` move` (such as a move constructor or a move assignment).
Oct 03
parent Manu <turkeyman gmail.com> writes:
On Fri, 4 Oct 2024 at 06:56, Timon Gehr via Digitalmars-d <
digitalmars-d puremagic.com> wrote:

 On 10/3/24 17:47, Walter Bright wrote:
 __rvalue() is not a move, it just guides the overload resolution to the
 move constructor/assignment. It's a hint.
It has to be a move though. An rvalue is owned, an lvalue is not. Whenever an lvalue goes into an rvalue, it is either copy or move. If `__rvalue` is just some sort of ad-hoc type-punning operation exclusively for overload resolution, it will often copy the argument, unless your intention is to force it to be consumed by a function that is implicitly ` move` (such as a move constructor or a move assignment).
Yes, exactly... __rvalue() must be SOME KIND of move; it is explicitly taking ownership away from the owner, and handing it to some new owner. The thing about __rvalue() is that you will hide it inside some function, like `T move(ref T t) => __rvalue(t)` That encapsulates the ownership transfer, and separates the visibility of the ownership transfer from the calling scope where that information is needed. Maybe you should reserve the name `move` and make it an error for a user to produce any declaration with that name? That might avoid your surprise overload issue?
Oct 03
prev sibling parent Manu <turkeyman gmail.com> writes:
On Fri, 4 Oct 2024 at 01:51, Walter Bright via Digitalmars-d <
digitalmars-d puremagic.com> wrote:

 On 10/2/2024 4:17 PM, Manu wrote:
 your __rvalue() is essentially the `T move(ref T)` intrinsic we
discussed at
 length; so why not just make the move intrinsic rather than this thing?
My original plan was to do just that: ``` S move(ref S s) => s; ``` would be recognized and replaced with the `__lvalue(s)`. What bothered me about it was the magic behavior of it, such as what happens with: ``` S move(ref S s, int i) => s; ``` which will behave completely differently, and may be quite surprising to users. I expect somebody will inevitably set at store on: ``` S move(ref S s) => s; S move(ref S s, int i) => s; ``` doing the same thing, but they don't, and the user will be baffled and I will get bug reports and the language will become more complicated is in the ugly way C++ became.
Okay, fair. I'm convinced.
 'move' is the operation we seek, and it has
 lifetime related semantics involved with the operation. __rvalue()
doesn't feel
 like the right abstraction for that set of semantics; you're gonna find
yourself
 wondering where to pin the lifetime semantics in the future...
__rvalue() does nothing more than say its argument is not an lvalue, so it will not match with a `ref` parameter. Lifetime analysis occurs after overload resolution, so I don't think it will impact it. __rvalue() is not a move, it just guides the overload resolution to the move constructor/assignment. It's a hint.
Okay, so then from this perspective without any obvious place to pin any lifetime tracking semantics, the lifetime of an argument to move will continue after the call to move; which implies (as we discussed at some length) that the callee MAY move the object, and if it does, the callee is responsible for putting it back in an init state so that it is valid when the calling object is destructed at some later time... This is fine, and we tolerate this arrangement in C++ (probably for the exact same reasons), but it's not quite an optimal solution. Sufficiently capable lifetime tracking could theoretically elide calls to destructors when it knows a move took place, but I can't see a way the calling scope can have any such confidence with __rvalue() as a tool? I guess this is where Timon's proposal for move comes in. Incidentally, that proposal seems to be additive to this work though; it could be added later to give the calling scope that information, but it doesn't seem to contradict your current path. I think I'm satisfied with your direction. It'd be interesting if you could push a working branch? I've been writing a whole bunch of containers the last few days and I'd like to try it out if it's in a testable state.
Oct 03
prev sibling next sibling parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
I suspect that we're going in an entirely wrong direction with move 
constructors for two reasons:

1. Nobody has been able to answer what the purpose of them is, is it an 
optimization, is it ownership transfer system (which is better done with 
isolated).
2.  move on the parameter. This can be inferred, AND applies to other 
functions like swap for escape analysis and opAssign!

```d
struct S {
	this( move ref S s) { // copy constructor with the /side effect/ of 
invaliding the original, call dependent
		s = S.init; // compiler removes this if seen
	}
}

void phone(S s1) {
	S s2 = s1; // only one of these has to invalidate s1
	S s3 = s1;
}
```

I strongly believe not going the attribute route is blocking us in for 
worse behaviors.
Sep 30
next sibling parent reply Atila Neves <atila.neves gmail.com> writes:
On Monday, 30 September 2024 at 20:28:05 UTC, Richard (Rikki) 
Andrew Cattermole wrote:
 I suspect that we're going in an entirely wrong direction with 
 move constructors for two reasons:

 1. Nobody has been able to answer what the purpose of them is, 
 is it an optimization, is it ownership transfer system (which 
 is better done with isolated).
My understanding is that we need SSA in order to implement isolated. And so for now I'd say it's an optimisation mostly, but also bug prevention with move-only types.
Sep 30
parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 01/10/2024 3:58 PM, Atila Neves wrote:
 On Monday, 30 September 2024 at 20:28:05 UTC, Richard (Rikki) Andrew 
 Cattermole wrote:
 I suspect that we're going in an entirely wrong direction with move 
 constructors for two reasons:

 1. Nobody has been able to answer what the purpose of them is, is it 
 an optimization, is it ownership transfer system (which is better done 
 with isolated).
My understanding is that we need SSA in order to implement isolated. And so for now I'd say it's an optimisation mostly, but also bug prevention with move-only types.
SSA? What? That has nothing to do with it. If you want isolated to not have its own analysis you need type state analysis, which is DFA. The same DFA we should be using for escape analysis. Forward pass only on success. One of my concerns for move constructors is the addition of analysis that is in addition to the escape analysis DFA.
Sep 30
parent reply Atila Neves <atila.neves gmail.com> writes:
On Tuesday, 1 October 2024 at 03:07:02 UTC, Richard (Rikki) 
Andrew Cattermole wrote:
 On 01/10/2024 3:58 PM, Atila Neves wrote:
 On Monday, 30 September 2024 at 20:28:05 UTC, Richard (Rikki) 
 Andrew Cattermole wrote:
 I suspect that we're going in an entirely wrong direction 
 with move constructors for two reasons:

 1. Nobody has been able to answer what the purpose of them 
 is, is it an optimization, is it ownership transfer system 
 (which is better done with isolated).
My understanding is that we need SSA in order to implement isolated. And so for now I'd say it's an optimisation mostly, but also bug prevention with move-only types.
SSA? What? That has nothing to do with it.
Not according to Amaury.
Oct 01
parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 02/10/2024 3:59 PM, Atila Neves wrote:
 On Tuesday, 1 October 2024 at 03:07:02 UTC, Richard (Rikki) Andrew 
 Cattermole wrote:
 On 01/10/2024 3:58 PM, Atila Neves wrote:
 On Monday, 30 September 2024 at 20:28:05 UTC, Richard (Rikki) Andrew 
 Cattermole wrote:
 I suspect that we're going in an entirely wrong direction with move 
 constructors for two reasons:

 1. Nobody has been able to answer what the purpose of them is, is it 
 an optimization, is it ownership transfer system (which is better 
 done with isolated).
My understanding is that we need SSA in order to implement isolated. And so for now I'd say it's an optimisation mostly, but also bug prevention with move-only types.
SSA? What? That has nothing to do with it.
Not according to Amaury.
He would be wrong to suggest that it is a requirement. It may make it easier to conceptualize it, and if you already have an IR that is SSA it may even allow you to do it cheaply. But it is not a requirement. You can confirm what I have said by reading Wikipedia on the subject where SSA is indeed listed as an alternative approach to some problems. https://en.wikipedia.org/wiki/Data-flow_analysis To construct an SSA IR will cost a lot of time, that quite frankly I can't see making the rest of the analysis faster. It would be a totally different situation if we could parallelize it (I went down that path with my semantic 4, but it makes more sense to do as part of semantic 3 for this). You basically need to make the CFG from the AST. Isolated and with that unique ownership is based upon the data flow analysis of the tracked objects. Object tracking isn't too complex if you are limited to forward pass only. For reference, good article by one of the creators of Midori, regarding isolated https://joeduffyblog.com/2016/11/30/15-years-of-concurrency/
Oct 01
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 9/30/2024 1:28 PM, Richard (Rikki) Andrew Cattermole wrote:
 1. Nobody has been able to answer what the purpose of them is
Performance.
Sep 30
parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 01/10/2024 4:11 PM, Walter Bright wrote:
 On 9/30/2024 1:28 PM, Richard (Rikki) Andrew Cattermole wrote:
 1. Nobody has been able to answer what the purpose of them is
Performance.
Ah huh! Now I'm on board with us having move constructors.
Sep 30
prev sibling next sibling parent Vindex9 <tech.vindex gmail.com> writes:
On Monday, 30 September 2024 at 16:05:16 UTC, Walter Bright wrote:
 I've been implementing move constructors. They are working, at 
 least on my test cases so far. They are distinguished by a copy 
 constructor takes its argument by ref, and a move constructor 
 by value:

 ```
 struct S
 {
     this(ref S); // copy constructor
     this(S);     // move constructor
 }
--- Is it necessary to breed entities? Doesn't std.algorithm.move do everything we need? ```d import core.stdc.stdlib : malloc, free; import core.stdc.string : memcpy; import std.algorithm.mutation : move; import std.exception : enforce; import std.stdio : writeln; struct S { void* content; size_t size; this(size_t n) { writeln("regular ctor"); // debug init(n); } this(ref return scope const S rhs) { writeln("copy ctor"); // debug init(rhs.size); memcpy(this.content, rhs.content, rhs.size); } private void init(size_t n) { this.content = malloc(n); enforce(this.content != null, "Memory allocation error."); this.size = n; } } void main() { S obj1 = S(1024); assert(obj1.content != null); S obj2 = move(obj1); // moving assert(obj1.content == null); assert(obj2.content != null); S obj3 = S(obj2); // copying assert(obj3.content != obj2.content); assert(obj3.size == obj2.size); } ```
Oct 01
prev sibling next sibling parent Manu <turkeyman gmail.com> writes:
I feel like we discussed exactly this at dconf fairly extensively, with
pictures and diagrams and stuff...

Just write `T move(ref T)` as an intrinsic.
There's potentially more work to do than this operation; the intrinsic will
also (in the future) include internal logic to end the lifetime of the
memory region, and possibly logic to record the transfer of ownership for
the value. This is all not expressible in-language... it's most appropriate
as an intrinsic; this is mega-fundamental stuff. Remember, the goal is to
murder core.lifetime with fire.

We don't want a function for this, no call/ret, do not want to pollute the
program flow in that way and definitely don't want to create weird program
flow while stepping through code while debugging. Intrinsic that initially
just strips off ref, and can be enhanced with lifetime management logic in
future.

On Tue, 1 Oct 2024, 02:11 Walter Bright via Digitalmars-d, <
digitalmars-d puremagic.com> wrote:

 I've been implementing move constructors. They are working, at least on my
 test
 cases so far. They are distinguished by a copy constructor takes its
 argument by
 ref, and a move constructor by value:

 ```
 struct S
 {
      this(ref S); // copy constructor
      this(S);     // move constructor
 }
 ```
 So far, so good. Consider:
 ```
 void phone(S s)
 {
      S t = s;  // copy constructor
 }
 ```
 But what if we want to initialize `t` via a move constructor? Somehow, `s`
 has
 to be converted from an lvalue to an rvalue. This is done via the function
 rvalue():
 ```
 S rvalue(ref S s) { return s; }

 S t = rvalue(s);
 ```
 Unfortunately, rvalue() gives us this:
 ```
 S __copytmp = 0;
 this(&__copytmp,&s);  // copy construction of s
 *__H1D1 = __copytmp;
 return __H1D1;
 ```
 which is correct, but not what we want, which is:
 ```
 return &s;
 ```
 and also not pass an extra hidden parameter for the return value, aka
 __H1D1.

 I have thought of several ways to do this, none of which seem particularly
 attractive as they are all not expressible in conventional D. I won't say
 what
 they are in order to not bias anyone.

 So, does anyone have any brilliant ideas for how to make the compiler
 treat an
 lvalue as an rvalue?

 P.S. C++ uses the std::move function to do it:


 https://learn.microsoft.com/en-us/cpp/standard-library/utility-functions?view=msvc-170#move

 which relies on rvalue references:


 https://learn.microsoft.com/en-us/cpp/cpp/rvalue-reference-declarator-amp-amp?view=msvc-170

 which is a major feature which I prefer to avoid.
Oct 02
prev sibling next sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Monday, September 30, 2024 10:05:16 AM MDT Walter Bright via Digitalmars-d 
wrote:
 I've been implementing move constructors. They are working, at least on my
 test cases so far. They are distinguished by a copy constructor takes its
 argument by ref, and a move constructor by value:

 ```
 struct S
 {
      this(ref S); // copy constructor
      this(S);     // move constructor
 }
 ```
 So far, so good. Consider:
 ```
 void phone(S s)
 {
      S t = s;  // copy constructor
 }
 ```
 But what if we want to initialize `t` via a move constructor? Somehow, `s`
 has to be converted from an lvalue to an rvalue. This is done via the
 function rvalue(): ```
Given the we're now looking at having a separate syntax for move constructors, I would argue that they should just have to be ref, which should eliminate the special cases that you're fighting here. Even if an rvalue is being passed in, it has to be _somewhere_ as a temporary, and fundamentally, a move constructor needs an lvalue, since it's moving an object from one location to another, even if the source location is a temporary one. - Jonathan M Davis
Oct 11
prev sibling parent reply Manu <turkeyman gmail.com> writes:
On Sat, 12 Oct 2024 at 04:20, Jonathan M Davis via Digitalmars-d <
digitalmars-d puremagic.com> wrote:

 On Monday, September 30, 2024 10:05:16 AM MDT Walter Bright via
 Digitalmars-d
 wrote:
 I've been implementing move constructors. They are working, at least on
my
 test cases so far. They are distinguished by a copy constructor takes its
 argument by ref, and a move constructor by value:

 ```
 struct S
 {
      this(ref S); // copy constructor
      this(S);     // move constructor
 }
 ```
 So far, so good. Consider:
 ```
 void phone(S s)
 {
      S t = s;  // copy constructor
 }
 ```
 But what if we want to initialize `t` via a move constructor? Somehow,
`s`
 has to be converted from an lvalue to an rvalue. This is done via the
 function rvalue(): ```
Given the we're now looking at having a separate syntax for move constructors, I would argue that they should just have to be ref, which should eliminate the special cases that you're fighting here.
You really need to re-read the DIP here and understand the design principle of this whole thing. This suggestion show us that you either don't understand the DIP, totally missed the point, or possibly that you fundamentally disagree with the DIP; and if that's the case, I think you need to present that argument and what you'd propose instead, rather than talk with respect to implementation of the DIP with a completely different idea in mind. If you change the fundamental substance of the DIP, it's a new DIP. The proposal in the DIP is very simple; struct rvalues are ref too now, so don't worry about the ref stuff; everything's a ref. The problem to be solved is, how do we appropriately distinguish an rvalue from an lvalue; and while we've had a myriad of proposals adding attributes, Walter found an arrangement where the distinction can be expressed in existing language in an astonishingly elegant way; recognise that by-value calls (accepts rvalue) are all actually move opportunities. void f(ref T) // arg is an lvalue; the syntax says "I have received a reference to *someone else's* thing"; or put another way, the callee does NOT own the argument. void f(T) // arg is an rvalue; this syntax says "I have received this thing"; the callee owns the argument, and as such, is a valid recipient of any move operation. In order to make move operations actually move operations, they need to be passed by ref (by *rvalue-ref*, so to speak), and that is literally the entire point of the DIP; the calling convention is adjusted so a by-value (r-value) parameter is passed by rvalue-ref. This is what I mean where I say we're talking about "move semantics", but everyone seems to be fixated on move constructors as if they're an extraordinarily interesting part of this story. A move constructor is just an efficient initialisation opportunity, and it's a small part of the overall story regarding move semantics. As I see it; the move constructor *must *be an overload; otherwise, surely you must assume the overload selection rules don't work, and so every single other function (aside from the move constructor which received a blessed hack!) will not make a proper selection. Move semantics ARE proper overload selection, that's the entire meat of this design. This design *IS* overload selection rules.... to try and work that problem off to the side would be to have completely missed the point. If you exclude overload resolution from the picture, then I don't even know what we're talking about here.
Oct 11
next sibling parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 12/10/2024 6:56 PM, Manu wrote:
 You really need to re-read the DIP here and understand the design 
 principle of this whole thing. This suggestion show us that you either 
 don't understand the DIP, totally missed the point, or possibly that you 
 fundamentally disagree with the DIP; and if that's the case, I think you 
 need to present that argument and what you'd propose instead, rather 
 than talk with respect to implementation of the DIP with a completely 
 different idea in mind. If you change the fundamental substance of the 
 DIP, it's a new DIP.
It may be time for us to get you into a meeting. Yesterday in the monthly meeting it was determined that Martin and you should talk about moving. He and I have some similar ideas regarding things like postblit and their interaction with it. So I am confident in a good outcome if he is leading it. I'll message Mike to reaffirm what I've said here and see if he can't arrange something between everyone.
Oct 11
parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 12/10/2024 7:42 PM, Richard (Rikki) Andrew Cattermole wrote:
 On 12/10/2024 6:56 PM, Manu wrote:
 You really need to re-read the DIP here and understand the design 
 principle of this whole thing. This suggestion show us that you either 
 don't understand the DIP, totally missed the point, or possibly that 
 you fundamentally disagree with the DIP; and if that's the case, I 
 think you need to present that argument and what you'd propose 
 instead, rather than talk with respect to implementation of the DIP 
 with a completely different idea in mind. If you change the 
 fundamental substance of the DIP, it's a new DIP.
It may be time for us to get you into a meeting. Yesterday in the monthly meeting it was determined that Martin and you should talk about moving. He and I have some similar ideas regarding things like postblit and their interaction with it. So I am confident in a good outcome if he is leading it. I'll message Mike to reaffirm what I've said here and see if he can't arrange something between everyone.
I've talked to Mike, he is busy, so I'll be doing the meeting arrangement. If you or anyone else who wants to be there but isn't on the email chain please let me know.
Oct 12
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 10/12/24 07:56, Manu wrote:
 
 The proposal in the DIP is very simple; struct rvalues are ref too now, 
 so don't worry about the ref stuff; everything's a ref. The problem to 
 be solved is, how do we appropriately distinguish an rvalue from an 
 lvalue; and while we've had a myriad of proposals adding attributes, 
 Walter found an arrangement where the distinction can be expressed in 
 existing language in an astonishingly elegant way; recognise that by- 
 value calls (accepts rvalue) are all actually move opportunities.
 
 void f(ref T) // arg is an lvalue; the syntax says "I have received a 
 reference to /someone else's/ thing"; or put another way, the callee 
 does NOT own the argument.
 void f(T) // arg is an rvalue; this syntax says "I have received this 
 thing"; the callee owns the argument, and as such, is a valid recipient 
 of any move operation.
 In order to make move operations actually move operations, they need to 
 be passed by ref (by /rvalue-ref/, so to speak), and that is literally 
 the entire point of the DIP; the calling convention is adjusted so a by- 
 value (r-value) parameter is passed by rvalue-ref.
 
 This is what I mean where I say we're talking about "move semantics", 
 but everyone seems to be fixated on move constructors as if they're an 
 extraordinarily interesting part of this story.
I buy this except: - With DIP1040, a move constructor `this(S)` is actually fundamentally different from a function `f(S)`, and not just because it is a constructor. I think this should be adjusted so it works the way you say. Then there can be some special syntax to elide destructor calls explicitly. - Move and copy constructors are indeed a bit special because the compiler may implicitly call them in ways it would not call any other constructor. Here, idk. It's not really an important concern to me personally but I can see why someone might have an issue with this. For copy constructors, consider: ```d import std.stdio; struct S{ this(int x){ writeln("constructed"); } this(ref S){ writeln("copied"); } } void foo(S s){} void main(){ S s=2; // ok, actually does `S s=S(2)` S t=s; // ok, actually does `S s=S(s)` // foo(2); // error, refuses to call `foo(S(s))` to convert to `int` foo(s); // ok, calls foo(S(s)) to convert to rvalue } ``` For move constructors, consider: ```d import std.stdio; struct S{ size_t address; this(int){ address=cast(size_t)&this; } this(S){ writeln("rvalue constructor"); } } S foo(bool x){ S s=2,t=2; if(x) return s; else return t; } void main(){ auto s=foo(true); // (no output) auto t=foo(false); // (no output) writeln(s.address," ",cast(size_t)&s); // (two distinct numbers) writeln(t.address," ",cast(size_t)&t); // (two distinct numbers) } ``` So with the compilation strategy that DMD currently uses for this code, it would need to start calling what is currently an rvalue constructor, but at the moment nothing is called. This is a change in behavior. To me this seems desirable, but maybe there is some code out there that does not expect rvalue constructors to be called in this fashion? Perhaps there should be a deprecation period?
Oct 12
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 10/13/24 00:43, Timon Gehr wrote:
 
 - Move and copy constructors are indeed a bit special because the 
 compiler may implicitly call them in ways it would not call any other 
 constructor.
(Also, it implicitly synthesizes them.)
Oct 12
prev sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 10/13/24 00:43, Timon Gehr wrote:
      // foo(2); // error, refuses to call `foo(S(s))` to convert to `int`
(Should say: "convert to `S`" or "convert from `int`".)
Oct 12