A quick intro to extern templates

The Problem

One annoying thing with using C++ templates is that they get instantiated multiple times if used with the same template arguments in different compilation units. The compiler happily instantiates the template in every compilation unit, only for the the linker to throw away all but one instances later.

// header.h
#pragma once

template<typename T>  
T fun(T t)  
{
    return t;
}
// file1.cpp
#include "header.h"

void f()  
{
    int i = fun<int>(42);
}
// file2.cpp
#include "header.h"

void f();  
void g()  
{
    auto j = fun<int>(23);
}

int main()  
{
    f();
    g();
}

The above example leads to one instance of int fun<int>(int) in file1.o and another one in file2.o. We can check that with the nm utility:

user@host:~/exampe1$ nm --demangle file1.o
U _GLOBAL_OFFSET_TABLE_
0000000000000000 T f()
0000000000000000 W int fun<int>(int)

user@host:~/example1$ nm --demangle file2.o    
U _GLOBAL_OFFSET_TABLE_
0000000000000018 T main
U f()
0000000000000000 T g()
0000000000000000 W int fun<int>(int)

As you can see, we have two instances of our fun() specialisation for int, both defined as weak symbols, so that the linker can choose one of them and drop the other one later.

The compiler is doing (part of) the work twice only for the linker to throw one instance of fun() away.

While this does not cause a lot of trouble in this toy example, it can produce a significant increase in build times when the template system is heavily used. Let's see what we can do about this:

Extern templates

C++11 introduced the notion of extern templates to address the problem. You can declare a template specialization explicitly to suppress multiple instantiations. Let's change file1.cpp to make use of extern templates.

// file1.cpp
#include "header.h"

extern template int fun(int);

void f()  
{
    int i = fun<int>(42);
}

This tells the compiler, that an instantiation of fun() exists elsewhere which can be used. If we have a look at the symbols in file1.o we can see that int fun(int) is now an undefined symbol (tagged with an U).

user@host:~/exampe2$ nm --demangle file1.o
U _GLOBAL_OFFSET_TABLE_
0000000000000000 T f()
U int fun<int>(int)

The linker can now just pick the instantiation from file2.o. If the linker can not find an instantiation of int fun(int) it will throw an error though.

Wrap up

Extern templates are a neat way to reduce build time and object file sizes in template-heavy projects. If you know in advance that a certain template specialization is instantiated in multiple source files you should use external template declarations.

Andre Haupt

Read more posts by this author.