
Ask HN: Do C++ templates result in a combinatorial explosion? - Konohamaru
1. I read that C++ templates work by replacing each type variable with a specific type and generating the following function. Depending on how many types are called with each template call, that looks like it could generate a combinatorial explosion. Do they?<p>2. Does the parametric polymorphism of Rust result in the same combinatorial explosion? Does Rust&#x27;s parametric polymorphism just generates ad-hoc polymorphic code?
======
hedora
They can cause a combinatorial explosion, though note that the compiler won’t
instantiate functions that aren’t invoked.

Instantiating the same template the same way in multiple compilation units can
get you another multiplicative factor. I’m not sure if link time optimization
de-duplicates the result or not.

The main practical problem with this (other than big binaries) is that you can
exhaust the instruction cache on your machine, which slows things down
significantly.

I’m not sure how rust code generation works.

Apparently, one of the central design tenants of swift was to keep the
compiled binary small, since it preserves instruction cache. This matters a
lot when you have many different programs running (such as on an iPhone).
Also, instruction cache thrashing is terrible for energy efficiency.

~~~
Konohamaru
C++ templates work more like LISP macros than they do like F#'s parametric
polymorphism. This is partially by necessity: C++ is supposed to be backwards-
compatible with C after all.... You can use templates to do parametric
polymorphism (in fact all of the lambda cube), but they're really a macro
system. Better than LISP's imo.

But do ALL implementations of parametric polymorphism boil down to "erasing
and replacing" with generated code?

~~~
SamReidHughes
What do you mean by erasing and replacing? What generated code do you mean?

For instance, in Haskell you can define a type such as

    
    
        data Tree a = Single a | Bigger (Tree (Node23 a))
        data Node23 a = Node2 a a | Node3 a a a
    

And that will basically look like a linked list Bigger (Bigger (Bigger ...))
until it hits a fixed-depth 2,3-tree (for some depth) whose type might look
something like Node23 (Node23 (Node23 (Node23 Int))).

To actually recurse over the Node23 in code, you need type classes. It works
just fine. The executable has finite size, and the type class instances you'd
use would presumably get constructed on the fly, holding a reference to
smaller types' instances.

I'm not sure if that answers your question.

~~~
Konohamaru
Erasing and replacing = Erasing the parameters and replacing them with ad-hoc
polymorphic functions generated by the compiler. Like what C++ does with
templates.

Basically do all languages with parametric polymorphism do that?

~~~
SamReidHughes
Any language’s implementation can do that in places as an optimization. But
Haskell is one example where it doesn’t generally do that, and the code above
works. I never checked this question out for other languages, but Java,
likewise, only supports generics on reference types and generates one bytecode
per definition, so it should be similar to Haskell in that respect.

