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

Steven Schveighoffer schveiguy at gmail.com
Tue Oct 14 14:44:56 UTC 2025


On Tuesday, 14 October 2025 at 12:30:23 UTC, Dennis wrote:
> 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);
>> ```

Yes, but it's not just runtime cost. There's cost to the compile 
time.

What I'm exploring is that we *have* an unrolled loop -- the 
parameter list. If somehow we can harness this into avoiding the 
different processing per call to reach the same conclusion, 
that's what I'm looking at. Whether this results in some kind of 
"best practice" or some special handling the compiler can use to 
save some compile time, I'm not sure. But I feel there is 
definitely some benefit that can be seen here.

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

It's also the same implementation for `int, int, int, int`!

the *hundreds of lines of implementation* is not only repeated 
for different types, it's also repeated for the *same* type with 
different parameter configuration.

In other words, `writelines(1, 2)` and `writelines(1, 2, 3)` 
creates 5 loop bodies, one for each parameter, whereas 
`writelines2(1)(2)` and `writelines2(1)(2)(3)` creates one "body" 
for int, and that's it.

The compiler compiles and processes all variations of function 
calls independently, and sees no benefit from realizing all the 
loop bodies are the same.

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

Nice, yes this is part of it.

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

I did not properly convey that I *don't* want this solution. It's 
just an exploration of what is possible with the current language.

I dislike C++ iostream operators, and prefer the parameter list 
style for writeln. But I had never considered that there is a 
*compile time* benefit to the way C++ is doing it.

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

Right, if nothing else it is a "best practice" on how to avoid 
template bloat. But if there is a way we can convey to the 
compiler this pattern, it would be very nice!

-Steve


More information about the Digitalmars-d mailing list