www.digitalmars.com         C & C++   DMDScript  

digitalmars.dip.development - Frist Draft (in this forum): Enum Parameters

reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
https://github.com/Bolpat/DIPs/blob/2bfef0b05e99c5448b416078da2e743482e3193d/DIPs/1NNN-QFS.md

This supersedes my idea of static indexing. The static indexing 
DIP draft even said that if some way to pass values as 
compile-time constants to functions, that would supersede it.

**Abstract**
On function templates, allow `enum` to be used as a function 
parameter storage class and a member function attribute. 
Arguments binding to `enum` parameters must be compile-time 
constants, as if template value parameters. With `auto enum`, 
“compile-time-ness” is determined from argument (cf. `auto ref`) 
and queried via a trait.
Apr 25
next sibling parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
Enum as a keyword has quite different meaning, I suggest ``__ctfe`` 
instead as it already has a similar meaning.

I looked for discussion to its interaction with string interpolation, 
and there is none.

Currently I see no benefit from requiring a known at compile time 
expression to be passed to a parameter.

If however it resulted in that function parameter being treated as a 
template parameter that would be different. But I don't see anything 
that suggests this.
Apr 25
parent reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Thursday, 25 April 2024 at 20:29:27 UTC, Richard (Rikki) 
Andrew Cattermole wrote:
 […]

 If however it resulted in that function parameter being treated 
 as a template parameter that would be different. But I don't 
 see anything that suggests this.
But that is exactly what I propose. Quoting from the draft:
 In the function body (including contracts and constraints), an 
 `enum` parameter’s value is a compile-time constant as if it 
 were template value parameter.
 The same is true for `this` in an `enum` non-`static` member 
 function body.
 The main difference between `enum` parameters and template 
 value parameters is only in calling syntax:
 `f!ct_value(args)` versus `f(ct_value, args)`.
 Another difference is that the type of an `enum` parameter can 
 be inferred, while a template value parameter must have its 
 type specified.
It would allow to define `opIndex` as ```d struct Tpl(Ts...) { Ts[i] opIndex(enum size_t i) { .. } Tpl!(Ts[l..u]) opSlice(enum size_t l, enum size_t u) { .. } } ``` So that ```d Tpl!(int, string, Object) t; t[0] // lowers to t.opIndex(0) of type int t[1] // lowers to t.opIndex(1) of type string t[2] // lowers to t.opIndex(2) of type Object t[3] // lowers to t.opIndex(3), a compile error as (int,string,Object)[3] fails t[0..2] // is of type Tpl!(int, string) ``` You could have `opBinary` that only accepts certain constants to add. Or `opEquals` that only allows for comparison with `0`. `regex` could simply “notice” that the pattern is a compile-time constant and be equivalent to `ctRegex`. `format` can check and/or optimize if the pattern is a compile-time constant. Maybe with interpolated strings, there’s a lot possible. I don’t know. If you find some application, please let me know.
Apr 25
parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 26/04/2024 11:29 AM, Quirin Schroll wrote:
 On Thursday, 25 April 2024 at 20:29:27 UTC, Richard (Rikki) Andrew 
 Cattermole wrote:
 […]

 If however it resulted in that function parameter being treated as a 
 template parameter that would be different. But I don't see anything 
 that suggests this.
But that is exactly what I propose. Quoting from the draft:
 In the function body (including contracts and constraints), an `enum` 
 parameter’s value is a compile-time constant as if it were template 
 value parameter.
 The same is true for `this` in an `enum` non-`static` member function 
 body.
 The main difference between `enum` parameters and template value 
 parameters is only in calling syntax:
 `f!ct_value(args)` versus `f(ct_value, args)`.
 Another difference is that the type of an `enum` parameter can be 
 inferred, while a template value parameter must have its type specified.
It wasn't jumping out at me, so this could do with some reworking.
Apr 27
prev sibling next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On Thursday, 25 April 2024 at 17:56:59 UTC, Quirin Schroll wrote:
 https://github.com/Bolpat/DIPs/blob/2bfef0b05e99c5448b416078da2e743482e3193d/DIPs/1NNN-QFS.md

 This supersedes my idea of static indexing. The static indexing 
 DIP draft even said that if some way to pass values as 
 compile-time constants to functions, that would supersede it.

 **Abstract**
 On function templates, allow `enum` to be used as a function 
 parameter storage class and a member function attribute. 
 Arguments binding to `enum` parameters must be compile-time 
 constants, as if template value parameters. With `auto enum`, 
 “compile-time-ness” is determined from argument (cf. `auto 
 ref`) and queried via a trait.
