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

Steven Schveighoffer schveiguy at gmail.com
Tue Oct 14 04:30:49 UTC 2025


In a conversation on a pull request for one of my libraries, I 
came across an interesting revelation. Variadic template 
functions waste resources for the most part, because much of the 
time, you don't care about the *relationship* between the 
parameters. In many such functions, you just process them in a 
loop.

Let's start with the traditional variadic D pattern. I'll write a 
function which writes each individual parameter on its own line:

```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.

But this function that is instantiated is all just calls to 
`writeln`! It's not an interesting function, nor is it really 
worthy of applying a combinatoric solution. We aren't getting any 
special optimization by having the entire list in view at once.

Let's take an example call:

```d
writelines(1, 2, 3, 4);
```

Note how we had to *type out* each parameter individually, in the 
same order they would be processed in the loop. Well, we can 
write this ourselves!

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

This accomplishes the same thing, but instead of a template 
instantiation per group of writes, we only get one instantiation 
of `writeln` for integers. We effectively have removed the 
combinatorics.

Now, this is quite the ugly solution! We have to repeat the call 
for each one.

But notice how we have written the same exact list! But instead 
of `", "`, our separator is `"); writeln("`.

What if we could reduce that separator? Maybe we can use an 
`opCall`?

```d
struct WriteLines
{
     ref opCall(T)(T val) {
         import std.stdio;
         writeln(val);
         return this;
     }
}

enum writelines2 = WriteLines.init;
```

Now, how does this look?


```d
writelines2(1)(2)(3)(4);
```

Our separator has changed into `")("`. A little nicer, but still 
looks weird.

But more importantly, we have *one* instantiation of the `opCall` 
for *all integers*. I can write any number of integers, or any 
combination of integers and strings, or anything else, and we 
only get one instantiation per type. The combinatorics are gone, 
and yet I'm *mostly* writing the same thing.

How about we try an operator? Wait, isn't there another language 
that does this?

```d
struct WriteLinesCpp
{
     ref opBinary(string s: "<<", T)(T val) {
         import std.stdio;
         writeln(val);
         return this;
     }
}
enum coutlines = WriteLinesCpp.init;
```

And the usage is as you would expect:

```d
coutlines << 1 << 2 << 3 << 4;
```

Again, the benefit here is less combinatorics -- one 
instantiation per type -- and less junk functions which are 
unrolling the unrolled loops that we typed in the first place.

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'll leave it up to the experts here to think about. I can 
probably think of ways, but I'm sure they would not stand up to 
scrutiny.

-Steve


More information about the Digitalmars-d mailing list