Jump to page: 1 26  
Page
Thread overview
January 12
Given the interest in CTFE of istrings, I have been thinking about making a general use case out of it instead of one specific to istrings.
---------------------

Sliding Template Arguments

Consider the following template:

```
void pluto(string s)()
{
    pragma(msg, s);
}

void test()
{
    pluto!"hello"();
}
```

This compiles because `s` is a compile time argument, and `pragma(msg, s)`
expects s to be a compile time value.

```
void pluto()(string s)
{
    pragma(msg, s);
}

void test()
{
    pluto("hello");
}
```

This fails to compile because `s` is a runtime argument that is not accessible
at compile time, even if it is inlined. (Inlining doesn't help here because inlining is done after CTFE and semantic analysis.)

Those examples illustrate the difference between a compile time and a runtime argument.

For background, to generate a tuple of elements:

```
alias AliasSeq(T...) = T;
```
and a function argument list that accepts a tuple:
```
void func(Args...)(Args args)
{
}
```
But notice that `args` are runtime arguments. It turns out there is no way
to use tuples to split an argument tuple into compile time and runtime tuples:

```
void pluto(Args...)(Args args)
{
    exec!(args[0])(args[1 .. args.length]);
}
```
This is the problem that DIP1036e ran into. It's clever solution is to have the compiler (because it cannot be done with metaprogramming) take the first argument, and use it as a compile time argument to a templated dummy value. Then, the type of that argument is a template with the value encoded into the type, which can be programmatically extracted and then processed at compile time.

The awkwardness of this is it only happens with istrings, it is not a general purpose capability, plus the insertion of dummy arguments into the argument list just so their types can be extracted.

Hence, the genesis of this proposal which describes a language capability to create a compile time parameter from a tuple of runtime expressions.

For lack of a better term, I'll call it "Sliding Template Arguments".

Consider a template function:

```
void pluto(string s, Args...)(Args args)
{
    pragma(msg, s);
}

void exec()
{
    pluto!"hello"(1,2,3);
}
```
which works now. But this fails to compile:
```
pluto("hello",1,2,3);
```
because there is no argument for `s`.

So, instead of issuing a compilation error, the compiler can "slide" the arguments to the left, so the first argument is moved into the compile time parameter list. Then, the call will compile.

The rule would be something like:

1. the function is a template taking a variadic runtime argument list
2. the compile time parameters are a sequence of N value parameters, followed by
the variadic type parameter.
3. the value parameters do not have default values
4. no compile time arguments are provided in the template invocation
5. the leftmost N runtime arguments are matched against the compile time parameters,
and removed from the runtime argument list
6. if they match, then the template instantiation is rewritten to reflect this
7. compilation then proceeds normally

Template Sliding thus becomes a general use facility. One interesting consequence of this is it opens up a whole new class of functions that can now do CTFE computations on the leftmost arguments.
January 13
On 1/12/24 23:35, Walter Bright wrote:
> Given the interest in CTFE of istrings, I have been thinking about making a general use case out of it instead of one specific to istrings.
> ---------------------
> 
> Sliding Template Arguments
> 
> Consider the following template:
> 
> ```
> void pluto(string s)()
> {
>      pragma(msg, s);
> }
> 
> void test()
> {
>      pluto!"hello"();
> }
> ```
> 
> This compiles because `s` is a compile time argument, and `pragma(msg, s)`
> expects s to be a compile time value.
> 
> ```
> void pluto()(string s)
> {
>      pragma(msg, s);
> }
> 
> void test()
> {
>      pluto("hello");
> }
> ```
> 
> This fails to compile because `s` is a runtime argument that is not accessible
> at compile time, even if it is inlined. (Inlining doesn't help here because inlining is done after CTFE and semantic analysis.)
> 
> Those examples illustrate the difference between a compile time and a runtime argument.
> 
> For background, to generate a tuple of elements:
> 
> ```
> alias AliasSeq(T...) = T;
> ```
> and a function argument list that accepts a tuple:
> ```
> void func(Args...)(Args args)
> {
> }
> ```
> But notice that `args` are runtime arguments. It turns out there is no way
> to use tuples to split an argument tuple into compile time and runtime tuples:
> 
> ```
> void pluto(Args...)(Args args)
> {
>      exec!(args[0])(args[1 .. args.length]);
> }
> ```
> This is the problem that DIP1036e ran into. It's clever solution is to have the compiler (because it cannot be done with metaprogramming) take the first argument, and use it as a compile time argument to a templated dummy value. Then, the type of that argument is a template with the value encoded into the type, which can be programmatically extracted and then processed at compile time.
> 
> The awkwardness of this is it only happens with istrings, it is not a general purpose capability, plus the insertion of dummy arguments into the argument list just so their types can be extracted.
> 
> Hence, the genesis of this proposal which describes a language capability to create a compile time parameter from a tuple of runtime expressions.
> 
> For lack of a better term, I'll call it "Sliding Template Arguments".
> 
> Consider a template function:
> 
> ```
> void pluto(string s, Args...)(Args args)
> {
>      pragma(msg, s);
> }
> 
> void exec()
> {
>      pluto!"hello"(1,2,3);
> }
> ```
> which works now. But this fails to compile:
> ```
> pluto("hello",1,2,3);
> ```
> because there is no argument for `s`.
> 
> So, instead of issuing a compilation error, the compiler can "slide" the arguments to the left, so the first argument is moved into the compile time parameter list. Then, the call will compile.
> 
> The rule would be something like:
> 
> 1. the function is a template taking a variadic runtime argument list
> 2. the compile time parameters are a sequence of N value parameters, followed by
> the variadic type parameter.
> 3. the value parameters do not have default values
> 4. no compile time arguments are provided in the template invocation
> 5. the leftmost N runtime arguments are matched against the compile time parameters,
> and removed from the runtime argument list
> 6. if they match, then the template instantiation is rewritten to reflect this
> 7. compilation then proceeds normally
> 
> Template Sliding thus becomes a general use facility. One interesting consequence of this is it opens up a whole new class of functions that can now do CTFE computations on the leftmost arguments.

Generally I think this is a good idea, but the proposed syntax is a bit too specialized, arbitrarily restricted and the behavior may be unexpected and should be opt-in instead.

Maybe we can do it like this instead:

```d
void pluto(string s, Args...)(enum string x = s, Args args){

}
```

I.e., you can use `enum` in a function argument list and you have to default-initialize them. This means that this parameter always needs to have exactly that value.

Then, the arguments matched to an `enum` parameter are evaluated at compile time and matched against the initializer.
January 13
I started working on an alternative mechanism to templates for 1036e yesterday. Which I alluded to.

Being able to pass a UDA at a call site and be able to get access to it via ``__traits(getAttributes, parameter)`` within the (templated) function.

``i"$ident$(expr)$(ident:format)${1:format}"``

becomes

```d
@IExpression("ident")
ident,
@IExpression("expr")
expr,
@IExpression("ident")
@IFormat("format")
ident,
@IFormat("format")
@IPosition(1)
IPosition.init
```

This would be a general purpose language feature.

```d
string username, password;

getopt(
    @description("My program")
    @description("Second line")
    commandsInfo,
    @description("My programs help info")
    @flag("help") @flag("h") helpInfo,
    @description("The username to connect with")
    @flag("username") @flag("u") username,
    @description("The password to connect with")
    @flag("password") @flag("p") password
);
```

I have already mocked up the getopt, the only additional template usage is for formattedRead. This is a general purpose feature, that string interpolation could tie into.

https://gist.github.com/rikkimax/812de12e600a070fe267f3bdc1bb3928
January 13

On Friday, 12 January 2024 at 22:35:54 UTC, Walter Bright wrote:

>

Given the interest in CTFE of istrings, I have been thinking about making a general use case out of it instead of one specific to istrings.


Sliding Template Arguments

This is a workable solution. But still not with format strings (which are specific to writef).

If I were to use this to implement interpolation tuples, I'd prefer the first parameter to be of type struct Interpolation { immutable string[] parts; }, and therefore the compiler would do:

void foo(Interpolation interp, Args...)(Args args) {...}

void main()
{
    string name = "Steve";
    int age = 42;
    foo(i"Hello, $name, I see you are $age years old.");
    // equivalent to:
    foo!(Interpolation(["Hello, ", "name", ", I see you are ", "age", " years old."]))(name, age);
}

There is still value, I think, in passing the parameters in the order they appear. And this doesn't allow for e.g. functions that take multiple istrings. But maybe that's fine?

Another interesting development from this, is that you can take the string literal data at runtime as well (when you prefer, or are ok with, runtime processing of the string literal data):

void writeln(Args...)(Interpolation interp, Args args)
{
    assert(interp.parts.length == args.length * 2 + 1);
    write(interp.parts[0]); // always a leading string;
    static foreach(i; 0 .. args.length)
        write(args[i], interp.parts[(i+1)*2]);
    writeln();
}

I don't view this as simpler than DIP1036e or DIP1027 -- a simple transformation is a simple transformation. Certainly a hybrid DIP1027 with a format string passed at compile time is still not viable, due to reasons already stated.

But it will be slightly less bloat. And it has benefits elsewhere, as you say.

If this mechanism is what gets it over the finish line, I can compromise with this.

Is this something that can be implemented and played with?

This does seem like it has the potential to break code:

void foo(int x, Args...)(Args args) {

}
void foo(Args...)(Args args) {
}

foo(1, 2, 3); // which is called? Today, the second is

So I would heed Timon's advice, maybe we do need an explicit opt-in.

-Steve

January 13

On Saturday, 13 January 2024 at 00:15:56 UTC, Steven Schveighoffer wrote:

>

This does seem like it has the potential to break code:

void foo(int x, Args...)(Args args) {

}
void foo(Args...)(Args args) {
}

foo(1, 2, 3); // which is called? Today, the second is

A more realistic example:

writefln("blah %d", 1)

Since writefln (and format) have string template format parameter versions.

Even with an opt-in, there will still be a decision to make (or throw an ambiguity error) for matching overloads.

-Steve

January 13

On Friday, 12 January 2024 at 23:04:58 UTC, Timon Gehr wrote:

>

I.e., you can use enum in a function argument list and you have to default-initialize them. This means that this parameter always needs to have exactly that value.

Then, the arguments matched to an enum parameter are evaluated at compile time and matched against the initializer.

Looks promising, but I have a question. Will this work?

void pluto(){ }

void pluto(string s, Args...)(enum string x = s, Args args){
    pragma(msg, s);
    pluto(args); // <-
}

pluto("abc", "def", "ghi"); // pluto!("abc", string, string)

I’m afraid it can’t. When we call pluto(args), args are not compile-time-known anymore. How can you overcome this?


I still think that Walter’s idea of relaxing requirements on alias parameters is the simplest yet general enough and is not limited to interpolations.

struct Interpolation {
    immutable(string)[ ] parts;
}

void fooImpl(Interpolation interp, Args...)(Args args) { ... }

template foo(Interpolation interp, args...) {
    alias foo = fooImpl!interp(args);
}

void main() {
    string name = "Steve";
    int fourty = 40;
    foo!(i"Hello, $name, I see you are $(fourty + 2) years old.");
    // Desugars to:
    foo!(Interpolation(["Hello, ", "name", ", I see you are ", "fourty + 2", " years old."]), name, fourty + 2);
    // `foo!(...)` expands to:
    fooImpl!(Interpolation(["Hello, ", "name", ", I see you are ", "fourty + 2", " years old."]))(name, fourty + 2);
}

This almost works now, except 1) you cannot pass fourty + 2 to a template parameter, 2) you cannot do alias foo = fooImpl!interp(args).

