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:
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:
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:
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.