I would love to see this happen. This is like a streamlining of template value parameters, akin to how lambdas streamline template type parameters. I have thoughts on reworking interpolation tuples if this got in... -Steve
Apr 25
parent reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Friday, 26 April 2024 at 02:47:18 UTC, Steven Schveighoffer 
wrote:
 I have thoughts on reworking interpolation tuples if this got 
 in...
Please, elaborate. I was somewhat absent from the forums while string interpolation took off. I read the docs, but it seems a lot of regulars like you have a lot more intuition about where interpolation tuples are useful. Not only could I use those ideas in the DIP, I’m honestly intrigued because I can’t imagine how they meaningfully interact.
Apr 26
next sibling parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 26/04/2024 9:39 PM, Quirin Schroll wrote:
 On Friday, 26 April 2024 at 02:47:18 UTC, Steven Schveighoffer wrote:
 I have thoughts on reworking interpolation tuples if this got in...
Please, elaborate. I was somewhat absent from the forums while string interpolation took off. I read the docs, but it seems a lot of regulars like you have a lot more intuition about where interpolation tuples are useful. Not only could I use those ideas in the DIP, I’m honestly intrigued because I can’t imagine how they meaningfully interact.
String interpolation slices and dices the string up into metadata arguments and the actual expressions specified by the user. The meta data arguments should be elevated to template parameters, but the expressions must remain runtime. What makes this tricky is given a sequence of expressions an interleaved sub sequence (that may not be odd/even), needs to be elevated but the others shouldn't be. If we can elevate the metadata expressions to templates we can validate types and formatting during compilation.
Apr 27
prev sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
On Friday, 26 April 2024 at 09:39:52 UTC, Quirin Schroll wrote:
 On Friday, 26 April 2024 at 02:47:18 UTC, Steven Schveighoffer 
 wrote:
 I have thoughts on reworking interpolation tuples if this got 
 in...
