Reducing variadic template combinatorics (C++ was onto something)

Dennis dkorpel at gmail.com
Tue Oct 14 12:30:23 UTC 2025


Very interesting thing you're bringing up, I'll share my thoughts.

On Tuesday, 14 October 2025 at 04:30:49 UTC, Steven Schveighoffer 
wrote:
> ```d
> void writelines(T...)(T values)
> {
>     import std.stdio;
>     static foreach(v; values)
>         writeln(v);
> }
> ```
>
> Every combination of every type creates a new template 
> instantiation. We only save on instantiations when 2 calls 
> happen to match all their types.

True, but the instantiations are all very short, so they are 
cheap to process and get inlined in an optimized build. In 
theory, it's not much worse than writing the calls yourself like 
in your second example:

> ```d
> writeln(1); writeln(2); writeln(3); writeln(4);
> ```

Much more problematic is:

```D
void writelines(T...)(T values)
{
     static foreach(v; values)
     {
         // *hundreds of lines of implementation for writeln(v)*
     }
}
```

Because then you duplicate the implementation multiple times, 
even though it's probably 99% the same for `int, uint, 
const(int), immutable(uint)` etc. + every variadic combination of 
those. So it's best practice to reduce the number of possible 
template parameter values as early as possible before getting to 
an implementation. Don't instantiate `impl!T` for every integral 
type when `impl!(T.sizeof)` also works.

Here's a real example of employing this technique: 
https://github.com/dlang/druntime/pull/3852

> How about we try an operator? (...)
> And the usage is as you would expect:
>
> ```d
> coutlines << 1 << 2 << 3 << 4;
> ```

The problem here is that << is meant to do arithmetic, not 
construct a list. In Java you can write `System.out.println("" + 
1 + 2 + 3 + 4);`, which is better, but + is still also used for 
arithmetic so it's still confusing (as seen by the `"" +` 
required to string concatenate rather than add). Now D has its 
own operator that naturally builds a list: ~

However, that often needlessly GC allocates, and it doesn't 
convert other types to strings. A pattern I often write is:

```D
void writeInTag(ref OutBuffer buf, int x)
{
     buf ~= "<";
     buf ~= x;
     buf ~= ">";
}
```

I'd rather write:
```D
buf ~= "<" ~ x ~ ">";
```

That doesn't work because I don't want `string ~ int`, I want it 
to use my `OutBuffer ~ int` implementation, which requires a 
different operator precedence:

```D
(buf ~= "<") ~ x ~ ">";
```

This looks bad, so I've considered swapping out the assignment 
operator for something with lower precedence:
```D
buf ~ "<" ~ x ~ ">";
```

But now we're back in C++ operator overloading abuse territory. 
An unsuspecting programmer would see this as a typo, creating a 
concatenated value and discarding it with no side effect. The 
only unambiguous one-liners I found to work so far are format 
strings or interpolated strings:

```D
buf.format("<%s>", x);
buf ~= i"<$(x)>";
```

> But... I still want to write `writelines(1, 2, 3, 4)`. The 
> ergonomics there are nice! Is there some way we can capture 
> this same reduction in complexity while still keeping the nice 
> syntax?

I'd say: just write a template helper that does nothing but 
forward each argument to another call. If that's somehow 
significantly worse than manually writing 4 function calls, 
investigate why that is and see if that can be fixed.


More information about the Digitalmars-d mailing list