2 days ago
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?
2 days ago
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.
2 days ago
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.

2 days ago
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++



2 days ago
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.
2 days ago
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).
2 days ago
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.


2 days ago
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?


2 days ago
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.




2 days ago
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.