Please, elaborate. I was somewhat absent from the forums while string interpolation took off. I read the docs, but it seems a lot of regulars like you have a lot more intuition about where interpolation tuples are useful. Not only could I use those ideas in the DIP, I’m honestly intrigued because I can’t imagine how they meaningfully interact.
I realize I forgot to expand on this. In the current regime of [IES](https://dlang.org/spec/istring.html), the compile-time strings are preserved via an attachment of the literal data to the *type* of the parameter. This necessitates always taking the IES via vararg template, and also results in some more advanced techniques required for introspecting the data. Compared to a blueprint-style function (like `writef`), it's much more complex to explain and use. But the benefit is awesome -- perfect rewriting of code is possible (via string mixin) as if you called the new code without doing any runtime parsing. But if we have an enum parameter mechanism, the compile-time data can become a simple array of literal data, and you can accept it via a runtime parameter (if you prefer runtime parsing, and less template bloat), or via an enum parameter (if you prefer to parse at compile time). I outlined my thoughts on it here: https://forum.dlang.org/post/ojotybtiosjikmmcxemw forum.dlang.org (there I also acknowledged that I didn't know about your previous DIP, and supported it) -Steve
May 07
prev sibling next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 4/25/24 19:56, Quirin Schroll wrote:
 https://github.com/Bolpat/DIPs/blob/2bfef0b05e99c5448b416078da2e743482e3193d/DIPs/1NNN-QFS.md
 
 This supersedes my idea of static indexing. The static indexing DIP 
 draft even said that if some way to pass values as compile-time 
 constants to functions, that would supersede it.
 
 **Abstract**
 On function templates, allow `enum` to be used as a function parameter 
 storage class and a member function attribute. Arguments binding to 
 `enum` parameters must be compile-time constants, as if template value 
 parameters. With `auto enum`, “compile-time-ness” is determined from 
 argument (cf. `auto ref`) and queried via a trait.
 
Nice, thanks! However, I think the technical part will need some more elaboration.
 if candidates contain enum parameters, constant folding must be attempted for
them, i.e. a candidate can only be excluded when an enum parameter is bound to
an argument for which constant folding failed.
I am not sure what you mean by constant folding. Do you mean CTFE has to be attempted? What are possible reasons for it to fail? E.g., if it throws an exception, will it match a runtime parameter instead?
 In the function body (including contracts and constraints), an enum
parameter’s value is a compile-time constant as if it were template value
parameter.
But it is not, so probably you have to specify some sort of lowering and/or name mangling strategy. Also, is there a way to manually instantiate the template? The idea I had (that I am not yet fully satisfied with) to bypass all of this was to require specific values for `enum` parameters. Then you'd do: ```d auto opSlice(size_t l, size_t u)(enum : l, enum : u) => slice!(l, u); ``` (Maybe there is better syntax. Mine is inspired from template specializations.) This way, you can manually instantiate the templates if you need to, and you also do not add a new category of symbol that requires updating the way D mangles names.
Apr 29
parent reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Monday, 29 April 2024 at 13:39:57 UTC, Timon Gehr wrote:
 On 4/25/24 19:56, Quirin Schroll wrote:
 https://github.com/Bolpat/DIPs/blob/2bfef0b05e99c5448b416078da2e743482e3193d/DIPs/1NNN-QFS.md
 
 This supersedes my idea of static indexing. The static 
 indexing DIP draft even said that if some way to pass values 
 as compile-time constants to functions, that would supersede 
 it.
 
 **Abstract**
 On function templates, allow `enum` to be used as a function 
 parameter storage class and a member function attribute. 
 Arguments binding to `enum` parameters must be compile-time 
 constants, as if template value parameters. With `auto enum`, 
 “compile-time-ness” is determined from argument (cf. `auto 
 ref`) and queried via a trait.
 
Nice, thanks! However, I think the technical part will need some more elaboration.
 if candidates contain enum parameters, constant folding must 
 be attempted for them, i.e. a candidate can only be excluded 
 when an enum parameter is bound to an argument for which 
 constant folding failed.
I am not sure what you mean by constant folding. Do you mean CTFE has to be attempted? What are possible reasons for it to fail? E.g., if it throws an exception, will it match a runtime parameter instead?
TL;DR: I thought constant-folding was the general term of evaluating stuff at compile-time and CTFE means executing a function at compile-time (as part of the constant-folding process). Constant folding is (technically, AFAIK) optional when binding to a run-time parameter: In the call `f(sqrt(2))`, the compiler may choose to CTFE `sqrt(2)` as part of optimization and call `f` on the literal value; or it issue a call to `sqrt` at runtime. An example for what my sentence means: ```d void f(enum int x) { } // A void f(long y) { } // B ``` The call `f(userInput)` requires overload resolution; that overload resolution can exclude overload `A` because it requires a compile-time value, but `userInput` is a run-time value, so only `B` remains. On the other hand, `f(10L + ctfeMystery())` will – due to D’s shenanigans – possibly `A` because `10L + ctfeMystery()` can be evaluated at compile-time and the value, albeit being typed `long` might fit into an `int` and therefore an `int` overload is preferred.
 In the function body (including contracts and constraints), an 
 enum parameter’s value is a compile-time constant as if it 
 were template value parameter.
But it is not, so probably you have to specify some sort of lowering and/or name mangling strategy.
I haven’t thought about mangling much as I thought that it will be possible somehow as the DIP adds a brand new concept. I know next to nothing about mangling, basically only what it means: Encoding the type of a function into a plain name.
 Also, is there a way to manually instantiate the template?
In my idea, the function is required to be a template for similar reasons a function is required to be a template for `auto ref`: It may generate different implementations under the same name. A plain function can’t do that. As with a function template with empty parameters (think `f()(auto ref T value)`), even if through the single choice in template parameters, not all instantiation are the same. So, as with `auto ref`, you could instantiate it, but values for `enum` parameters are passed by function call syntax. In the background, they could be lowered and mangled like template parameters.
 The idea I had (that I am not yet fully satisfied with) to 
 bypass all of this was to require specific values for `enum` 
 parameters. Then you'd do:

 ```d
 auto opSlice(size_t l, size_t u)(enum : l, enum : u) => 
 slice!(l, u);
 ```

 This way, you can manually instantiate the templates if you 
 need to, and you also do not add a new category of symbol that 
 requires updating the way D mangles names.
The way I’d view/frame your idea: An enum parameter allows you to infer a template value parameter form the value you pass as a function argument. Probably spelling out the template parameter should be optional, so that you _can_ control where in the template parameter list it appears if you need to (probably so that manually passing template parameters is possible), but if you just want to have enum parameters, the language adds the needed template value parameters at the end of the template parameter list. There’s one caveat, though: For an `auto enum`, you can’t spell it out in a template value parameter on the template – the parameter simply need not be there. I don’t know what to do about the call syntax, though. Probably `enum` parameters should be skipped in the function parameter part. Should one be allowed to partially pass template and enum parameters? Probably not. Consider: ```d struct X { auto f()(auto enum size_t i) { static if (__traits(isEnum, i) { ... } else { ... } } } X x; size_t i; ``` Then, `x.f(0)` lowers to `x.f!0()`, but `x.f(i)` lowers to `x.f!()(i)`, and the latter does not have any template value parameters. I’m considering this, it solves a problem that I intentionally didn’t address.
Apr 29
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 4/29/24 18:08, Quirin Schroll wrote:
 but if you just want to have enum parameters, the language adds the 
 needed template value parameters at the end of the template parameter list
The (logistical) issue with this is that e.g., the following is disallowed: ```d void foo(T...,int x)(){} ``` This gets back to the issue of name mangling, because I think there is no way to distinguish an instantiation where the last template parameter goes to `T...` and an instantiation where the last template parameter goes to `x`. Of course, this introduces many other issues unrelated to `enum` parameters, e.g., I think you cannot have a (non-nested) template accepting multiple delegate types with different but variadic parameter lists. So in general, some thought will have to be put into how to support IFTI.
Apr 29
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 4/29/24 18:32, Timon Gehr wrote:
 The (logistical) issue with this is that e.g., the following is disallowed:
 
 ```d
 void foo(T...,int x)(){}
 ```
 
 This gets back to the issue of name mangling, because I think there is no way
to distinguish an instantiation where the last template parameter goes to
`T...` and an instantiation where the last template parameter goes to `x`. 
Sorry, my bad. This point did not fully make sense, because `x` is not optional in this example. The problem with ambiguity only occurs if you have multiple variadic parameters (which are also disallowed, but you do not need them in your proposal). It is still true that D currently disallows adding any template parameters after the variadic parameter though.
Apr 29
prev sibling next sibling parent reply Paul Backus <snarwin gmail.com> writes:
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. 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. 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(fporward!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.
Apr 29
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 4/29/24 21:17, Paul Backus wrote:
 
 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(fporward!arg);
 }
If the `__traits(isEnum, arg)` is required at all (which I strongly doubt), it would be put into `forward`. The `auto enum` point stands, however, and another issue it causes is CTFE and template instantiation overhead even if the final function does not even accept `enum` parameters. I don't think this is even perfect forwarding, as it may cause CTFE that would not otherwise happen, which currently may actually change the observable behavior of the code due to e.g. floating-point shenanigans.
May 05
prev sibling parent reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
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.
May 07
parent reply Paul Backus <snarwin gmail.com> writes:
On Tuesday, 7 May 2024 at 11:23:10 UTC, Quirin Schroll wrote:
 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.
Good to know; thanks for looking into this. However, the main issue here is not `forward`--it's that in order to write correct generic code with this feature in place, D programmers will forever be obliged to defensively write `auto enum auto ref` on any parameter they intend to forward (and will have to go back and add `auto enum` to many of their existing `auto ref` parameters). Most D programmers already forget to add `auto ref` to such parameters (see for example the vast swathes of Phobos which fail to compile when used with non-copyable types). Adding an additional thing for them to forget is, like I said, setting them up for failure. (Also, `auto enum auto ref` is quite ugly and "attribute spam"-y, and some programmers will no doubt leave it out of their code intentionally in order to make the code look nicer--as they already do with existing attributes and parameter storage classes.)
May 07
next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On Tuesday, 7 May 2024 at 15:20:24 UTC, Paul Backus wrote:
 On Tuesday, 7 May 2024 at 11:23:10 UTC, Quirin Schroll wrote:
 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.
Good to know; thanks for looking into this. However, the main issue here is not `forward`--it's that in order to write correct generic code with this feature in place, D programmers will forever be obliged to defensively write `auto enum auto ref` on any parameter they intend to forward (and will have to go back and add `auto enum` to many of their existing `auto ref` parameters).
I don't think this is going to be the case. Consider that without explicitly forwarding, the difficulty is not in preserving refness, but non-refness. If care is not taken (e.g. via using forward), then a non-ref parameter suddenly turns into a ref parameter (implicitly). This is not the case for enum parameters -- enumness is preserved whether it's not an enum or is an enum. Yes, generic code that wishes to forward enum-ness to another thing must use auto enum to accomplish this. However, comparing to the the current mechanism, things are pretty inferior. Switching from an enum parameter to a runtime parameter involves *changing the place where parameters are put*. You currently have to remember to create a nested template with an outer template that takes a tuple for this to work correctly (something which I doubt many generic functions do). Put another way, if the cost of having the convenience of enum parameters is that we must also allow auto enum, I don't think this is a large enough drawback.
 Most D programmers already forget to add `auto ref` to such 
 parameters (see for example the vast swathes of Phobos which 
 fail to compile when used with non-copyable types). Adding an 
 additional thing for them to forget is, like I said, setting 
 them up for failure.

 (Also, `auto enum auto ref` is quite ugly and "attribute 
 spam"-y, and some programmers will no doubt leave it out of 
 their code intentionally in order to make the code look 
 nicer--as they already do with existing attributes and 
 parameter storage classes.)
Based on what Quirin explained, these attributes are not orthogonal. So `auto ref enum` or `auto enum ref` would be sufficient. It's at least a little better. But... even `auto enum ref Typename` is quite noisy, I wish we had a combining storage class to do all the inference. `auto ref` is kind of ugly on its own, it would be nice to find a better way. Note that `auto ref` dates back to a time without ` attributes` and also at a time of extreme prejudice against adding new keywords. -Steve
May 07
parent reply Paul Backus <snarwin gmail.com> writes:
On Tuesday, 7 May 2024 at 16:34:52 UTC, Steven Schveighoffer 
wrote:
 I don't think this is going to be the case. Consider that 
 without explicitly forwarding, the difficulty is not in 
 preserving refness, but non-refness. If care is not taken (e.g. 
 via using forward), then a non-ref parameter suddenly turns 
 into a ref parameter (implicitly). This is not the case for 
 enum parameters -- enumness is preserved whether it's not an 
 enum or is an enum.
If a single function in a call stack forgets to pass a given parameter by `ref`, it will be copied. If your code relies on the parameter not being copied (e.g., if it has a disabled copy constructor), this failure to preserve `ref` results in a bug. If a single function in a call stack forgets to pass a given parameter by `enum`, it will be evaluated at runtime. If your code relies on the parameter being evaluable at compile time (e.g., if it uses it as a template argument or in a `static if` condition), this failure to preserve `enum` results in a bug. The two are exactly analogous.
 Yes, generic code that wishes to forward enum-ness to another 
 thing must use auto enum to accomplish this. However, comparing 
 to the the current mechanism, things are pretty inferior. 
 Switching from an enum parameter to a runtime parameter 
 involves *changing the place where parameters are put*. You 
 currently have to remember to create a nested template with an 
 outer template that takes a tuple for this to work correctly 
 (something which I doubt many generic functions do).

 Put another way, if the cost of having the convenience of enum 
 parameters is that we must also allow auto enum, I don't think 
 this is a large enough drawback.
The argument here is essentially that it is worth it to make the library *author's* job more difficult in order to give the library *users* a better experience. In general, I agree. But there's a point at which this argument falls apart. If you make the library author's job so difficult that they can no longer write correct code, it is library users who will ultimately suffer. Personally, I think the `ref` storage class *already* crosses this line. The vast majority of generic D code does not handle non-copyable types correctly, and the design of `ref` is 100% to blame for this. Since `enum` uses the exact same design as `ref`, I think it's pretty reasonable to assume that it will cause the same kinds of problems. So, when I evaluate this DIP, here's the tradeoff I'm looking at: * **Pro:** more convenient library APIs (e.g., `format`). * **Con:** more bugs in generic library code. I don't think this is a good tradeoff.
May 07
next sibling parent reply Meta <jared771 gmail.com> writes:
On Tuesday, 7 May 2024 at 17:44:25 UTC, Paul Backus wrote:
 If a single function in a call stack forgets to pass a given 
 parameter by `ref`, it will be copied. If your code relies on 
 the parameter not being copied (e.g., if it has a disabled copy 
 constructor), this failure to preserve `ref` results in a bug.

 If a single function in a call stack forgets to pass a given 
 parameter by `enum`, it will be evaluated at runtime. If your 
 code relies on the parameter being evaluable at compile time 
 (e.g., if it uses it as a template argument or in a `static if` 
 condition), this failure to preserve `enum` results in a bug.
Won't this always result in a compile error, not a runtime bug?
May 07
parent Paul Backus <snarwin gmail.com> writes:
On Tuesday, 7 May 2024 at 22:32:04 UTC, Meta wrote:
 On Tuesday, 7 May 2024 at 17:44:25 UTC, Paul Backus wrote:
 If a single function in a call stack forgets to pass a given 
 parameter by `ref`, it will be copied. If your code relies on 
 the parameter not being copied (e.g., if it has a disabled 
 copy constructor), this failure to preserve `ref` results in a 
 bug.

 If a single function in a call stack forgets to pass a given 
 parameter by `enum`, it will be evaluated at runtime. If your 
 code relies on the parameter being evaluable at compile time 
 (e.g., if it uses it as a template argument or in a `static 
 if` condition), this failure to preserve `enum` results in a 
 bug.
Won't this always result in a compile error, not a runtime bug?
Yes. But in generic code, that compile error might not happen until a user instantiates it in a particular way that the library author hasn't thought of. (Again, see Phobos's handling of non-copyable types.)
May 07
prev sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
On Tuesday, 7 May 2024 at 17:44:25 UTC, Paul Backus wrote:
 On Tuesday, 7 May 2024 at 16:34:52 UTC, Steven Schveighoffer 
 wrote:
 I don't think this is going to be the case. Consider that 
 without explicitly forwarding, the difficulty is not in 
 preserving refness, but non-refness. If care is not taken 
 (e.g. via using forward), then a non-ref parameter suddenly 
 turns into a ref parameter (implicitly). This is not the case 
 for enum parameters -- enumness is preserved whether it's not 
 an enum or is an enum.
