Reputation: 2683
I'm getting the following error while compiling this simple program using Visual Studio:
error LNK2019: unresolved external symbol "public: void __thiscall CoList<int>::enqueue(int)" (?enqueue@?$CoList@H@@QAEXH@Z) referenced in function _main
error LNK2019: unresolved external symbol "public: virtual __thiscall CoList<int>::~CoList<int>(void)" (??1?$CoList@H@@UAE@XZ) referenced in function _main
error LNK2019: unresolved external symbol "public: int __thiscall CoList<int>::dequeue(void)" (?dequeue@?$CoList@H@@QAEHXZ) referenced in function _main
error LNK2019: unresolved external symbol "public: int __thiscall CoList<int>::count(void)" (?count@?$CoList@H@@QAEHXZ) referenced in function _main
error LNK2019: unresolved external symbol "public: __thiscall CoList<int>::CoList<int>(void)" (??0?$CoList@H@@QAE@XZ) referenced in function _main
error LNK1120: 5 unresolved externals
My program is very simple. I don't use external libraries, just the 'iostream' and 'exception' headers... Here is the full code:
CoList.h
#pragma once
#include "CoListItem.h"
template <class T>
class CoList
{
public:
CoList();
virtual ~CoList();
void enqueue(T value);
T dequeue();
T *peek();
int count();
private:
CoListItem<T> *m_root;
int m_count;
};
CoListItem.h
#pragma once
template <class T>
class CoListItem
{
public:
CoListItem();
virtual ~CoListItem();
T value;
CoListItem *next;
};
CoList.cpp
#include "CoList.h"
#include <exception>
template <class T>
CoList<T>::CoList()
{
}
template <class T>
CoList<T>::~CoList()
{
}
template <class T>
void CoList<T>::enqueue(T value)
{
if (this->m_root != NULL) {
this->m_root = new CoListItem<T>();
this->m_root->value = value;
this->m_root->next = NULL;
} else {
CoListItem<T> *tempitem = new CoListItem<T>();
tempitem->value = value;
tempitem->next = this->m_root;
this->m_root = tempitem;
}
this->m_count++;
}
template <class T>
T CoList<T>::dequeue()
{
if (this->m_root == NULL) {
throw std::exception();
} else {
T retval = this->m_root->value;
CoListItem *next = this->m_root->next;
delete this->m_root;
this->m_root = next;
return retval;
}
}
template <class T>
T *CoList<T>::peek()
{
if (this->m_root == NULL) {
return NULL;
} else {
return *this->dequeue();
}
}
template <class T>
int CoList<T>::count()
{
return this->m_count;
}
CoListItem.cpp
#include "CoListItem.h"
template <class T>
CoListItem<T>::CoListItem()
{
}
template <class T>
CoListItem<T>::~CoListItem()
{
}
and finally the main function:
#include <iostream>
#include "CoList.h"
#include "CoListItem.h"
using namespace std;
int main(int argc, char *argv[])
{
CoList<int> list;
for(int i = 0; i < 10; i++)
list.enqueue(i);
cout << "Count: " << list.count() << endl;
for(int i = 0; i < 10; i++)
cout << "Item: " << list.dequeue() << endl;
cout << "Count: " << list.count() << endl;
int wait = 0;
cin >> wait;
}
As you can see it is a very simple Queue implementation using a linked list...
Upvotes: 4
Views: 1085
Reputation: 206636
This is straight from Nicolai Josutis's legendary book,
C++ Templates: A complete Guide
Templates are compiled twice:
This leads to an important problem in the handling of templates practice. When a function template is used in a way that it triggers instantiation, a compiler will (at some point) need to see that template definition. This breaks the usual compile and link distinction for functions, when declaration of function is sufficient to compile the its use.
Thus, For a template the declaration and definition should be kept in the same header file so that they are visible in every cpp which uses them.
Upvotes: 0
Reputation: 18441
Consider a template function that takes T and performs modulus (%), or a simple addition (+) for that matter.
template <class T>
T GetT(T t1, T t2)
{
return t1%t2;
}
You see NO ERROR in this code. Alright. When I pass two ints it gets compiled:
GetT(10,20);
But when I pass float/double it WONT compile:
GetT(10.6, 20.5);
Compiler will emit: error C2296: '%' : illegal, left operand has type 'double'
and other related errors.
The point is that template code doesn't get compiled until you instantiate it at least once for a particular data type. The template code stays junk - compiler doesn't care what actually inside the code. In your case the CPP is nothing but a text-file the compiler has ignored - All of it.
Having said that, when I use operator +
, instead of operator %
it would work for all basic data-types, but not for classes that are missing operator +
. In that case compiler will compile again the template-stuff for that data-type (the class).
There are cases where compiler and linker work together to reduce final binary code size, when they see some code is duplicate and would be same for all/several data-types. But that's a different case.
Upvotes: 0
Reputation: 70100
template
definitions should be visible to the code which is using it. For that,
#include
that ".cpp" fileFor example, in your case you can #include "CoList.cpp"
instead of "CoList.h"
. And so on.
Upvotes: 1
Reputation: 133112
The definitions of function templates(including member functions of class templates) must be in the .h file so that they are present in every cpp file in which they are used. That's how templates work. You cant put the definitions into a cpp file. Technically, there is an export
keyword which enables this but since almost no implementation supported it it was removed in the new standard.
Read this: The inclusion model
Upvotes: 4