Frist Draft (in this forum): Enum Parameters

Quirin Schroll qs.il.paperinik at gmail.com
Tue May 7 11:23:10 UTC 2024


On Monday, 29 April 2024 at 19:17:47 UTC, Paul Backus wrote:
> On Thursday, 25 April 2024 at 17:56:59 UTC, Quirin Schroll 
> wrote:
>> https://github.com/Bolpat/DIPs/blob/2bfef0b05e99c5448b416078da2e743482e3193d/DIPs/1NNN-QFS.md
>
> Yet another non-orthogonal language feature that all generic 
> code will have to go out of its way to account for until the 
> end of time. `ref` is already bad enough; the last thing we 
> need is another feature that repeats the exact same design 
> mistakes.

TL;DR: While `auto enum` is similar to `auto ref` in that it 
infers a storage class based on the argument passed, forwarding 
is a non-issue for `auto enum` and even `auto enum auto ref`. 
Analysis is below.

> For example: currently, in order to forward an argument 
> correctly in generic code, we must write the following:
>
> ```d
> void forwardTo(alias fun, T)(auto ref T arg)
> {
>     import core.lifetime: forward;
>     fun(forward!arg);
> }
> ```
>
> Notice how much overhead is required *just* to deal with the 
> `ref` storage class.

I agree that forwarding in its current form is indeed a nuisance.

> If this proposal were accepted, we would have to replace all 
> instances of the above with the following:
>
> ```d
> void forwardTo(alias fun, T)(auto enum auto ref T arg)
> {
>     import core.lifetime: forward;
>     static if (__traits(isEnum, arg))
>         fun(arg);
>     else
>         fun(forward!arg);
> }
> ```
>
> Needless to say, the vast majority of D programmers are not 
> going to bother with this, which means that we will forever be 
> plagued by buggy handling of `enum` parameters in our library 
> code (just as we are already plagued by buggy handling of `ref` 
> parameters).
>
> I do not think it is a good idea to introduce language features 
> that set future D programmers up for failure like this.

You’re right in that `forward` better be mentioned in the DIP. 
Writing an answer to this post, I found out that not only could 
`forward` easily be adapted to recognize `enum` parameters and 
handle them properly by leaving them alone, `forward` actually 
would handle them correctly today.

First things first, contrary to `auto ref`, an `auto enum` 
parameter bound to a compile-time constant (i.e. one that becomes 
`enum`) would stay a compile-time constant (and a run-time value 
would stay a run-time value), so forwarding `auto enum` is 
automatic (it requires nothing in terms of written code to 
happen). That is contrary to `auto ref` where no matter how the 
argument was passed (i.e. no matter whether the parameter becomes 
`ref`), the parameter itself is always an lvalue and binds 
to/prefers `ref` when passed to another function. This is why 
forwarding `auto ref` must be explicit.

Another aspect is that there is no `enum ref` parameters, so 
`auto enum auto ref` is non-orthogonal by design: It can become 
`enum` (pass as compile-time constant), `ref` (pass by reference) 
or none (pass by value).

Assuming the following code (which apart from additional `auto 
enum` is identical to the example of currently existing code):
```d
void forwardTo(alias fun, T)(auto enum auto ref T arg)
{
     import core.lifetime: forward;
     fun(forward!arg);
}
```

How does `forward` handle the three cases? The cases `ref` and 
pass-by-value are clear, they are as they are today. As far as 
the function template implementation is concerned, the `enum` 
case is equivalent to:

```d
void forwardTo(alias fun, T, T arg)()
{
     import core.lifetime: forward;
     fun(forward!arg);
}
```

That is valid code today!
```d
void main()
{
     immutable myInt = 10;
     forwardTo!(
         (auto ref x) { pragma(msg, __traits(isRef, x)); },
         int,
         myInt
     );
}
```
It prints `false`. This means, `forward` passed the compile-time 
constant to the lambda unchanged and a compile-time constant is 
seen as an rvalue. Note that `myInt` could absolutely be bound as 
an lvalue.

The reason for this is that `forward` not only recognizes some 
storage classes (`ref` among them), it also checks if `move` 
would work and if it doesn’t, it does not use it. That is because 
template value parameters are rvalues and `enum` parameters would 
be rvalues as well, and `move` only works on lvalues.


More information about the dip.development mailing list