If a single function in a call stack forgets to pass a given parameter by `ref`, it will be copied. If your code relies on the parameter not being copied (e.g., if it has a disabled copy constructor), this failure to preserve `ref` results in a bug. If a single function in a call stack forgets to pass a given parameter by `enum`, it will be evaluated at runtime. If your code relies on the parameter being evaluable at compile time (e.g., if it uses it as a template argument or in a `static if` condition), this failure to preserve `enum` results in a bug. The two are exactly analogous.
If an entire ref chain is broken by one copy, then the *semantics* are different. That is, all of a sudden, you aren't affecting the original parameter. If an entire enum chain is broken by one runtime evaluation, the *code does not compile*. This is either a loud error, or a silent acceptance *that does not affect correctness*. Of course, this assumes that the auto-enum in question does not do something drastically different based on enumness. By example: ```d void foo(auto ref int x) { bar(x); } void bar(int x) { baz(x); // oops, this does not affect the caller's x } void baz(auto ref int x) { ++x; } // and with enums: void foo(auto enum int x) { bar(x); } void bar(int x) { baz(x); } void baz(auto enum int x) { writeln(x); // same whether bar is enum or not, no semantic difference } ``` Yes, if `baz` accepts by enum only, you will get a *compiler error*. Contrast that with `ref`, where you get no indication that something is wrong, and it is semantically wrong.
 Yes, generic code that wishes to forward enum-ness to another 
 thing must use auto enum to accomplish this. However, 
 comparing to the the current mechanism, things are pretty 
 inferior. Switching from an enum parameter to a runtime 
 parameter involves *changing the place where parameters are 
 put*. You currently have to remember to create a nested 
 template with an outer template that takes a tuple for this to 
 work correctly (something which I doubt many generic functions 
 do).

 Put another way, if the cost of having the convenience of enum 
 parameters is that we must also allow auto enum, I don't think 
 this is a large enough drawback.
