2

Possible Duplicate:
Why do I get “unresolved external symbol” errors when using templates?

I have a little bit complicated set of classes.
Class _A which has child parametrized class A which has two children A1 and A2.
Class B. Contains pointer on object of class _A as member. Has two child classes B1 and B2 which correspond to classes A1 and A2 each. B1 constructs _A as A1. B2 as A2 respectivelly.
And finally class Y which has child BY defined inside of class B.
Now how it is present in files.

tf1.h

#include <iostream>

struct Y{ // goes to the file tf2.h as ancestor of the class B::BY
};


struct _A{ // goes to the file tf2.h as a member of the class B
};

template<class X>
struct A: public _A {  // goes to two classes below only as an ancestor
    virtual void method();
    protected:
    virtual void m() = 0;
};

template<class X>
struct A1: public A<X>{  // goes to the file tf2.h to the class B1
    protected:
    void m();
};

template<class X>
struct A2: public A<X>{  // goes to the file tf2.h to the class B2
    protected:
    void m();
};

tf1.cpp

#include "tf1.h"


template<class X>
void A<X>::method(){
    /* here the class X used */
    std::cout << "A::method called" << std::endl;
    m();
}

template<class X>
void A1<X>::m(){
    std::cout << "A1::m called" << std::endl;
}

template<class X>
void A2<X>::m(){
    std::cout << "A1::m called" << std::endl;
}

tf2.h

#include "tf1.h"

class B{   // is the counterpain of the class _A
    protected:
    class BY: public Y{
    };

    _A * mp_x;
};


class B1: public B{  // is the counterpain of the class A1
    public:
    B1(){mp_x = new A1<BY>; std::cout << "B::B is called" << std::endl;}
};

class B2: public B{  // is the counterpain of the class A2
    public:
    B2(){mp_x = new A2<BY>; std::cout << "C::C is called" << std::endl;}
};

tfmain.cpp

#include <stdlib.h>

#include "tf2.h"

int main (int,char**){
    B2 b2;
    system ("PAUSE");
}

And, finally, the problem.

