September 26, 2022

On Friday, 23 September 2022 at 16:44:05 UTC, Quirin Schroll wrote:

>

On Friday, 23 September 2022 at 16:17:01 UTC, ryuukk_ wrote:

>

enum is supposed to be an enumeration, not a _ for compile time usecase, in fact, comptime is a better name, so i personally would love if it was changed to that, if that's possible.

It probably must be an existing keyword. comptime would be possible as a contextual keyword, something Walter opposes. static is the only other candidate. I’ve looked through the whole list of keywords.

Nevermind. I just found out that attributes for function parameters are a thing as of late. As with @safe, @nogc and @property, @comptime could be another compiler-recognized attribute.

As a member function attribute, @comptime intuitively looks more like it means that the function can only be used for CTFE and never at run-time (cf. consteval in C++; something like this was suggested for D before).

September 26, 2022

On Friday, 23 September 2022 at 17:54:49 UTC, Paul Backus wrote:

>

On Friday, 23 September 2022 at 15:41:21 UTC, Quirin Schroll wrote:

>

Read the draft here: https://github.com/Bolpat/DIPs/blob/EnumParameters/DIPs/1NNN-QFS.md

Feedback is welcome.

Some thoughts.

  • Does this need __traits(isEnum) (like __traits(isRef)) for working with auto enum parameters?

Good catch, __traits(isEnum) does not exist currently. But it makes sense to add it, not only for this proposal, but generally. I’ll add that.

>
  • Sometimes library authors prefer to have a parameter passed at runtime even when it could be passed at compile time, to avoid template bloat. So for use cases like format and regex, this proposal is not a clear win.

Is that the case? Of course, you get one additional template instantiation for regex because it normally is not a template at all. format is already a template; with a template argument format, you ideally only get a different instantiation. A high number of template instances is usually only a problem if the templates are recursive. If it happens to be a problem, one can always put the format or regex in a local variable and use that. This workaround is harder if you also want the value to be an rvalue; you’ll then need to define a function that returns the value and takes a run-time value as a parameter (that it won’t actually use) so that CTFE is out of the picture.
This is a niche case; I guess it’s okay for this to be a little work.

>
  • A significant chunk of the proposed use-cases could also be addressed by adding opStaticIndex. Probably worth mentioning in the "Alternatives" section.

I wrote a draft for static indexing. There, I mentioned that something like this, i.e. a feature to pass parameters at compile-time were added, it would supersede the proposal. I figured that it was actually easier to specify and explain compile-time parameter passing than static indexing.

Static indexing with a completely separate set of operators almost doubles the jungle of indexing operators and is very specific.

>
  • Incompatibility with other parameter storage classes is a design smell. Ideally, we would like our language features to be orthogonal and work together with each other.

Maybe you got this wrong. It is not that by virtue of enum any thinkable parameter storage class would be incompatible. It is just that all of the existing ones are.

enum is a stronger form of in and thus is in the same group with in, out, and ref, which specify parameter passing. Two of them together would have a very specific meaning and not simply the rules for both because that would be contradictory.

Let’s go through the list:

  • auto can only be used in conjunction with ref to form auto ref. Alone, it is invalid.
  • TypeCtor are allowed. It says so in the DIP.
  • final as far as I know is only there to generate a specific error; it is never valid.
  • in (see below)²
  • lazy makes no sense; the value is already produced at compile-time.
  • out is incompatible with enum because enum values are never lvalues and cannot be written to.
  • ref is incompatible with enum because enum values are never lvalues.
  • return is really only something for run-time values.¹
  • scope is really only something for run-time values.¹

¹ To be honest, I don’t know how scope and by extension return would apply to compile-time constants. If I’m not mistaken, life-time of those objects is infinite.

² The in storage class means const scope and maybe ref. enum is incompatible with ref, scope does not apply.

>
  • Similarity to ref is also a design smell. ref is badly designed, and things like auto ref and core.lifetime.forward exist entirely to work around its deficiencies. We should avoid making the same mistake twice.

I see how auto ref has flaws because the value category is not preserved, i.e. the parameter is an lvalue regardless whether it was initialized by an rvalue or lvalue. This is where the analogy parts. An auto enum parameter initialized by a compile-time constant is a compile-time constant and an auto enum parameter initialized by a run-time value is a run-time value. Nothing has to be done to forward.

The analogy is limited to the way overloading and inference on the category (value category for ref and compile-time category for enum) works.

>

[…] I can see situations where this would be useful, but I'm not convinced it's useful enough to justify the language-complexity and maintenance costs.

That’s what I’m trying to find out.

September 26, 2022

On Saturday, 24 September 2022 at 07:51:32 UTC, Imperatorn wrote:

>