The argument here is essentially that it is worth it to make the library *author's* job more difficult in order to give the library *users* a better experience.
No, I think it makes both easier. Consider the case of `writef`. We have two versions of `writef`, one which takes a string format via a runtime parameter, and one which takes a string format via compile time parameter. They have to be called in different ways (burden on the user). You have to write two different functions, and overload them based on template constraints (burden on the developer). ```d void writef(alias fmt, A...)(A args) if (isSomeString!(typeof(fmt))) { ... } void writef(Char, A...)(in Char[] fmt, A args) { ... } // vs void writef(Char, A...)(auto enum Char[] fmt, A args) // note the lack of template constraints needed { ... } ``` It also has the benefit of being easier to explain and easier to document.
 In general, I agree. But there's a point at which this argument 
 falls apart. If you make the library author's job so difficult 
 that they can no longer write correct code, it is library users 
 who will ultimately suffer.
I don't think this case has been made, even with `auto enum`.
 Personally, I think the `ref` storage class *already* crosses 
 this line. The vast majority of generic D code does not handle 
 non-copyable types correctly, and the design of `ref` is 100% 
 to blame for this. Since `enum` uses the exact same design as 
 `ref`, I think it's pretty reasonable to assume that it will 
 cause the same kinds of problems.
I can't see how `ref` is responsible for this. Non-copyable types are difficult to work with in general. Can you explain further?
 So, when I evaluate this DIP, here's the tradeoff I'm looking 
 at:

 * **Pro:** more convenient library APIs (e.g., `format`).
 * **Con:** more bugs in generic library code.
