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( std::vector<T> v,std::string sep); template <class T> void dumpVector(std::vector<T> v, std::string sep) { typename std::vector<T>::iterator vi; vi = v.begin(); std::cout << *vi; vi++; for (;vi<v.end();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
?
A template is not like a function which can be compiled into byte code. It is just a pattern to generate such a function. If you put a template on its own into a *. cpp file, there is nothing to compile.
Because the templates are not functions, they can't be compiled separately.
To instantiate a template function explicitly, follow the template keyword by a declaration (not definition) for the function, with the function identifier followed by the template arguments. template float twice<float>(float original); Template arguments may be omitted when the compiler can infer them.
Defining a Function TemplateA function template starts with the keyword template followed by template parameter(s) inside <> which is followed by the function definition. In the above code, T is a template argument that accepts different data types ( int , float , etc.), and typename is a keyword.
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 within x.h
. If this seems funky and wrong, just keep in mind that #include
does nothing more than read the specified file and compile it as if that file were part of x.cpp
In 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 #include
d file on it's own. For this reason, I usually give these kinds of files an hpp
extension to distinguish them from h
files and from cpp
files.
#include <iostream> #include <string> #include <vector> void test(); template <class T> void dumpVector( std::vector<T> v,std::string sep); #include "dumper2.hpp"
template <class T> void dumpVector(std::vector<T> v, std::string sep) { typename std::vector<T>::iterator vi; vi = v.begin(); std::cout << *vi; vi++; for (;vi<v.end();vi++) { std::cout << sep << *vi ; } std::cout << "\n"; return; }
3) Since the problem is that a particular instantiation of dumpVector
is 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 #include
the hpp
file from within the h
file:
#include <iostream> #include <string> #include <vector> void test(); template <class T> void dumpVector( std::vector<T> v,std::string sep);
template <class T> void dumpVector(std::vector<T> v, std::string sep) { typename std::vector<T>::iterator vi; vi = v.begin(); std::cout << *vi; vi++; for (;vi<v.end();vi++) { std::cout << sep << *vi ; } std::cout << "\n"; return; } template void dumpVector<int>(std::vector<int> v, std::string sep);
By the way, and as a total aside, your template function is taking a vector
by-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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With