Thread overview
Template and Type Literals
May 22
monkyyy
Jun 04
monkyyy
May 21

D has function and delegate literals (usually spelled x => … or (x) { … } or function (T x) {…}). You know them, you like them. I don’t have to explain to you how ugly map, filter and other ranges would be to use if you had to define and pass the function/predicate/… by name.

What D lacks is something like that for constructs that aren’t functions, in particular templates. The idea is that sometimes, one wants to pass something to a template that isn’t a value, e.g. a type or a template.

A type literal is something like struct { … }, which you could use as a template argument instead of a named struct. Like with function literals, there are probably some limitations (e.g. function literals can’t be recursive), however, using typeof(this) allows one to refer to the otherwise anonymous struct type, so from the top of my head, I can’t think of one.

More interesting are template literals. There can’t be proper template literals, though, but specialized ones.

What I mean by “proper template literals” is that there can’t be template(T) => because what’s going to be after the => arrow? Templates “return” by eponymous member, but the template has no name.

Example:

import std.meta;

Instantiate!(alias T => const(T), int)[] xs = [ staticMap!(enum T => T.sizeof, byte, short, int, long, struct{ long x, y; }) ];
// const(int)[] xs = [1, 2, 4, 8, 16];

For everything D has an abbreviated template syntax, there should be a template lambda syntax using the same keyword, i.e. struct (T) { … }, class (T) { … }, etc.
For aggregates, parentheses around a single type parameter are obligatory, but for alias and enum, they should be optional.

Passing a type or template literal is equivalent to passing the name of a newly defined aggregate type or template.

I intend to limit those constructs to be template arguments only. If you find use-cases, let me know, but I feel like allowing them anywhere else makes things a lot more complicated.

Grammar:

    TemplateArgument:
        Type
        AssignExpression
        Symbol
+       AggregateLiteral
+       TemplateLiteral
+
+   AggregateLiteral:
+       struct { AggregateBody }
+       union { AggregateBody }
+       class { AggregateBody }
+       interface { AggregateBody }
+
+   TemplateLiteral:
+       alias TemplateParameters => TemplateArgument
+       alias Identifier => TemplateArgument
+       enum TemplateParameters => TemplateArgument
+       enum Identifier => TemplateArgument
+       struct TemplateParameters { AggregateBody }
+       union TemplateParameters { AggregateBody }
+       class TemplateParameters { AggregateBody }
+       interface TemplateParameters { AggregateBody }

Normally, after TemplateParameters, there is an optional Constraint, but that doesn’t really make sense here.

May 22

On Tuesday, 21 May 2024 at 17:38:41 UTC, Quirin Schroll wrote:

>

A type literal is something like struct { … }, which you could use as a template argument instead of a named struct.

Can't we use a type sequence instead? I would prefer built-in sequence syntax e.g. __Seq(i: int, f: float) rather than struct {int i; float f;}, as sequence literals can be used for any symbol sequence, not just types.

If the struct was a large type, it arguably would be better to make it a named type anyway, even if only used once. Then the code reads better.

>

More interesting are template literals. There can’t be proper template literals, though, but specialized ones.

What I mean by “proper template literals” is that there can’t be template(T) => because what’s going to be after the => arrow? Templates “return” by eponymous member, but the template has no name.

Now => means return. We could just use =:

alias(T) = const(T)
enum(T) = T.sizeof

I would require brackets for the parameters, because then the syntax is clearer and easy for the parser.

>

For everything D has an abbreviated template syntax, there should be a template lambda syntax using the same keyword, i.e. struct (T) { … }, class (T) { … }, etc.

I'm a bit skeptical that those would be small enough to be written inline rather than being named.

>

Normally, after TemplateParameters, there is an optional Constraint, but that doesn’t really make sense here.

It might be useful with introspection.

May 22

On Tuesday, 21 May 2024 at 17:38:41 UTC, Quirin Schroll wrote:

>

A type literal is something like struct { … }, which you could use as a template argument instead of a named struct.

when named arguments work with templates, it may just work to use "pack"

template pack(T...){
  alias unpack=T;
}

template usestruct(alias S){
  alias S_=S.unpack;
  ...
}
alias foo=usesstruct!(pack!(i:int,f:float));

idk when thats coming tho

May 30

On Tuesday, 21 May 2024 at 17:38:41 UTC, Quirin Schroll wrote:

>

D has function and delegate literals (usually spelled x => … or (x) { … } or function (T x) {…}). You know them, you like them. I don’t have to explain to you how ugly map, filter and other ranges would be to use if you had to define and pass the function/predicate/… by name.

What D lacks is something like that for constructs that aren’t functions, in particular templates. The idea is that sometimes, one wants to pass something to a template that isn’t a value, e.g. a type or a template.

A type literal is something like struct { … }, which you could use as a template argument instead of a named struct. Like with function literals, there are probably some limitations (e.g. function literals can’t be recursive), however, using typeof(this) allows one to refer to the otherwise anonymous struct type, so from the top of my head, I can’t think of one.

More interesting are template literals. There can’t be proper template literals, though, but specialized ones.