I have different opinions of these categories. -Steve
May 07
prev sibling parent reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Tuesday, 7 May 2024 at 15:20:24 UTC, Paul Backus wrote:
 On Tuesday, 7 May 2024 at 11:23:10 UTC, Quirin Schroll wrote:
 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.
Good to know; thanks for looking into this. However, the main issue here is not `forward`--it's that in order to write correct generic code with this feature in place, D programmers will forever be obliged to defensively write `auto enum auto ref` on any parameter they intend to forward (and will have to go back and add `auto enum` to many of their existing `auto ref` parameters).
Well, there’s always going to be legacy code that doesn’t interact well with the latest features. It’s unfortunate, but true for every new feature that extends APIs. What are we gonna do? Never ever extend APIs?
 Most D programmers already forget to add `auto ref` to such 
 parameters (see for example the vast swathes of Phobos which 
 fail to compile when used with non-copyable types). Adding an 
 additional thing for them to forget is, like I said, setting 
 them up for failure.
I cannot help but find this argument quite weak. It only concerns library authors. If you write production code and find that a function needs to change and forward `ref`, you can just change it to do so. Same with `enum`. It’s the same with attributes. On the other hand, if a library author doesn’t bother to annotate functions as ` safe`, the library isn’t usable for SafeD.
 (Also, `auto enum auto ref` is quite ugly and "attribute 
 spam"-y, and some programmers will no doubt leave it out of 
 their code intentionally in order to make the code look 
 nicer--as they already do with existing attributes and 
 parameter storage classes.)