To say it another way, it should be allowed to instantiate a template with something unmangleable, but in that case the template must not declare anything that requires mangling (functions, variables, structs, unions, classes; uncertain about enums).

This approach has an advantage over several other proposals: if something is being accessed at compile time, you must indicate it clearly by putting an exclamation mark at the call site.

January 13

On Friday, 12 January 2024 at 22:35:54 UTC, Walter Bright wrote:

>

Sliding Template Arguments

Consider the following template:

void pluto(string s)()
{
    pragma(msg, s);
}

void pluto(Args...)(Args args)
{
    exec!(args[0])(args[1 .. args.length]);
}

Like this, I think as long as the left side is known at compile time, we can directly add this feature!

void pluto(string s, Args...)(string x = s, Args args){

}

I think if there is an overloaded version, then choose the overloaded version. If not, you can simply slide it because there are usually no side effects, but there should be a method to determine whether the variable is known or unknown at compile time.

I found out earlier that we can create an attribute dictionary. Functions/variables/etc... can all have an attribute dictionary.

Here, if 'x' has an attribute dictionary, it can extract information that if 'x' is a compile time variable.

So, you can use this information for compile time metaprogramming!

Moreover, try to deduct the function's attribute dictionary as much as possible. This way, there is no need for each function's endless attribute soup.