What I mean by “proper template literals” is that there can’t be template(T) => because what’s going to be after the => arrow? Templates “return” by eponymous member, but the template has no name.

Example:

import std.meta;

Instantiate!(alias T => const(T), int)[] xs = [ staticMap!(enum T => T.sizeof, byte, short, int, long, struct{ long x, y; }) ];
// const(int)[] xs = [1, 2, 4, 8, 16];

For everything D has an abbreviated template syntax, there should be a template lambda syntax using the same keyword, i.e. struct (T) { … }, class (T) { … }, etc.
For aggregates, parentheses around a single type parameter are obligatory, but for alias and enum, they should be optional.

Passing a type or template literal is equivalent to passing the name of a newly defined aggregate type or template.

I intend to limit those constructs to be template arguments only. If you find use-cases, let me know, but I feel like allowing them anywhere else makes things a lot more complicated.

Grammar:

    TemplateArgument:
        Type
        AssignExpression
        Symbol
+       AggregateLiteral
+       TemplateLiteral
+
+   AggregateLiteral:
+       struct { AggregateBody }
+       union { AggregateBody }
+       class { AggregateBody }
+       interface { AggregateBody }
+
+   TemplateLiteral:
+       alias TemplateParameters => TemplateArgument
+       alias Identifier => TemplateArgument
+       enum TemplateParameters => TemplateArgument
+       enum Identifier => TemplateArgument
+       struct TemplateParameters { AggregateBody }
+       union TemplateParameters { AggregateBody }
+       class TemplateParameters { AggregateBody }
+       interface TemplateParameters { AggregateBody }

Normally, after TemplateParameters, there is an optional Constraint, but that doesn’t really make sense here.

I've wanted "metafunction lambdas" for a while now, as well as fixing the historical mistake we made with eponymous templates. What mistake, I hear you ask? Well, here's something we fixed wrt C++:

```
struct Foo {
     Foo() { /* ... */ }
    ~Foo() { /* ... */ }
};
```

Java inherited the same mistake, namely that we have to repeat the type's name to declare constructor(s) and the destructor. This makes refactoring harder, which is a Bad Thing(tm). In D we use this and Bob's your proverbial uncle.

Unfortunately, we then went and did something nearly exactly the same with templates that's caused me hours of debugging when I decide to rename the thing and don't immediately catch it because of reflection or __traits(compiles).

Anyway, all to say that I've long wanted to write template lambdas and use this instead of a template's name.

May 31

On Thursday, 30 May 2024 at 18:42:02 UTC, Atila Neves wrote:

>

Java inherited the same mistake, namely that we have to repeat the type's name to declare constructor(s) and the destructor. This makes refactoring harder, which is a Bad Thing(tm). In D we use this and Bob's your proverbial uncle.

Unfortunately, we then went and did something nearly exactly the same with templates that's caused me hours of debugging when I decide to rename the thing and don't immediately catch it because of reflection or __traits(compiles).

Anyway, all to say that I've long wanted to write template lambdas and use this instead of a template's name.

The trouble with using this for eponymous templates is that it can't be used for eponymous template functions, because it would conflict with constructor syntax. Instead I suggested either the template keyword, or the __self identifier:
https://issues.dlang.org/show_bug.cgi?id=7364#c4
https://issues.dlang.org/show_bug.cgi?id=7364#c9

The latter wouldn't need a grammar fix, and could also be useful for lambda recursion.

June 03

On Friday, 31 May 2024 at 15:24:44 UTC, Nick Treleaven wrote:

>

On Thursday, 30 May 2024 at 18:42:02 UTC, Atila Neves wrote:

>

Java inherited the same mistake, namely that we have to repeat the type's name to declare constructor(s) and the destructor. This makes refactoring harder, which is a Bad Thing(tm). In D we use this and Bob's your proverbial uncle.

Unfortunately, we then went and did something nearly exactly the same with templates that's caused me hours of debugging when I decide to rename the thing and don't immediately catch it because of reflection or __traits(compiles).

Anyway, all to say that I've long wanted to write template lambdas and use this instead of a template's name.

The trouble with using this for eponymous templates is that it can't be used for eponymous template functions, because it would conflict with constructor syntax. Instead I suggested either the template keyword, or the __self identifier:
https://issues.dlang.org/show_bug.cgi?id=7364#c4
https://issues.dlang.org/show_bug.cgi?id=7364#c9

The latter wouldn't need a grammar fix, and could also be useful for lambda recursion.

If we had mixin identifiers, i.e. a clause that allowed Identifier to be synthesized from a mixin, it could be as easy as adding a special keyword __TEMPLATE_NAME__ that resolves to a string naming the innermost template’s name. Then,

alias mixin(__TEMPLATE_NAME__) = …;

does the magic. Template name would also be useful in things like static assert error messages.

June 04

On Monday, 3 June 2024 at 11:15:50 UTC, Quirin Schroll wrote:

>

\

alias mixin(__TEMPLATE_NAME__) = …;

what would be the expected output of?

string bar(string s:__TEMPLATE_NAME__)()=>s;
string foo()=>bar;
void main(){
  foo.writeln;
}