Frist Draft (in this forum): Enum Parameters

Quirin Schroll qs.il.paperinik at gmail.com
Mon Apr 29 16:08:03 UTC 2024


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.


More information about the dip.development mailing list