Thread overview
Brainstorming: Explicit instantiation of function template with `auto ref` parameters
Feb 21
monkyyy
Feb 21
Basile B.
Feb 24
Manu
Feb 24
M. M.
Feb 24
Dukc
February 20

Function templates with auto ref parameters can’t be explicitly instantiated, but must be IFTI’d to infer ref-ness of auto ref parameters.

When doing meta-programming, that is annoying. Some template might have verified that some alias refers to a function template and that it can be called with the kinds of arguments the template wants to use. Great job, D, for making this rather easy! However, for some reason, we need a function pointer (or delegate) to the respective instance. That is always possible, except if the alias is to a function template with auto ref parameters.

We should find a solution to this. It does not have to be pretty, there should be just some way to do it.

The ideal test case is a function template with a sequence parameter and auto ref on non-template-argument-type parameters:

void example(Ts...)(auto ref int arg, auto ref Ts args) { }

auto fp = &example/*YOUR IDEA*/;

One way I thought could work is just passing ref T: example!(ref long), but that doesn’t work for the int parameter.

While I think the following looks okay, it occupies the syntax ![] which we might use for something more useful:

auto fp1 = &example![ref](); // void function(ref int)
auto fp2 = &example![auto](); // void function(int)

auto fp1 = &example![ref, auto, ref](char, wchar); // void function(ref int, char, ref wchar)
auto fp2 = &example![auto, ref, auto](char, wchar); // void function(int, ref char, wchar)

While required for explicit function template instantiation, even IFTI can profit from it: Not every lvalue argument should be passed by reference.

int x;
example(x); // Full IFTI: `ref`, Ts empty
example!()(x); // Partial IFTI: infers `ref`, Ts empty, same as above
example![ref]()(x); // No IFTI: `ref` explicit, Ts empty, same as above
example![auto]()(x); // No IFTI: `auto` means not `ref`. Pass `x` by value/copy

February 21

On Thursday, 20 February 2025 at 23:24:10 UTC, Quirin Schroll wrote:

>

Function templates with auto ref parameters can’t be explicitly instantiated, but must be IFTI’d to infer ref-ness of auto ref parameters.

[...]

unittest? Im not entirely sure its impossible

February 21

On Thursday, 20 February 2025 at 23:24:10 UTC, Quirin Schroll wrote:

>

[...]
One way I thought could work is just passing ref T: example!(ref long), but that doesn’t work for the int parameter.

maybe a way to go further with this idea is not to forget that we know the name of the int parameter, so something like

void example(Ts...)(auto ref int arg, auto ref Ts args) { }

// void function(ref int arg);
auto fp1 = &example!(ref int arg);

// void function(ref int, char, ref wchar);
auto fp2 = &example!(ref int arg, char, ref wchar);

looks possible even if that only works if the parameter name is specified.

But otherwise the other syntax, I mean the one you've explained more, would work I think. That looks a bit like if the instantiation is for an array literal but it can be distinguished with two lookups (unless I'm loosing my D, an expression cannot start with auto or ref ?), or otherwise three (once reached a comma or a closing squared bracket).

February 21

On Thursday, 20 February 2025 at 23:24:10 UTC, Quirin Schroll wrote:

>

Function templates with auto ref parameters can’t be explicitly instantiated, but must be IFTI’d to infer ref-ness of auto ref parameters.

When doing meta-programming, that is annoying. Some template might have verified that some alias refers to a function template and that it can be called with the kinds of arguments the template wants to use. Great job, D, for making this rather easy! However, for some reason, we need a function pointer (or delegate) to the respective instance. That is always possible, except if the alias is to a function template with auto ref parameters.

It seems to me like this is a general problem with IFTI and/or overload resolution, not just auto ref. For example, suppose we use separate overloads instead of auto ref:

template fun(T)
{
    void fun(    T x,     T y) {}
    void fun(ref T x,     T y) {}
    void fun(    T x, ref T y) {}
    void fun(ref T x, ref T y) {}
}

In this example, fun is callable using IFTI with any combination of lvalue and rvalue arguments, but obtaining a pointer to the specific overload of fun that's called for a given argument list is, let's say, non-trivial.

So, given the above, here's my proposed solution for this entire class of problems:

int lvalue;
enum int rvalue = 123;

auto fp = &__traits(resolve, fun(lvalue, rvalue));
// fp == &fun!int.fun(ref int, int)

__traits(resolve) takes a function call expression as its argument, and returns an alias to the function symbol that would be called by that expression, with both IFTI and overload resolution taken into account. If either IFTI or overload resolution fail, __traits(compiles) also fails--errors are not gagged.

February 24
Just yet another in the endless stream of cases why ref should be part of
the type and not a 'storage class'! Literally everything ref touches gets
more complex than it should.
*🎉*

On Fri, 21 Feb 2025 at 09:26, Quirin Schroll via Digitalmars-d < digitalmars-d@puremagic.com> wrote:

