Skip to Content
Course content

221: How Templates Are Instantiated at Compile Time

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've noticed a recurring theme when I mentor developers moving into intermediate C++: they often treat templates as if they are "generic functions" that exist in the compiled binary as a single, flexible piece of logic. You might think that when you write a template, the compiler creates one version of that function that somehow handles any type you pass into it using some kind of internal magic.

Templates are not "Generic Functions"

If you believe the compiler creates one universal function, you're thinking of dynamic dispatch or something closer to how Python or JavaScript handles types. In C++, that is fundamentally not what's happening. A template is not a function; it is a blueprint for a function.

Take a look at this simple example:

template <typename T>
T multiply(T a, T b) {
    return a * b;
}

int main() {
    multiply(5, 10);      // Call A
    multiply(3.14, 2.0);  // Call B
    return 0;
}

If templates were truly generic functions, the compiler would generate one multiply function. But that's impossible because the CPU needs to know exactly which instruction to use: imul for integers or mulsd for doubles. They are completely different operations at the hardware level.

The Compiler as a Code Generator

What actually happens is called instantiation. When the compiler sees multiply(5, 10), it realizes you've asked for a version of multiply that works with int. It then literally generates a brand new function in the background that looks like this:

int multiply_int(int a, int b) {
    return a * b;
}

Then, it sees multiply(3.14, 2.0) and generates another one:

double multiply_double(double a, double b) {
    return a * b;
}

I like to think of the compiler as a very efficient copy-paste machine. It takes your template blueprint, plugs in the type you requested, and writes a concrete function for you. This is why templates are so fast—there is no "looking up" the type at runtime. By the time your program is running, the "template" part is gone. All that remains are standard, concrete functions.

Implicit vs. Explicit Instantiation

In the example above, we used implicit instantiation. The compiler saw the function call and said, "I need an int version of this, let me make one." Most of the time, this is what you want. But sometimes, you want to be the boss.

You can use explicit instantiation to tell the compiler to generate the code for a specific type regardless of whether it's been called yet. It looks like this:

template <typename T>
T multiply(T a, T b) {
    return a * b;
}

// Explicitly tell the compiler to bake this into the binary
template int multiply<int>(int, int);
template float multiply<float>(float, float);

Why bother? In massive projects, this is a trick we use to speed up compile times. By explicitly instantiating a template in one .cpp file and declaring it as extern in others, you prevent the compiler from regenerating the same code in every single translation unit. It's a way of telling the compiler, "Stop guessing and just build these three versions."

The trade-off here is something we call "code bloat." If you instantiate a massive template with 50 different types, your binary size will grow because you now have 50 nearly identical functions sitting in your executable. It's a classic C++ trade-off: we sacrifice a bit of binary space to get maximum execution speed.




📋 Practical Task

Exercise: Forcing Explicit Instantiation for a Physics Utility

You are building a physics engine where a Vector template is used for various precisions (float for GPU calculations, double for high-precision CPU simulations). To ensure that the compiler generates the necessary code for these types without relying on implicit calls in the main loop, you need to implement explicit instantiation.

Requirements:

  • Create a template class Vector that holds a single value of type T.
  • Add a member function T scale(T factor) that returns the value multiplied by the factor.
  • In the global scope (outside of main), explicitly instantiate the Vector class for both float and double.
  • In main, create one instance of Vector<float> and one of Vector<double>, call the scale method on both, and print the results to verify they work.

Goal: Demonstrate that you can move the "generation" of the template code away from the point of usage by using explicit instantiation syntax.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.