January 12
On 1/12/2024 4:15 PM, Steven Schveighoffer wrote:
> I don't view this as simpler than DIP1036e or DIP1027 -- a simple transformation is a simple transformation.

Adding extra hidden templates isn't that simple. If a user is not using a canned version, he'd have to be pretty familiar with D to be able to write his own handler. 1027 is simpler in that if the generated tuple is examined, it matches just what one would have written using a format. Nothing much to learn there.


> Certainly a hybrid DIP1027 with a format string passed at compile time is still not viable, due to reasons already stated.

The other reasons:

1. preventing calls to functions passing an ordinary string as opposed to an istring tuple
2. preventing nested istrings

have already been addressed. The compile time thing was the only one left.

> This does seem like it has the potential to break code:

The shifted one would be a more costly match, and so the legacy others would be favored first.
January 12
On 1/12/2024 4:39 PM, Steven Schveighoffer wrote:
> ```d
> writefln("blah %d", 1)
> ```
> 
> Since `writefln` (and `format`) have string template format parameter versions.
> 
> Even with an opt-in, there will still be a decision to make (or throw an ambiguity error) for matching overloads.

That's why the template shifting one would be a lower priority match.

January 13

On Saturday, 13 January 2024 at 02:14:55 UTC, zjh wrote:
`attribute

>

dictionary. Functions/variables/etc...can all have anattribute dictionary`.

For things like named parameters, as long as there is an attribute dictionary, it is very simple to implement named parameters because we can give each parameter a position. With named parameters, the value can be directly set through the mapping of name and position.

« First   ‹ Prev
1 2 3 4 5 6