digitalmars.dip.development - Frist Draft (in this forum): Enum Parameters
- Quirin Schroll (11/11) Apr 25 https://github.com/Bolpat/DIPs/blob/2bfef0b05e99c5448b416078da2e743482e3...
- Richard (Rikki) Andrew Cattermole (9/9) Apr 25 Enum as a keyword has quite different meaning, I suggest ``__ctfe``
- Quirin Schroll (31/46) Apr 25 But that is exactly what I propose.
- Richard (Rikki) Andrew Cattermole (2/24) Apr 27 It wasn't jumping out at me, so this could do with some reworking.
- Steven Schveighoffer (7/18) Apr 25 I would love to see this happen. This is like a streamlining of
- Quirin Schroll (8/10) Apr 26 Please, elaborate. I was somewhat absent from the forums while
- Richard (Rikki) Andrew Cattermole (10/18) Apr 27 String interpolation slices and dices the string up into metadata
- Steven Schveighoffer (22/32) May 07 I realize I forgot to expand on this.
- Timon Gehr (19/34) Apr 29 Nice, thanks! However, I think the technical part will need some more
- Quirin Schroll (70/112) Apr 29 TL;DR: I thought constant-folding was the general term of
- Timon Gehr (14/16) Apr 29 The (logistical) issue with this is that e.g., the following is disallow...
- Timon Gehr (7/14) Apr 29 Sorry, my bad. This point did not fully make sense, because `x` is not
- Paul Backus (34/35) Apr 29 Yet another non-orthogonal language feature that all generic code
- Timon Gehr (9/22) May 05 If the `__traits(isEnum, arg)` is required at all (which I strongly
- Quirin Schroll (66/104) May 07 TL;DR: While `auto enum` is similar to `auto ref` in that it
- Paul Backus (18/23) May 07 Good to know; thanks for looking into this.
- Steven Schveighoffer (28/51) May 07 I don't think this is going to be the case. Consider that without
- Paul Backus (29/47) May 07 If a single function in a call stack forgets to pass a given
- Meta (2/11) May 07 Won't this always result in a compile error, not a runtime bug?
- Paul Backus (5/19) May 07 Yes. But in generic code, that compile error might not happen
- Steven Schveighoffer (60/109) May 07 If an entire ref chain is broken by one copy, then the
- Quirin Schroll (13/36) May 16 Well, there’s always going to be legacy code that doesn’t
- Paul Backus (8/17) May 16 What we do is, we weigh the costs and benefits of each proposed
- IchorDev (14/21) Jul 31 I have been dying to have this feature in my hands for since I
- Quirin Schroll (18/21) Jul 31 I truly wonder how. In a function template, you can’t define an
- IchorDev (15/25) Aug 01 It could still happen in string mixins, and generally we want a
- Quirin Schroll (23/49) Aug 02 I'll try to reword the DIP.
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
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
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
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:It wasn't jumping out at me, so this could do with some reworking.[…] 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.
Apr 27
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
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
On 26/04/2024 9:39 PM, Quirin Schroll wrote:On Friday, 26 April 2024 at 02:47:18 UTC, Steven Schveighoffer wrote: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.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 27
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 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) -SteveI 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.
May 07
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
On Monday, 29 April 2024 at 13:39:57 UTC, Timon Gehr wrote:On 4/25/24 19:56, Quirin Schroll wrote: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.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?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.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?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
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 listThe (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
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
On Thursday, 25 April 2024 at 17:56:59 UTC, Quirin Schroll wrote:https://github.com/Bolpat/DIPs/blob/2bfef0b05e99c5448b416078da2e743482e3193d/DIPs/1NNN-QFS.mdYet 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
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
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: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.https://github.com/Bolpat/DIPs/blob/2bfef0b05e99c5448b416078da2e743482e3193d/DIPs/1NNN-QFS.mdYet 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.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
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
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: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.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.)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
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
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
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: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.)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
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: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.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.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.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.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
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: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?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.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
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
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
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
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
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'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.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 02