On Friday, 23 September 2022 at 15:41:21 UTC, Quirin Schroll wrote:

>

Read the draft here: https://github.com/Bolpat/DIPs/blob/EnumParameters/DIPs/1NNN-QFS.md

Feedback is welcome.

I'm a bit torn about static vs enum. I partly agree with prior speakers about enum getting too many meanings, but on the other hand, it's not really a problem imo.

Its not really adding a new meaning, enum already (somtimes) means "do this at compile time", that's really the problem, that usage of enum has literally nothing to do with enumeration. It's like we've run out of English words so we have to use "count" any time we want to say "now".

Using enum to mean "compile time" or "manifest constant" should be depreciated in favour of a keyword that actually at least vaguely relates to those meanings.

September 26, 2022

On Saturday, 24 September 2022 at 00:12:35 UTC, Timon Gehr wrote:

>

Looks useful.

Thanks.

>
  • The author's name is spelled wrong.

Happens to the best. 😅

>
  • The description of auto enum makes it not fully clear if the compiler attempts to interpret the argument with CTFE or not to check whether it is a compile-time constant. Same question about overloads. (Though for overloads I would expect CTFE to be attempted, as in the first step it has to be determined which overloads even match.)

Yes, I have to make this clear. CTFE must be attempted when binding to auto enum. It only becomes to non-enum if enum (without auto) would not be able to bind the argument. (Similarly, auto ref is required to bind as ref even if it could bind via copy.)

>
  • The special-case qualifier and storage class behavior is not necessary, just make it behave like a normal enum variable.

I don’t really know what you mean exactly. Maybe this section:

>

The enum storage class is incompatible with any other storage classes except type constructors. Using them together is a compile error. Type constructors are allowed, but have no effect because compile-time values are immutable and for any type constructor qual and every type T, we have qual(immutable T)immutable T).

By the way, I’ve figured the explanation in the DIP is wrong. enum values are not immutable or effectively immutable. It appears that the semantics of enum currently make the value a literal for the first and second level of indirection. The other parts are mutable unless qualified, and do not count as mutable global state as far as pure is concerned. Issue 23375

I realized when answering to Paul Backus about the incompatibility with storage classes, in particular scope. I created some examples with indirections and found bugs.

September 26, 2022

Sorry in advance for the long answer.

On Saturday, 24 September 2022 at 01:25:17 UTC, Nicholas Wilson wrote:

>

Regarding Prior Art, this reminds me a lot of C++'s auto function parameters that make the function a template function without the need of the syntax overhead that comes with C++.

The key difference here is that C++ template syntax is verbose and auto is a syntax sugar.
I thought about having functions with enum parameters be templates implicitly. I decided against that. For one, it could be allowed later if deemed useful, for second, making a function a function template in D requires adding () between the name and the parameter list. I.e.

void f(enum int i) { } // error, f is not a template
void g()(enum int i) { } // this is how it’s done
>

It also reminds me of SPIR-V specialisation constants, which serve a different purpose of late binding values for the optimiser.

I know nothing about SPIR-V. If you don’t mind, you can explain it to me or send me a link to a description.

>

Those are two incompatible meanings, and it is not entirely clear which one the DIP wants to be. I think the best option would be to have a static opSlice be something else and have enum parameters be values that must resolve at compile time.

Do you mean that that they must resolve at compile-time and then be run-time values? That would throw away valuable information with virtually no gain.

>

This DIP makes no mention of what one can do with an enum parameter.

It does, three examples are provided: format, regex, static indexing. You can do with them whatever you can do with template value parameters.

>

