Templates and Compilation
Templates are cool, right?
Yeah!
They do have a catch, though.
I knew it was too good to be true.
It's not too bad, but compilation gets a little funky with templates.
Compiling a Template?
Imagine that you are the compiler. Someone comes along and hands you a file containing the code
template <typename T>
void printVal(T x) {
cout << x << endl;
}
It's your job to convert this code into machine language and create an object file. Remember, no one has used printVal
at all yet; you only have the template definition.
This code can't be compiled into machine language.
We can't convert this code to machine language on its own because we don't know the type T
. We need to know that type in order to know what low-level instructions to perform in place of our high-level operations! In this case, the streaming operator would do very different things to a double
, an int
, or a string
!
Arguably, a template is not code. It is a recipe for code. It's like fill-in-the-blanks code.
So it doesn't make sense to compile a template on its own.
Compiling a Template Specialization
What can be compiled is a specialization of a template where the template parameters are filled in. For example, say we have this code in a file:
template <typename T>
void printVal(const T& x) {
cout << x << endl;
}
int main() {
printVal("Moo");
}
When the compiler reaches the main
function, it can
- Deduce that (for this call) the type
T
isstring
. - Generate C++ code for the specialization
printVal<string>
. - Compile
printVal<string>
andmain
into machine language.
I see. So a template can only be compiled along with the code that uses it!
That's it exactly.
But before we made a big deal about compiling code separately from the code that uses it, right? Isn't that the whole reason for object files and whatnot?
Yes, separate compilation is really useful! And also we can't do it with templates.
Technically…
Okay, sometimes you can, but it's tricky and outside the scope of CS 70.
Template File Organization
The main lesson from above is that templates must be compiled along with the code that use them.
That pretty much rules out separate compilation. We can't put a template in a source file and compile it on its own.
As a result, we are going to have to #include
the function template definition (not just the declaration) in any file that uses the template.
There are many possible ways to organize our files, but here is the file organization idiom that we will use in CS 70:
main.cpp
— a source file that uses the template
#include "printval.hpp"
int main() {
printVal(42);
printVal("Moo");
}
printval.hpp
— a header file that declares the template
#ifndef PRINTVAL_HPP_INCLUDED
#define PRINTVAL_HPP_INCLUDED
template <typename T>
void printVal(const T& x);
#include "printval-private.hpp"
#endif
Notice that just before the #endif
, we have a have a #include
line that reads in the implementation of the printVal
function.
printval-private.hpp
— a file that defines the template
#include <iostream>
template <typename T>
void printVal(const T& x)
{
std::cout << x << std::endl;
}
So… the header that declares the template includes the file that defines the template?
Yup! The definition is included at the bottom! That way the definition comes after the declaration.
So why do we need them both? Why not just go straight to the definition?
Technically, we could put all the code in one file rather than across two files, but here's why we don't…
It still makes sense to have one file specify the interface (our function declarations) and another specify the implementation (our function definitions).
- In this case,
printval.hpp
describes the interface without all the implementation details—it's what users of ourprintVal
function should refer to when using the API. - The precise details of how our
printVal
function works aren't something that people need to look at, so they're in their own file. - If we had multiple function templates that called each other, we would need everything to be declared before it is called.
- We will very shortly talk about class templates. The class-template definition definitely needs to come before the definitions of the member-function templates.
Hay! Doesn't
printval-private.hpp
need an include guard? I thought headers always needed one of those.It's true that
printval-private.hpp
is a file that gets#include
d, but it's only ever included in one place—at the end ofprintval.hpp
. It isn't a header that anyone else will ever#include
.And when it's included by
printval.hpp
, it's already inside that header's include guard.
If you really want an include guard of some kind for your private–template-implementation header file, it might be better to make it look like
// Detect users erroneously including this file
#ifndef PRINTVAL_HPP_INCLUDED
#error You need to include printval.hpp, not this file.
#endif
#include <iostream>
template <typename T>
void printVal(const T& x)
{
std::cout << x << std::endl;
}
(When logged in, completion status appears here.)