The only thing I could imagine doing about this is shortening it to `auto enum ref`.
May 16
parent Paul Backus <snarwin gmail.com> writes:
On Thursday, 16 May 2024 at 13:59:35 UTC, Quirin Schroll wrote:
 Well, there’s always going to be legacy code that doesn’t 
 interact well with the latest features. It’s unfortunate, but 
 true for every new feature that extends APIs.
 What are we gonna do? Never ever extend APIs?
What we do is, we weigh the costs and benefits of each proposed change, and decide accordingly. I agree that there are cases where introducing incompatibilities with "legacy code" is justified; I just don't think this is one of them.
 I cannot help but find this argument quite weak. It only 
 concerns library authors. If you write production code and find 
 that a function needs to change and forward `ref`, you can just 
 change it to do so. Same with `enum`. It’s the same with 
 attributes.
Given how often people complain that D needs more libraries, I think it's in our best interest to make library authors' lives easier, not harder.
May 16
prev sibling parent reply IchorDev <zxinsworld gmail.com> writes:
On Thursday, 25 April 2024 at 17:56:59 UTC, Quirin Schroll wrote:
 **Abstract**
 On function templates, allow `enum` to be used as a function 
 parameter storage class and a member function attribute. 
 Arguments binding to `enum` parameters must be compile-time 
 constants, as if template value parameters. With `auto enum`, 
 “compile-time-ness” is determined from argument (cf. `auto 
 ref`) and queried via a trait.
I have been dying to have this feature in my hands for since I first read about it nearly 2 years ago. The fact that this DIP keeps getting left in limbo is very disappointing. I’m a library author, I write a lot of compile-time code, and in private I have written my own `std.traits`-style library before. This DIP solves one of my most longstanding frustrations with writing functions that have a compile-time optimised variant. The possibility of enhancing IES is also really exciting. I haven’t commented on this DIP here until now because I actually didn’t notice this thread. My only real gripe with the DIP is that `isEnum` should only work for enum parameters. Having it check for enum membership will surely create bugs in generic code somewhere.
Jul 31
parent reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Wednesday, 31 July 2024 at 08:15:31 UTC, IchorDev wrote:
 My only real gripe with the DIP is that `isEnum` should only 
 work for enum parameters. Having it check for enum membership 
 will surely create bugs in generic code somewhere.