> Function templates with `auto ref` parameters can’t be explicitly instantiated, but must be IFTI’d to infer `ref`-ness of `auto ref` parameters.
>
> When doing meta-programming, that is annoying. Some template might have verified that some alias refers to a function template and that it can be called with the kinds of arguments the template wants to use. Great job, D, for making this rather easy! However, for some reason, we need a function pointer (or delegate) to the respective instance. That is always possible, except if the alias is to a function template with `auto ref` parameters.
>
> We should find a solution to this. It does not have to be pretty, there should be just *some* way to do it.
>
> The ideal test case is a function template with a sequence
> parameter and `auto ref` on non-template-argument-type parameters:
> ```d
> void example(Ts...)(auto ref int arg, auto ref Ts args) { }
>
> auto fp = &example/*YOUR IDEA*/;
> ```
>
> One way I thought could work is just passing `ref T`: `example!(ref long)`, but that doesn’t work for the `int` parameter.
>
> While I think the following looks okay, it occupies the syntax `![]` which we might use for something more useful:
>
> ```d
> auto fp1 = &example![ref](); // void function(ref int)
> auto fp2 = &example![auto](); // void function(int)
>
> auto fp1 = &example![ref, auto, ref](char, wchar); // void
> function(ref int, char, ref wchar)
> auto fp2 = &example![auto, ref, auto](char, wchar); // void
> function(int, ref char, wchar)
> ```
>
> While required for explicit function template instantiation, even
> IFTI can profit from it: Not every lvalue argument should be
> passed by reference.
> ```d
> int x;
> example(x); // Full IFTI: `ref`, Ts empty
> example!()(x); // Partial IFTI: infers `ref`, Ts empty, same as
> above
> example![ref]()(x); // No IFTI: `ref` explicit, Ts empty, same as
> above
> example![auto]()(x); // No IFTI: `auto` means not `ref`. Pass `x`
> by value/copy
>
> ```
>


February 24
On Monday, 24 February 2025 at 00:24:03 UTC, Manu wrote:
> Just yet another in the endless stream of cases why ref should be part of
> the type and not a 'storage class'!

Modern languages avoid doing that, just like D. Herb Sutter:

> C++ references were invented to be used as function parameter/return types, and that’s what they’re still primarily useful for. Since C++11, that includes the range-for loop which conceptually works like a function call (see Q&A).

> Sometimes, a reference can also be useful as a local variable, though in modern C++ a pointer or structured binding is usually better (see Q&A).

> That’s it. All other uses of references should be avoided.

https://herbsutter.com/2020/02/23/references-simply/

In particular, the Q & A section:

> For example, if you’re writing a class template, just assume (or document) that it can’t be instantiated with reference types.

Which concludes:

> the dual nature of references is always the problem.

>    If the design embraces the pointer-ness of references (one level of indirection), then one set of use cases works and people with alias-like use cases get surprised.

>    If the design embraces the alias-ness of references (no indirection), then the other set of use cases works and people with pointer-like use cases get surprised.

>    If the design mixes them, then a variety of people get surprised in creative ways.
February 24
On Monday, 24 February 2025 at 00:24:03 UTC, Manu wrote:
> Just yet another in the endless stream of cases why ref should be part of
> the type and not a 'storage class'! Literally everything ref touches gets
> more complex than it should.
> *🎉*

+1000

The consistent, principled way to bring ref into the type system would be to assign all lvalue expressions a type of ref(T), and all rvalue expressions a type of T, with an implicit ref(T) -> T conversion for copyable T.

Unfortunately this is not really feasible for D, since it would be an enormous breaking change. But anyone designing a new language should consider this approach.
February 24
On Monday, 24 February 2025 at 13:50:07 UTC, Paul Backus wrote:
> On Monday, 24 February 2025 at 00:24:03 UTC, Manu wrote:
>> Just yet another in the endless stream of cases why ref should be part of
>> the type and not a 'storage class'! Literally everything ref touches gets
>> more complex than it should.
>> *🎉*
>
> +1000
>
> The consistent, principled way to bring ref into the type system would be to assign all lvalue expressions a type of ref(T), and all rvalue expressions a type of T, with an implicit ref(T) -> T conversion for copyable T.
>
> Unfortunately this is not really feasible for D, since it would be an enormous breaking change. But anyone designing a new language should consider this approach.

not even with the editions coming? . . .
February 24

On Thursday, 20 February 2025 at 23:24:10 UTC, Quirin Schroll wrote:

>

Function templates with auto ref parameters can’t be explicitly instantiated, but must be IFTI’d to infer ref-ness of auto ref parameters.

I thought you could at least do it if I was willing to jump through the hoops to completely specify the function type I was trying to instantiate:

void example(Ts...)(auto ref int arg, auto ref Ts args)
{	import std.stdio;
	writeln(is(typeof(&arg))? "lvalue " : "rvalue ", arg);
}

void main()
{	void function(int) fPtr1 = &example!();
	void function(ref int) fPtr2 = &example!();
	
	fPtr1(10);
	fPtr2(*new int(20));
}

. But the compiler plain out stated

app.d(1): Error: cannot explicitly instantiate template function with `auto ref` parameter

, so this problem is even worse than I thought. I agree some sort of solution is needed, though I don't have a good idea what it should be like.

February 24
On Monday, 24 February 2025 at 16:30:59 UTC, M. M. wrote:
> On Monday, 24 February 2025 at 13:50:07 UTC, Paul Backus wrote:
>> On Monday, 24 February 2025 at 00:24:03 UTC, Manu wrote:
>>> Just yet another in the endless stream of cases why ref should be part of
>>> the type and not a 'storage class'! Literally everything ref touches gets
>>> more complex than it should.
>>> *🎉*
>>
>> +1000
>>
>> The consistent, principled way to bring ref into the type system would be to assign all lvalue expressions a type of ref(T), and all rvalue expressions a type of T, with an implicit ref(T) -> T conversion for copyable T.
>>
>> Unfortunately this is not really feasible for D, since it would be an enormous breaking change. But anyone designing a new language should consider this approach.
>
> not even with the editions coming? . . .

Nope. Even with editions, some changes are simply too disruptive.