Last weekend, San Diego C++ Meetup hosted Andreas Fertig. Andreas presented his excellent talk on “C++ Templates”.
During his talk, one of the attendees asked a question that many, if not all, C++ programmers had asked at least once in their early life as C++ programmers. In this post, I hope it would provide answers to anyone that bumped into this issue before, and for those that haven’t yet 🙂
And the questions goes something like this.
While compiling and linking my C++ application, which happens to use templates, I’m getting a linker error.
To demonstrate this issue here is a simple example:
hpp file (include guard omitted):
1 2 3 4 5 6 7 8 |
namespace kobi { template <typename T> class MyTemplate final { T x_ {}; public: T getVal() const; }; } // namespace kobi |
Plus, here is a cpp file, that is added to compile line (e.g. via CMakeLists.txt or any other means)
1 2 3 4 5 6 |
#include "MyTemplate.hpp" namespace kobi { template <typename T> T MyTemplate<T>::getVal() const { return x_; } } // namespace kobi |
And eventually main():
1 2 3 4 5 6 7 |
#include <iostream> #include "MyTemplate.hpp" int main() { kobi::MyTemplate<int> mt; std::clog << mt.getVal() << '\n'; return 0; } |
Just to discover the following linking error:
1 2 |
/usr/bin/ld: CMakeFiles/templates_and_linking.dir/main.cpp.o: in function `main': /home/kobi/develop/templates-and-linking/main.cpp:7: undefined reference to `kobi::MyTemplate<int>::getVal() const' |
As mentioned above, I think all (most?) of us had this at least one time in our C++ programming career.
So what makes C++ Templates so special and makes it impossible for us to place a Template class definition in cpp file, compile it and try to call this function?
Before we answer this question, let’s take a step back and understand the compile/linker stages.
To put it simply, there are few steps to compile your C++ code. There’s a preprocessor step, C++ files to object files compilation step and finally, the linker will create a binary – executable or shared library. Creating a static library is just an archive of all of those object files (compilation units) and no linking step is involved in this case – so let’s ignore it for now (linking happens when exe/so is linked with your static lib).
Here is an example for a simple scenario, using non-Templated code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// some header file namespace kobi { class MyNonTemplate { unsigned int x_{}; public: unsigned int getVal() const; }; } // namespace kobi // some cpp file #include "MyNonTemplate.hpp" namespace kobi { unsigned int MyNonTemplate::getVal() const { return x_; } } // namespace kobi |
And your main() would be something like:
1 2 3 4 5 6 7 8 9 |
#include <iostream> #include "MyNonTemplate.hpp" int main() { kobi::MyNonTemplate nt; std::clog << nt.getVal() << '\n'; return 0; } |
And compiling the above would pass without issue. This is really a simple, vanilla example. Let’s inspect what we have on the file-system:
1 2 3 4 5 6 7 8 9 10 11 |
➜ templates_and_linking.dir objdump -Ct MyNonTemplate.cpp.o MyNonTemplate.cpp.o: file format elf64-x86-64 SYMBOL TABLE: 0000000000000000 l df *ABS* 0000000000000000 MyNonTemplate.cpp 0000000000000000 l d .text 0000000000000000 .text 0000000000000000 l d .data 0000000000000000 .data 0000000000000000 l d .bss 0000000000000000 .bss ... 0000000000000000 g F .text 0000000000000014 kobi::MyNonTemplate::getVal() const |
Note the getVal() that is part of the .text section. We have full definition in our compiled object file.
When main() is linked with this object file, the linker is capable of finding this symbol and all is good. Here is how main() object file looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
➜ templates_and_linking.dir objdump -Ct main.cpp.o main.cpp.o: file format elf64-x86-64 SYMBOL TABLE: 0000000000000000 l df *ABS* 0000000000000000 main.cpp 0000000000000000 l d .text 0000000000000000 .text 0000000000000000 l d .data 0000000000000000 .data 0000000000000000 l d .bss 0000000000000000 .bss 0000000000000000 l O .bss 0000000000000001 std::__ioinit 0000000000000064 l F .text 000000000000004d __static_initialization_and_destruction_0(int, int) 00000000000000b1 l F .text 0000000000000019 _GLOBAL__sub_I_main ... 0000000000000000 g F .text 0000000000000064 main 0000000000000000 *UND* 0000000000000000 _GLOBAL_OFFSET_TABLE_ 0000000000000000 *UND* 0000000000000000 kobi::MyNonTemplate::getVal() const 0000000000000000 *UND* 0000000000000000 std::clog ... |
Note the *UND* kobi::MyNonTemplate::getVal() const – fear not! When creating the entire executable the Linker will find the definition and when you inspect the actual binary:
1 2 3 4 5 6 7 8 9 10 11 12 |
➜ cmake-build-debug objdump -Ct ./templates_and_linking ./templates_and_linking: file format elf64-x86-64 SYMBOL TABLE: 0000000000000318 l d .interp 0000000000000000 .interp ... 0000000000000000 F *UND* 0000000000000000 std::ostream::operator<<(unsigned int)@@GLIBCXX_3.4 0000000000004000 g .data 0000000000000000 __data_start 0000000000004158 g .bss 0000000000000000 _end 0000000000001294 g F .text 0000000000000014 kobi::MyNonTemplate::getVal() const ... |
You see? all good. We have kobi::MyNonTemplate::getVal() const in the .text section and both compilation and linking stages completed w/o errors.
Now, let’s inspect what happened with the Templated one.
Here is the compiled .cpp file that contained that template member function definition:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
➜ templates_and_linking.dir objdump -Ct ./MyTemplate.cpp.o ./MyTemplate.cpp.o: file format elf64-x86-64 SYMBOL TABLE: 0000000000000000 l df *ABS* 0000000000000000 MyTemplate.cpp 0000000000000000 l d .text 0000000000000000 .text 0000000000000000 l d .data 0000000000000000 .data 0000000000000000 l d .bss 0000000000000000 .bss 0000000000000000 l d .debug_line 0000000000000000 .debug_line 0000000000000000 l d .debug_str 0000000000000000 .debug_str 0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack 0000000000000000 l d .note.gnu.property 0000000000000000 .note.gnu.property 0000000000000000 l d .comment 0000000000000000 .comment |
Unlike the non-templated one, there is nothing interesting here!
getVal() does not exist here in any shape or form. So what happened?
Well, to put it simple, Template, and in this case class Template, is just a vehicle, a special syntax in the language for creating concrete types (or functions), concrete definitions. But for this to happen, one would need to:
- Ask the compiler to create, instantiate the class type. (same idea for functions btw).
- The compiler must have full visibility to the definition at the time of the instantiation. (unless extern template is used – this part is not covered in this article).
When we compiled the class Template in its own “compilation unit” and tried to instantiate this class type (+ calling a member function) inside main(), the compiler (at the point of instantiation) only had the declaration of the member function since we #include <> the header file. The compiler did not see the complete definition. It’s like there is no such definition!
Wait you say – but I have the cpp compiled. True, but remember, there is no spoon! There is no member getVal() function definition compiled into this translation unit!
It’s just bunch of code that no one instantiate when this specific “template” cpp was compiled!
OK. so what can we do? Few options that I will highlight here.
Define the body of the function in the header itself
There are couple ways you can define the function in the class Template header.
We want to do this so when another cpp includes this header, the point of instantiation has full visibility to the definition.
1 2 3 4 5 6 7 8 |
namespace kobi { template <typename T> class MyTemplate final { T x_ {}; public: T getVal() const { return x_; } }; } // namespace kobi |
Or, you can leave the class with just function declarations, and have the definition somewhere outside of the class template:
1 2 3 4 5 6 7 8 9 10 11 12 |
namespace kobi { template <typename T> class MyTemplate final { T x_ {}; public: T getVal() const; }; template <typename T> T MyTemplate<T>::getVal() const { return x_; } } // namespace kobi |
If you check your compiled object with objdump , you’ll see that getVal() is now showing in your .text section.
Oh yes – using the above method, there is no .cpp anymore. You don’t need it and of course, you don’t need to compile it as a standalone .cpp file.
#include your cpp file in your header
Usually when I introduce this method to a junior C++ programmers, it really confuses them. How can you #include<> a .cpp file in your header. Well, I usually explain them that technically, I can #include<> (almost?) anything, and include it anywhere inside headers of C++ files. Not that every such #include<> would make sense, but yes, I can do it. Sue me!
What does it mean to have #include<> of one file within another file. It means the preprocessor will take the file mentioned in the preprocessor directive as #include<> and will just embed it in the overall buffer that will be fed later into the next compilation step. Don’t believe me? No problem. Run your compile line with -E (of course, no need to use -o option and similar) and you’ll become a true believer 🙂 You’ll see the output buffer of the preprocessor and if you have #include<> directives (and we assume that the preprocessor can find those files), you’ll see the content of these files in the output buffer.
So let’s put this knowledge into practice:
1 2 3 4 5 6 7 8 9 10 11 |
namespace kobi { template <typename T> class MyTemplate final { T x_ {}; public: T getVal() const; }; } // namespace kobi #include "MyTemplate.cpp" |
And here is your cpp, it would not have a #include<> of the header:
1 2 3 4 |
namespace kobi { template <typename T> T MyTemplate<T>::getVal() const { return x_; } } // namespace kobi |
Again, if you check with objdump, you’ll notice getVal() is in the .text section.
But what if I really want to _compile_ my template cpp file?
I said above that it does not really make sense to compile a .cpp Template file. Well, I lied a bit. This is how you explain things in C++, you lie a bit and then, towards the end, you have a twist in the plot.
In some use cases, you might have the following requirements when working with external entities:
- You cannot release the definitions of your class (of function) Template. (IP reasons or you just don’t want others to see how bad is your code 😉
- You are OK to allow instantiation of only specific types.
In both cases, the mechanism is as described below:
- You write your class definition with only the function member declaration in the header.
- You write your class function member definition in a separate .cpp. This .cpp will be compiled.
- In the above mentioned .cpp, you instantiate this class Template with specific types. The compiler will generate full class type definition at the point of this explicit instantiation. (btw, this is applicable to non-member functions as well).
Example of such .cpp (#include<> of the header omitted):
1 2 3 4 5 6 7 |
namespace kobi { template <typename T> T MyTemplate<T>::getVal() const { return x_; } } // namespace kobi template class kobi::MyTemplate<int>; template class kobi::MyTemplate<float>; |
I have explicit instantiation of this class type for int and float.
and example of usage:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <iostream> #include "MyTemplate.hpp" int main() { kobi::MyTemplate<int> mt; std::clog << mt.getVal() << '\n'; kobi::MyTemplate<float> mtf; std::clog << mtf.getVal() << '\n'; kobi::MyTemplate<double> mtd; // std::clog << mtd.getVal() << '\n'; // linker error, would not work return 0; } |
Note that we cannot instantiate and use getVal() of double since we are back to square one with this type. There is no definition available for double!
Note that since the constructor is implicitly generated (and hence available to the compiler and to the linker) , instance of kobi::MyTemplate<double> would compile and link without issue. I just cannot call functions using this instance.
But there is one drawback with this approach.
One of the beautiful parts of class Template is that you only generate code that you actually use. So if, for example, I have other function(s) in the class Template, and I’m using the traditional way to write Templates (i.e. include the definition in the header and NOT having an explicit instantiate the entire class Template with a specific type), then in this case, objdump will only show me what is actually being used. Example:
1 2 3 4 5 6 7 8 9 10 |
namespace kobi { template <typename T> class MyTemplate final { T x_ {}; public: T getVal() const; void print() const { std::clog << x_ << '\n'; } }; } // namespace kobi |
If your main() does not invoke print() on some MyTemplate<> instance, this function would not show up in the object file!
However, when you explicitly instantiate this class Template with the syntax showed above:
1 |
template class kobi::MyTemplate<int>; |
Then, no matter if you are using or not using parts of the class Template, everything will be compiled and be part of the .text section.
Did anyone order code bloat?
1 2 |
0000000000000000 w F .text._ZNK4kobi10MyTemplateIiE6getValEv 000000000000014 kobi::MyTemplate<int>::getVal() const 0000000000000000 w F .text._ZNK4kobi10MyTemplateIiE5printEv 0000000000000034 kobi::MyTemplate<int>::print() const |
Yes, print() will be there even if I did not use it in main() .
Conclusions
The traditional way to write and use Template code is to have the definition visible to the compiler at the time of instantiation. You can use it by inline-ing the definition in the body of the class, or outside of the class. Another approach is to write a .cpp (or any other extension) file that contains definitions of the member function definitions and #include<> it in the header. In all of the above, you do not compile a separate .cpp Template file.
Another approach is to compile a separate .cpp file and explicitly instantiate the class Template with specific types. These are the only types you could use with this class Template. Also, using this approach the compiler will instantiate the entire class member functions regardless of what you actually use. This is different from the traditional approach where the compiler is generating only parts that you actually use.