Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ template function compiles in header but not implementation

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?

like image 480
flies Avatar asked Jun 14 '10 20:06

flies


People also ask

Why template function only defined header files?

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.

Why can't we compile template code into its own .O file?

Because the templates are not functions, they can't be compiled separately.

How do I force a template instantiation?

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.

How do you implement a template function in C++?

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.


1 Answers

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 #included 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.

File: dumper2.h

#include <iostream> #include <string> #include <vector>  void test(); template <class T> void dumpVector( std::vector<T> v,std::string sep); #include "dumper2.hpp" 

File: 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:

File: dumper2.h

#include <iostream> #include <string> #include <vector>  void test(); template <class T> void dumpVector( std::vector<T> v,std::string sep); 

File: dumper2.cpp

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.

like image 154
John Dibling Avatar answered Sep 17 '22 12:09

John Dibling