In particular it would be very useful to be able to use CTFE with it, and do static assertions (e.g. check signatures of format strings). and (not that I'm sure there is any easy way to do it) being able to differentiate (and let the compiler know, probably more of a problem for DMD) if the intention is to reduce template bloat (in the case of format) or use the value as a specialisation constant.

That is exactly the specified semantics. I thought about defining the feature in terms or rewrites. Those would make the proposal more understandable, but also constrain the implementation. Also, it would define things to be a certain way that could be depended upon.

Potential rewrite implementation examples for

void f(T)(enum T x, T y) { /+impl+/ }

(A) Append the enum parameters to the template argument list (cf. C++ auto parameters).

void f(T, T x)(T y) { /+impl+/ }
// which in turn is actually
template f(T, T x)
{
    void f(T y) { /+impl+/ }
}
f(10, 20) // equivalently, 10.f(20)
// is actually
f!(int, 10)(20)

(B) Nested template

template f(T)
{
    template f(T x)
    {
        void f(T y) { /+impl+/ }
    }
}
f(10, 20)
// is actually
(f!int)!10(20) // note: syntactically invalid,
// i.e. in code, it must be:
Instantiate!(f!int, 10)(20)

I guess both have their upsides and downsides. I did not investigate this further. Again, it need not be a rewrite at all, and I don’t think it will be, unless someone has a great idea that has no weird semantics.

> >

cf. a ref parameter only binds to lvalues

This is not the case, for C++ interop reasons (among others) const ref can bind rvalues.

I tried on run.dlang.io/nightly, this statement is wrong; const ref parameters cannot bind rvalues.
You can, however, use in instead of const ref. With -preview=in, in on parameters of an extern(C++) function means what const& means in C++.
Example:

extern(C++) void f(const ref int x) { }
extern(C++) void g(in int x) { }
void main()
{
    f(1); // error
    g(1); // good
}

DIP 1016 wanted ref to be able to bind rvalues. It was rejected.

September 26, 2022

On Saturday, 24 September 2022 at 07:51:32 UTC, Imperatorn wrote:

>

I'm a bit torn about static vs enum. I partly agree with prior speakers about enum getting too many meanings, but on the other hand, it's not really a problem imo.

One biggie against using static is that on member functions it would have to go to the back (where it is currently invalid) with a totally different meaning than in front (although it would make the function a static).

Non-member:

void f()(static int ctValue) { } // this is fine

Member:

struct S
{
    void f()() static { } // different from:
    static void g()() { }

    void p()() enum { } // same as
    enum void q()() { }
}

This would be no problem if D did not allow putting some stuff before and after the function declaration with the same meaning. I really dislike void f()() static { }.

Currently, enum is valid for member functions like q, but does nothing. That would change, but I’d bet that almost no one has this in their code base anyway, so breakage would be minimal.

September 26, 2022

On Monday, 26 September 2022 at 09:18:54 UTC, claptrap wrote:

>

Using enum to mean "compile time" or "manifest constant" should be depreciated in favour of a keyword that actually at least vaguely relates to those meanings.

The value of that would be close to zero. At least in Phobos, enum is more often used to define a single value than an enumeration type.

With a new keyword, we’d need to deprecate enum or have both, also we’d have make sure the new keyword is dissimilar to identifiers currently used not to break code. On parameters, a compiler-recognized UDA, say @comptime, would be an alternative, but that cannot be trivially used instead of enum without special-casing it in the grammar. Inconsistency is worse than bad naming. Also, I don’t think that enum is actually a bad name. It has a specific meaning in D, but apart from that, it’s not that bad.

[Half Joking] Another option would be no keyword at all, but some kind of other token. But I guess no one prefers f()(^int x) or similar over f(enum int x).

September 26, 2022

On Monday, 26 September 2022 at 11:23:58 UTC, Quirin Schroll wrote:

>

On Saturday, 24 September 2022 at 07:51:32 UTC, Imperatorn wrote:

>

[...]

One biggie against using static is that on member functions it would have to go to the back (where it is currently invalid) with a totally different meaning than in front (although it would make the function a static).

[...]

True, and we're used to enum behaving like that, so I guess it's preferrable

September 26, 2022

On Monday, 26 September 2022 at 09:18:54 UTC, claptrap wrote:

>

On Saturday, 24 September 2022 at 07:51:32 UTC, Imperatorn wrote:

>

On Friday, 23 September 2022 at 15:41:21 UTC, Quirin Schroll wrote:

>

[...]

I'm a bit torn about static vs enum. I partly agree with prior speakers about enum getting too many meanings, but on the other hand, it's not really a problem imo.

Its not really adding a new meaning, enum already (somtimes) means "do this at compile time", that's really the problem, that usage of enum has literally nothing to do with enumeration. It's like we've run out of English words so we have to use "count" any time we want to say "now".

Using enum to mean "compile time" or "manifest constant" should be depreciated in favour of a keyword that actually at least vaguely relates to those meanings.

Hmm, yes, this is the other sane option. To actually introduce a new keyword to differentiate

September 26, 2022

On Monday, 26 September 2022 at 11:55:00 UTC, Imperatorn wrote:

>

On Monday, 26 September 2022 at 09:18:54 UTC, claptrap wrote:

>

On Saturday, 24 September 2022 at 07:51:32 UTC, Imperatorn wrote:

>

[...]

Its not really adding a new meaning, enum already (somtimes) means "do this at compile time", that's really the problem, that usage of enum has literally nothing to do with enumeration. It's like we've run out of English words so we have to use "count" any time we want to say "now".

Using enum to mean "compile time" or "manifest constant" should be depreciated in favour of a keyword that actually at least vaguely relates to those meanings.

Hmm, yes, this is the other sane option. To actually introduce a new keyword to differentiate

Maybe a keyword like fixed, invariant could be used. I don't know how immutable would work since it's already used.