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