Contents

C++ quick tips: Full template specialisation and one definition rule

Contents

Recently I’ve been doing a lot of template meta-programming in C++. As we all know, when writing a class or a function template, the definition must be in the header file. This is because the compiler needs to see the definition of the template in order to instantiate it. There are some exceptions to this rule. The restriction doesn’t apply if you explicitly instantiate the template in the source file (and of course limit the template’s use to the set of types you’ve instantiated it with). But there’s one more exception, which I think, is not very widely known - full template specialisation.

Tip
Fully specialised class or function templates must be defined in the *.cpp file or defined inline explicitly!

To illustrate the this in practice, I’m gonna start with a simple header file foo.hpp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#ifndef __FOO_HPP__
#define __FOO_HPP__

#include <iostream>

template <typename T>
struct Foo {
    void foo(T t) {
        std::cout << "Foo<T>::foo(" << t << ")" << std::endl;
    }
};

template <>
struct Foo<double> {
    void foo(double t) {
        std::cout << "Foo<double>::foo(" << t << ")" << std::endl;
    }
};

#endif // __FOO_HPP__

What I have is struct Foo template and its full specialisation for double. This can be used in a source file like so (this is my main.cpp):

1
2
3
4
5
6
7
#include <foo.hpp>

int main() {
    Foo<int>{}.foo(123);
    Foo<double>{}.foo(456.0);
    return 0;
}

Now, I’m gonna introduce one more translation unit which uses the template as well. First, the header file wrapper.hpp:

1
2
3
4
5
6
7
8
#ifndef __WRAPPER_HPP__
#define __WRAPPER_HPP__

void useFooInt();

void useFooDouble();

#endif // __WRAPPER_HPP__

and wrapper.cpp file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <foo.hpp>
#include <wrapper.hpp>

void useFooInt() {
    Foo<int>{}.foo(123);
}

void useFooDouble() {
    Foo<double>{}.foo(456.0);
}

I’m gonna use this code in main.cpp so, its final contents will be:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <foo.hpp>
#include <wrapper.hpp>

int main() {
    Foo<int>{}.foo(123);
    Foo<double>{}.foo(456.0);
    useFooInt();
    useFooDouble();
    return 0;
}

I can now, try to build the whole thing:

g++ -I. -c main.cpp
g++ -I. -c wrapper.cpp
g++ main.o wrapper.o -o main

… and no complaints from the compiler. So, what am I talking about?

Tip
Definitions of member functions within the class are implicitly inline.

Because of inline, the one definition rule is not broken. Quoting cppreference:

There may be more than one definition of an inline function or variable(since C++17) in the program as long as each definition appears in a different translation unit and (for non-static inline functions and variables(since C++17)) all definitions are identical…

Let’s make a small change to foo.hpp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
template <typename T>
struct Foo {
    void foo(T t);
};

template <typename T>
void Foo<T>::foo(T t) {
    std::cout << "Foo<T>::foo(" << t << ")" << std::endl;
}

template <>
void Foo<double>::foo(double t) {
    std::cout << "Foo<double>::foo(" << t << ")" << std::endl;
}

Now, I have a fully specialised template which is no longer inline. If I try to build the project again, I get:

1
2
3
4
5
6
g++ -I. -c main.cpp
g++ -I. -c wrapper.cpp
g++ main.o wrapper.o -o main
/usr/bin/ld: wrapper.o: in function `Foo<double>::foo(double)':
wrapper.cpp:(.text+0x0): multiple definition of `Foo<double>::foo(double)'; main.o:main.cpp:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status

The linker is complaining about multiple definitions of Foo<double>::foo(double). This is because the definition of the fully specialised template is not inline neither it is placed in the *.cpp file. One definition rule is violated.

My preference is to move the code to *.cpp file - this will improve build times and might have positive impact on code size as well. Here’s my final version of foo.hpp:

1
2
3
4
5
6
7
8
9
template <typename T>
struct Foo {
    void foo(T t);
};

template <typename T>
void Foo<T>::foo(T t) {
    std::cout << "Foo<T>::foo(" << t << ")" << std::endl;
}

and additionally, I’ve introduced a dedicated implementation file foo.cpp for Foo containing the full template specialisation:

1
2
3
4
5
6
#include <foo.hpp>

template <>
void Foo<double>::foo(double t) {
    std::cout << "Foo<double>::foo(" << t << ")" << std::endl;
}

One last rebuild attempt will produce the desired results:

1
2
3
4
5
6
7
8
9
$ g++ -I. -c main.cpp
$ g++ -I. -c wrapper.cpp
$ g++ -I. -c foo.cpp
$ g++ main.o wrapper.o foo.o -o main
$ ./a.out 
Foo<T>::foo(123)
Foo<double>::foo(456)
Foo<T>::foo(123)
Foo<double>::foo(456)

Conclusion

The decision making, when implementing a template can be summarised with the below flow chart.

/images/template_flowchart.png