Traditionally, we put common code in a separate library file, and then write our application code referencing the functions or classes. We satisfy the Compiler by a #include header file with all the function declarations. The Linker then adds the library object file to the final executable.

So far, so good.

A Template is an instruction to the Compiler. The instruction says “when the code invokes a function with this name, using any kind of data type, you shall create an implementation of the function for that Type.”

So the Compiler needs the following:

  • to see all the invocations of the function (so that it knows what Types to create code for)
  • to see the template body (so that it knows what code to generate)

Because this is a feature of the Compiler (and not the Linker) we run into problems
when trying to move those templated functions into a separate library file.

If we follow the traditional model, we put the function declaration into the header file, library.h:

#pragma once 
#include <string>
template <typename T>
void printOut( std::string x, T y);

And we put the body into our library.cpp:

#include <iostream>
#include <string>

template <typename T>
void printOut( std::string x, T y)
{
    std::cout << x << y << '\n';
}

And now in our main.cpp, we include the header and invoke our library function:

#include "library.h"

int main()
{
   int x {7};
   printOut( "x = ", x );
   return 0;
}

This will result in a Linker error:

"undefined reference to 'void printOut<int>(std::string, int)'

That’s right: Even though our main code is invoking the templated function with an int argument, somehow we don’t have an implementation of our function for INT. Why not?

Remember, template expansion is a Compiler feature. And the Compiler didn’t see any invocation of the templated function and probably hasn’t generated any data-type specific implementations for it. But thanks to our included library.h header file, we didn’t get any compiler errors during the compilation of main.cpp.

So, how to solve? Perhaps the most straight-forward way is to provide an invocation for each data type we need, by adding a dummy function to the bottom of library.cpp:

void force_template_generation()
{
    int i   = 6;
    float f = 1.234;
    printOut( "Int", i );
    printOut( "Float", f );
}

Now, when the Compiler processes library.cpp, the object file will include generated implementations for int and float, because the Compiler can see that they are required. The Linker will no longer complain about missing template functions when it pulls together the object code for main.cpp and library.cpp.

References: