views:

29

answers:

4

This problem is in defining and declaring a function template in a namespace that is defined in an external file from where the function is instantiated. Here's the smallest reproducible example I could come up with. 4 files follow:

The function template declaration in a named namespace:

// bar.h
#include <algorithm>

namespace barspace {

  template <typename Iter>
    void DoSomething (Iter first, Iter last);

}

The function template definition in a separate file:

// bar.cpp
#include "bar.h"

namespace barspace {

  template <typename Iter>
    void DoSomething (Iter first, Iter last) {
      typedef typename std::iterator_traits<Iter>::value_type val_t;
      std::sort (first, last);
    }

} // namespace barspace

The header for the main program

// foo.h
#include "bar.h"
#include <vector>

Lastly, the main program where the function template is called:

//foo.cpp
#include "foo.h"

int main () {

  std::vector<double> v_d;
  for (int i = 0; i < 10; i++) {
    v_d.push_back (i);
  }

  barspace::DoSomething (v_d.begin(), v_d.end());

  return 0;
}

I compile as follows:

g++ -c -o bar.o bar.cpp

g++ -c -o foo.o foo.cpp

These run fine. Now for linking:

g++ bar.o foo.o -o foobar

And the resulting compiler error about the undefined reference:

foo.o: In function `main':
foo.cpp:(.text+0x6e): undefined reference to `void barspace::DoSomething<__gnu_cxx::__normal_iterator<double*, std::vector<double, std::allocator<double> > > >(__gnu_cxx::__normal_iterator<double*, std::vector<double, std::allocator<double> > >, __gnu_cxx::__normal_iterator<double*, std::vector<double, std::allocator<double> > >)'
collect2: ld returned 1 exit status

There is an obvious problem with the code not getting made available from within the namespace, or from the bar compilation unit.

Furthermore, when I try to place the definition of DoSomething in the bar.h header, as I would to circumvent the issues when defining class template methods in separate .cpp files, I get the same error.

Can you shed some light unto my compiler linking error?

+1  A: 

Putting templates definitions in a separate source file is not well supported (I believe the only compiler that support that is Comeau).

You need to move the definition into your header files:

// bar.h

namespace barspace {

  template <typename Iter>
    void DoSomething (Iter first, Iter last) {
      typedef typename std::iterator_traits<Iter>::value_type val_t;
      std::sort (first, last);
    }

} // namespace barspace
R Samuel Klatchko
A: 

Many compilers do not like having template functions defined in seperate files. Put the whole definition in the header file and it should compile just fine.

Duracell
+1  A: 

You are trying to hide the implementation of your templated function into the cpp file, which, unfortunately, is not possible for most compilers. Templated functions/classes are instantiated when used, so at the point where you are calling DoSomething, the compiler needs the definition of the function to be able to compile it.

There are a couple of solutions.

  1. Move the function body into the header file. You had trouble doing that before, but i'd say it's related to something else. This is the preferred approach.

  2. Include the cpp file from foo.cpp. (wild, but not that uncommon).

  3. Instantiate the template for double:

// bar.cpp
#include "bar.h"

namespace barspace {

  template<>
    void DoSomething<double> (double first, double last) {
      typedef typename std::iterator_traits<double>::value_type val_t;
      std::sort (first, last);
    }

} // namespace barspace
Igor Zevaka
A: 

You must define template functions in the headers. You can't declare them and then define them in an implementation file; that just won't work. The reason is that the compiler must instantiate the templates before you can call them. To instantiate a function template the compiler needs two things:

  1. The template definition
  2. The template parameters to instantiate with

In your case, the compiler works with two compilation units. It may well run them in two different processes, so the compilation units are independent of each other. When the compiler compiles bar.cpp, it sees a template definition with no requests (calls) for specific instantiations. Therefore, the compiler doesn't instantiate the template; compiling bar.cpp yields, in fact, nothing. The compiler is "smart" enough to see that you don't need that template, and optimizes it down to zero overhead.

Of course, you do need that template -- in foo.cpp -- but that is a different compilation unit, and by now the compiler forgot all (or hasn't learnt yet) about bar.cpp. The compiler does, however, see a declaration of the template function, and it makes the usual assumption that if it was declared then it was defined (instantiated) elsewhere -- and says nothing.

Finally, the linker comes along and gets the ultimate bird-view. It can see there's no DoSomething<Iter>(Iter, Iter) instantiated for std::vector<double>::iterator and complains.

There are a few solutions to your problem. The best solution is to use the export keyword when you declare the template. Unfortunately, that is also the worst solution, since the vast majority of compilers don't support this standard feature.

Seriously though, your best bet is to define your template in the header file. Don't declare it there, define it there. You won't need bar.cpp if that's what it was for in the first place.

wilhelmtell