I’m trying to learn templates and I’ve run into this confounding error. I’m declaring some functions in a header file and I want to make a separate implementation file where the functions will be defined.
Here’s the code that calls the header (dum.cpp):
#include <iostream>
#include <vector>
#include <string>
#include "dumper2.h"
int main() {
std::vector<int> v;
for (int i=0; i<10; i++) {
v.push_back(i);
}
test();
std::string s = ", ";
dumpVector(v,s);
}
Now, here’s a working header file (dumper2.h):
#include <iostream>
#include <string>
#include <vector>
void test();
template <class T> void dumpVector(const std::vector<T>& v,std::string sep);
template <class T> void dumpVector(const std::vector<T>& v, std::string sep) {
typename std::vector<T>::iterator vi;
vi = v.cbegin();
std::cout << *vi;
vi++;
for (;vi<v.cend();vi++) {
std::cout << sep << *vi ;
}
std::cout << "\n";
return;
}
With implementation (dumper2.cpp):
#include <iostream>
#include "dumper2.h"
void test() {
std::cout << "!olleh dlrow\n";
}
The weird thing is that if I move the code that defines dumpVector from the .h to the .cpp file, I get the following error:
g++ -c dumper2.cpp -Wall -Wno-deprecated
g++ dum.cpp -o dum dumper2.o -Wall -Wno-deprecated
/tmp/ccKD2e3G.o: In function `main':
dum.cpp:(.text+0xce): undefined reference to `void dumpVector<int>(std::vector<int, std::allocator<int> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >)'
collect2: ld returned 1 exit status
make: *** [dum] Error 1
So why does it work one way and not the other? Clearly the compiler can find test(), so why can’t it find dumpVector?
The problem you’re having is that the compiler doesn’t know which versions of your template to instantiate. When you move the implementation of your function to x.cpp it is in a different translation unit from main.cpp, and main.cpp can’t link to a particular instantiation because it doesn’t exist in that context. This is a well-known issue with C++ templates. There are a few solutions:
1) Just put the definitions directly in the .h file, as you were doing before. This has pros & cons, including solving the problem (pro), possibly making the code less readable & on some compilers harder to debug (con) and maybe increasing code bloat (con).
2) Put the implementation in x.cpp, and
#include "x.cpp"from withinx.h. If this seems funky and wrong, just keep in mind that#includedoes nothing more than read the specified file and compile it as if that file were part ofx.cppIn other words, this does exactly what solution #1 does above, but it keeps them in seperate physical files. When doing this kind of thing, it is critical that you not try to compile the#included file on it’s own. For this reason, I usually give these kinds of files anhppextension to distinguish them fromhfiles and fromcppfiles.File: dumper2.h
File: dumper2.hpp
3) Since the problem is that a particular instantiation of
dumpVectoris not known to the translation unit that is trying to use it, you can force a specific instantiation of it in the same translation unit as where the template is defined. Simply by adding this:template void dumpVector<int>(std::vector<int> v, std::string sep);… to the file where the template is defined. Doing this, you no longer have to#includethehppfile from within thehfile:File: dumper2.h
File: dumper2.cpp
By the way, and as a total aside, your template function is taking a
vectorby-value. You may not want to do this, and pass it by reference or pointer or, better yet, pass iterators instead to avoid making a temporary & copying the whole vector.