I truly wonder how. In a function template, you can’t define an `enum` locally that has the same name as a parameter. And if there’s an eponymous `enum` constant outside the function’s scope, the name of the argument refers to the argument (unless you use `with`). It says that it checks for enum *membership,* but the most relevant special case would be for simple `enum` constants, which are technically a shorthand for defining a member of an unnamed `enum`, so those are implicitly included. Considering `__traits(isRef)` works to detect `ref` variables, too (in the current implementation), it would be weird if `__traits(isEnum)` wouldn’t detect `enum` other than `enum` parameters. Is it really a big issue that `__traits(isEnum, arg)` may return `true` if you misspell `arg` so that it accidentally becomes some `enum`? It’s not like it would be an error to use `isEnum` on it, it’d just return `false` instead, which IMO is just worse.
Jul 31
parent reply IchorDev <zxinsworld gmail.com> writes:
On Wednesday, 31 July 2024 at 22:55:36 UTC, Quirin Schroll wrote:
 I truly wonder how. In a function template, you can’t define an 
 `enum` locally that has the same name as a parameter. And if 
 there’s an eponymous `enum` constant outside the function’s 
 scope, the name of the argument refers to the argument (unless 
 you use `with`). It says that it checks for enum *membership,* 
 but the most relevant special case would be for simple `enum` 
 constants, which are technically a shorthand for defining a 
 member of an unnamed `enum`, so those are implicitly included.
It could still happen in string mixins, and generally we want a trait to mean one specific thing.
 It’s not like it would be an error to use `isEnum` on it, it’d 
 just return `false` instead, which IMO is just worse.
Silently doing the wrong thing instead of giving an error message IS worse—this is D, not JavaScript! It sounds like you intend the DIP to mean `isEnum` checks for manifest constant declarations? This is really not clear in the text, it sounds like you want it to check whether `is(typeof(symbol) == enum)`. You could probably clarify this a little better. So, does `isEnum` yield `true` for template value parameters? If not, I think it’s too specific. Why not generalise it to all manifest constants and call it `isManifestConstant` (or just `isManifest`) instead? This could benefit meta-programming and potential future language features.
Aug 01
parent Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Friday, 2 August 2024 at 03:49:22 UTC, IchorDev wrote:
 On Wednesday, 31 July 2024 at 22:55:36 UTC, Quirin Schroll 
 wrote:
 I truly wonder how. In a function template, you can’t define 
 an `enum` locally that has the same name as a parameter. And 
 if there’s an eponymous `enum` constant outside the function’s 
 scope, the name of the argument refers to the argument (unless 
 you use `with`). It says that it checks for enum *membership,* 
 but the most relevant special case would be for simple `enum` 
 constants, which are technically a shorthand for defining a 
 member of an unnamed `enum`, so those are implicitly included.
It could still happen in string mixins, and generally we want a trait to mean one specific thing.
 It’s not like it would be an error to use `isEnum` on it, it’d 
 just return `false` instead, which IMO is just worse.
Silently doing the wrong thing instead of giving an error message IS worse—this is D, not JavaScript! It sounds like you intend the DIP to mean `isEnum` checks for manifest constant declarations? This is really not clear in the text, it sounds like you want it to check whether `is(typeof(symbol) == enum)`. You could probably clarify this a little better. So, does `isEnum` yield `true` for template value parameters? If not, I think it’s too specific. Why not generalise it to all manifest constants and call it `isManifestConstant` (or just `isManifest`) instead? This could benefit meta-programming and potential future language features.
I'll try to reword the DIP. Checking if a type or an expression's type is an enemy type is quite easy as of now. Checking if some expression is a compile-time constant is not as trivial, but it's not exactly hard either. Checking if a symbol is a manifest constant, as far as I know, isn't possible. Are template value parameters manifest constants? Good question. They're compile-time constants, but not every compile-time constant is a manifest constant (e.g. `static immutable` values aren't), so maybe the answer is no. Literals aren't manifest constants either. There are many forms of compile-time constants. Yes, this isn't JavaScript, but again, `isRef` doesn't check if it's a `ref` parameter, it checks if a symbol is a reference, and with the upcoming [v2.111](https://dlang.org/changelog/pending.html#dmd.reflocal), that will include local variables, possibly more. `isEnum` checks if something is declared using `enum`, in particular after inference from arguments. The only thing that `isRef` has to contend with is that it doesn't detect `ref` returning functions and one could argue it should. But that's beside the point as Enum Parameters doesn't propose enum returns, whatever that would even mean.
Aug 02