Why am I talking about this?

Something I've begun making judicious use of at work in our codebase are strong / new types. Strong types are extremely thin wrappers around other types, usually language provided primitive ones, that help to ensure you don't accidentally use a type or value in a place that you shouldn't. I'll be writing about them in more detail soon, but in my opinion the best way to implement them is through the use of a templates.

Why is compiling templates slow?

Templates are instantiated in every translation unit (cpp file) that uses them unless you explicitly tell the compiler not to. This leads to the compiler performing the same work over and over many times if you use the same type in a template repeatedly. The linker then has to clean up the compiler's mess and throw out nearly all of that work, otherwise the one definition rule (ODR) would be violated.

What can we do about it?

Assuming that templates are necessary in the situation you're using them (in my case I believe they are), there's some easy things we can do for huge reductions in compilation time.

If you know what types you are going to be using in your templates the majority of the time, declare them as extern in the header that declares them, and implement them once in a single translation unit. Let's use a Pixel3 strong type as an example.

// In file pixel3.hpp

template<typename T>
class Pixel3 {
  public:
    T b;
    T g;
    T r;
};

When working with pixel data, there's a finite number of types we can expect T to be, mainly primitive integral and floating point types. The code I work with tends to be entirely 8 bit integers, 16 bit integers, and the the two floating point types. Instead of making the compiler repeat work, let's make its life a little easier:

// at the bottom of file pixel3.hpp, after the Pixel3 class implementation

extern template class Pixel3<uint8_t>;
extern template class Pixel3<uint16_t>;
extern template class Pixel3<float>;
extern template class Pixel3<double>;

using Pixel3b = Pixel3<uint8_t>;
using Pixel3us = Pixel3<uint16_t>;
using Pixel3f = Pixel3<float>;
using Pixel3d = Pixel3<double>;

extern template tells the compiler that it should not instantiate a Pixel3 template in a translation unit that includes pixel3.hpp if it's one of the four aforementioned types, and instead trust that it will find those definitions during the linking process. I also define some aliases to save on typing in the future. Then in one single file we can tell the compiler to generate the definitions:

// in file pixel3.cpp
// don't forget to link against this file!

template class Pixel3<uint8_t>;
template class Pixel3<uint16_t>;
template class Pixel3<float>;
template class Pixel3<double>;

And that's it! The compiler now only performs the work of generating our four Pixel3 types once. This would also be a good place to throw a few static_asserts if your strong types have specific size and / or alignment requirements.