Skip to main content

Raymii.org Logo (IEC resistor symbol) logo

Quis custodiet ipsos custodes?
Home | About | All pages | RSS Feed | Gopher

C++ template definitions in a .cpp file (instead of a header file)

Published: 22-06-2019 | Author: Remy van Elst | Text only version of this article


Table of Contents


In this snippet I'll show you how to place your C++ template definitions in a seperate .cpp file. I'd recommend you to just put template definitions in your header file, or a .hpp file, but if you really want to there is a trick to get them in a seperate .cpp file. The trick is to explicitly instanciate every template you're going to use at the end of the .cpp file. With many different templates and types this becomes cumbersome, but for certain usecases it could be useful.

If you like this article, consider sponsoring me by trying out a Digital Ocean VPS. With this link you'll get $100 credit for 60 days). (referral link)

Template definitions

Small recap on templates. A template is not an actual class or function, but a "pattern" that the compiler uses to generate a family of classes or functions.

In order for the compiler to generate the code, it must see both the template definition (not just declaration) and the specific types/whatever used to "fill in" the template. For example, if you're trying to use a Foo<int>, the compiler must see both the Foo template and the fact that you're trying to make a specific Foo<int>. See here for more explanation.

Placing templates in your .h files might result in cluttered header files, it also could increase code bloat and the compiled binary size. (That however does depend on how smart your compiler is). For the cluttering people often resort to .hpp files. Which brings its own set of problems, for example with your build system if you're doing something special.

The trick I found here is that you can place your template definitions in a seperate .cpp file and explicitly instanciate every form of that template that is going to be used in that .cpp file.

If you don't instanciate all forms in your .cpp file you will get undefined reference errors, I'll show you an example later on.

The linker however does spew out the specific form so you can copy/paste it quickly.

Example code

I've written a sample program with a class with one template function, one other class and the main.cpp file. This is the directory layout, you can ignore the CMake files:

$ tree -L 1   
.
|-- CMakeLists.txt
|-- TestClass1.cpp
|-- TestClass1.h
|-- TestClass2.cpp
|-- TestClass2.h
|-- cmake-build-debug
`-- main.cpp

1 directory, 6 files

TestClass1.h

This file contains the class with one template function. It does not contain the template defintion, only the declaration. Normally you would define the entire template here but that's the part we don't want to in this example.

#ifndef TESTCLASS1_H
#define TESTCLASS1_H

#include <iostream>

class TestClass
        {
private:
    bool m_bool1 { false };

public:
    TestClass(bool bool1) : m_bool1(bool1) {}
    // just the template declaration
    template <typename T1, typename T2>
    void templateFunction(T1 var1, T2 var2);

};

#endif //TESTCLASS1_H

TestClass1.cpp

This is where the template is defined, and at the bottom, instanciated explicitly for the types we're going to use in the code.

#include <iostream>
#include "TestClass1.h"
//actual template definiton
template <typename T1, typename T2>
void TestClass::templateFunction (T1 var1, T2 var2) {
    std::cout << "var1: " << var1 << ", ";
    std::cout << "var2: " << var2 << ", ";
    std::cout << "m_bool1: " << m_bool1 << "\n";
}

// Here is the explicit instanciation
template void TestClass::templateFunction<int, int>(int, int);
template void TestClass::templateFunction<char const*, char const*>(char const*, char const*);

TestClass2.h

This is just another class where the template is used, as an example.

#ifndef TESTCLASS2_H
#define TESTCLASS2_H
#include "TestClass1.h"

class TestClass2 {
private:
    bool m_abc1 {false};

public:
    void printTest();
};

#endif //TESTCLASS2_H

TestClass2.cpp

Here is the definition of the above function, where the other template is called with a const char *.

#include "TestClass2.h"

void TestClass2::printTest () {
    TestClass example(false);
    example.templateFunction ("abc", "def");
};

main.cpp

It all comes together in the main.cpp file, one of both classes. I've used two different methods of calling the class templated function, either explicitly telling which types were using or just letting the compiler figure it out.

#include <iostream>
#include "TestClass1.h"
#include "TestClass2.h"

int main () {
    TestClass example1(true);
    example1.templateFunction<int, int> (1, 2);
    example1.templateFunction (3, 4);

    TestClass2 lala = TestClass2();
    lala.printTest ();
    return 0;
}

Example output:

var1: 1, var2: 2, m_bool1: 1
var1: 3, var2: 4, m_bool1: 1
var1: abc, var2: def, m_bool1: 0

Error, undefined reference

The warning when you forget to instanciate a template, or in this example, uncommented one:

//template void TestClass::templateFunction<int, int>(int, int);
template void TestClass::templateFunction<char const*, char const*>(char const*, char const*);

Output:

[100%] Linking CXX executable example
CMakeFiles/folder.dir/main.cpp.o: In function `main':
folder/main.cpp:7: undefined reference to `void TestClass::templateFunction<int, int>(int, int)'
folder/main.cpp:8: undefined reference to `void TestClass::templateFunction<int, int>(int, int)'
collect2: error: ld returned 1 exit status

If you would use the template with two doubles you would have to add this at the end of the file TestClass1.cpp:

template void TestClass::templateFunction<double, double>(double, double);

In the header file

If the template function for TestClass1 was in the header file, it would look like this:

TestClass1.h:

#ifndef TESTCLASS1_H
#define TESTCLASS1_H

#include <iostream>

class TestClass {
private:
    bool m_bool1 { false };

public:
    TestClass(bool bool1) : m_bool1(bool1) {}
    // template declaration and definiton
    template <typename T1, typename T2>
    void templateFunction (T1 var1, T2 var2) {
        std::cout << "var1: " << var1 << ", ";
        std::cout << "var2: " << var2 << ", ";
        std::cout << "m_bool1: " << m_bool1 << "\n";
    }
};

#endif //TESTCLASS1_H

You would not need the TestClass1.cpp file.

Tags: c++ , cpp , development , hpp , linux , snippets , software , templates