d:>g++ -std=c++0x tfmain.cpp -o tfmain.exe
ccB2BnSP.o:tfmain.cpp:(.rdata$_ZTV2A2IN1B2BYEE[__ZTV2A2IN1B2BYEE]+0x8): undefined reference to `A<B::BY>::method()'
ccB2BnSP.o:tfmain.cpp:(.rdata$_ZTV2A2IN1B2BYEE[__ZTV2A2IN1B2BYEE]+0xc): undefined reference to `A2<B::BY>::m()'
ccB2BnSP.o:tfmain.cpp:(.rdata$_ZTV1AIN1B2BYEE[__ZTV1AIN1B2BYEE]+0x8): undefined reference to `A<B::BY>::method()

I am using MinGW v4.7.2

Community
  • 1
  • 1
OlegG
  • 975
  • 3
  • 10
  • 30

3 Answers3

3

You can't put your templated functions definitions inside of .cpp files, you have to put them in your header files. This is a weird limitation of templates.

It has to do with the fact that templated versions of your functions are only generated when they are used. Thus, you can't pre-compile a templated function, because it hasn't been used yet, so it doesn't generate everything.

Xymostech
  • 9,710
  • 3
  • 34
  • 44
  • But compiler does not protest against it. So why have I to think that I do something wrong? – OlegG Nov 28 '12 at 19:51
  • Well, you got a linker error. That should probably be a red flag. – Xymostech Nov 28 '12 at 19:53
  • Generally speaking you're obviously right - if I get linker error then I do something wrong. Yes, captain. But compiler does not protest and allows me to make mistakes which are not mistakes from its point of view. Weird. Definetly weird. – OlegG Nov 28 '12 at 20:01
  • 1
    I mean, you're not really "making a mistake", from the compiler's point of view. The compiler never sees that you have used the template, so it doesn't generate any code. Thus, only when you get to linking, the compiler gets confused about why you never defined them. Like I said, it's just a bit of a weird/annoying thing that you have to do to get templated functions to work. – Xymostech Nov 28 '12 at 20:04
  • 1
    @OlegG From the compiler's point of view, there is nothing wrong with calling a template when some or all definition of its methods is not in the scope. It's the same with using a normal class. From a grammar point of view, there is absolutely nothing wrong with it. The only problem is how the compiler generates code when it comes to template. My answer below tries to explain this. – Jimmy Lu Nov 28 '12 at 20:05
  • @OlegG : You can define the templates in another compilation unit and link them in, but you have to explicitly instantiate them in the other compilation unit. So until link time, the compiler does not know you are screwing up. Now, maybe you should get a warning, because few people intend to do this, but such a warning ends up being redundant with the shortly-arriving linker error, so... – Yakk - Adam Nevraumont Nov 28 '12 at 20:15
  • Guys, please, show me what and how in my case `method()` and `m()` are not in the scope. And why it happens only for A2 and not for A1? – OlegG Nov 28 '12 at 20:15
3

To put it simply, you can't put template definitions into a cpp file (there are workarounds... but not pleasant ones)

The reason is that the compiler only instantiates a template at the first point you invoke that template, since the compiler needs to know what type to instantiates the template with.

Now, if you put the template definition into a separate cpp file which gets compiled separately into its own translation unit. Where does the compiler look for cases where you instantiate the template?

For example

// Template.h
template <typename T>
class templateObj { ~templateObj(); };

// Template.cpp
#include "Template.h"
template <typename T>
templateObj::~templateObj() { /* delete stuff! */ }

// YourFile.cpp
#include "Template.h"
templateObj<int> myObj;

Now, when the compiler compiles this code. It will generate two translation units, one for Template.cpp, the other for YourFile.cpp.

Note that Template.cpp has not a single clue of what YourFile.cpp is and what's inside it. So it has no way of knowning that in YourFile.cpp, you have used templateObj with a template parameter type int. Because of this, in the resulting translation unit of Template.cpp, the compiler will not generate an instantiated version of the destructor of templateObj

Now, let's look at YourFile.cpp, when the compiler sees that you're instantiating templateObj with type int, it goes to look for the definition of templateObj. Since you have included Template.h, the compiler sees that you have declared a destructor for templateObj. But where is the definiton??

The compiler doesnt see the definition of ~templateObj(), nor does it know where to look for at this point. So it simply holds off and will pass on to the linker to search for the correct module to link to.

Now here is the problem:

In the two translation units the compiler has just produced, neither YourFile.o or Template.o has the definition for template<int>::~template(). The linker reads in YourFile.o and expects to have that version of the destructor of templateObj to link to, but the only other translation unit Template.o doesn't have it; in fact, it has nothing.

So what now? The linker has to complain... and that's the error message you get.


A bit more details about what happened with your code:

  • Two translation units are produced: tf1.o and tfmain.o
  • tf1.o is generated from tf1.cpp and whatever it includes.
  • tfmain.o is generated from tfmain.cpp and whatever itcludes.

So what do they include? What does tf1.cpp and tfmain.cpp know about the rest of the code?

tf1.cpp

  • #include "tf1.h" ==> which #include <iostream>...

tfmain.cpp

  • #include "tf2.h" ==> which #include "tf1.h" ==> which #include <iostream>...

What does tf1.cpp have? What does it know?

  • knows declarations of Y and _A
  • knows template declarations of A, A1 and A2
  • has template definitions for various methods in A, A1, and A2

What does tfmain.cpp have? What does it know?

  • knows declarations of B, B1, B2, Y and _A
  • knows template declarations of A, A1 and A2
  • has definitions for various methods in B, B1, B2
  • has main and an instance of B2

So, now the questions are:

Does tf1.cpp know anything about tf2.h or tfmain.cpp? Does it know that you're creating an instance to B2 which instantiates A2 with type BY?

Does tfmain.cpp know anything about tf1.cpp? Does it know the definitions of methods in A, A1 or A2??

They do NOT know each other, and thus the compiler has no way to generate the code for the definiton to your template classes in the translation unit tf1.o (At that point, it doesn't even know you're creating an instance to B2 which instantiates A2 with type BY).

And finally the linker has no way to find the code tfmain.o is asking for, since it is NOT there.

Jimmy Lu
  • 4,810
  • 7
  • 25
  • 30
  • Does compiler compile header file separatelly from a corresponding cpp file that we can think about cpp file as about separate cimpilation unit? – OlegG Nov 28 '12 at 20:05
  • The compiler does **not** compile a header file. When you `#include "headerfile.h"`, all you are doing is instructing the preprocessor to **COPY and PASTE** that header file into the cpp file. Yeah, that's all it's doing. C/C++ unfortunately has quite an ancient system for this... – Jimmy Lu Nov 28 '12 at 20:07
  • If compiler dos not compile a header file then in my case every class implementation falls in the same corresponding compilation unit. And therefore it is not the case of separate compilation units. See please my code. – OlegG Nov 28 '12 at 20:10
  • 1
    well you see, `tf1.o` and `tf2.o` are two separate translation units. And they do not have *full* mutual knowledge of each other. And when you use that template class in `tf2.cpp`, what the compiler sees is the content that can be found in `tf1.h1`. Now does `tf1.h` have the full definition of the template class you're using? And, does `tf1.cpp` know in any way that you're instantiating the class with that specific type in `tf2.cpp`? – Jimmy Lu Nov 28 '12 at 20:13
  • But please note that there is no file tf2.cpp first. And the second, tf2.h knows all about tf1.o because it includes tf1.h – OlegG Nov 28 '12 at 20:19
  • your file names have confused me. I'm going to edit my answer to show you why it does not work. – Jimmy Lu Nov 28 '12 at 20:20
  • Yes, Seems you have to read my code more carefully. And please note that error happens for A1 and not hapens for A2 while they both are absolutelly identical. Why so? – OlegG Nov 28 '12 at 20:27
  • @OlegG I have updated my answer. Note the reason only `A2` have problems because in `tfmain.cpp` you are not creating an instance to `B1`, which instantiates `A1`, so the linker had no need to link code to `A1`'s methods. It only needed to link to `A2`'s methods. – Jimmy Lu Nov 28 '12 at 20:43
  • Yes. Now it is totally clear. Thanks a lot :) – OlegG Nov 29 '12 at 06:14
0

Xymostech answered your question, but I do not agree that this limitation is weird. ;) This should clarify this matter for you.

Community
  • 1
  • 1
Dino
  • 599
  • 1
  • 9
  • 20
  • Well, it's only weird in the sense that it doesn't work the same as other function declarations/definitions. I know what you mean, though. – Xymostech Nov 28 '12 at 19:53