CS 70

Templates and Compilation

  • LHS Cow speaking

    Templates are cool, right?

  • Dog speaking

    Yeah!

  • LHS Cow speaking

    They do have a catch, though.

  • Hedgehog speaking

    I knew it was too good to be true.

  • LHS Cow speaking

    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.

What is it about being a function template that would make producing specific machine code particularly challenging?

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

  1. Deduce that (for this call) the type T is string.
  2. Generate C++ code for the specialization printVal<string>.
  3. Compile printVal<string> and main into machine language.
  • Cat speaking

    I see. So a template can only be compiled along with the code that uses it!

  • LHS Cow speaking

    That's it exactly.

  • Cat speaking

    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?

  • LHS Cow speaking

    Yes, separate compilation is really useful! And also we can't do it with templates.

  • Bjarne speaking

    Technically…

  • LHS Cow speaking

    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;
}
  • Duck speaking

    So… the header that declares the template includes the file that defines the template?

  • LHS Cow speaking

    Yup! The definition is included at the bottom! That way the definition comes after the declaration.

  • Duck speaking

    So why do we need them both? Why not just go straight to the definition?

  • LHS Cow speaking

    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 our printVal 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.
  • Horse speaking

    Hay! Doesn't printval-private.hpp need an include guard? I thought headers always needed one of those.

  • LHS Cow speaking

    It's true that printval-private.hpp is a file that gets #included, but it's only ever included in one place—at the end of printval.hpp. It isn't a header that anyone else will ever #include.

  • RHS Cow speaking

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