I have a problem with templates in c++, I have a method that uses them and I declare it in its file .hpp
but within the logic of the function I still declare the template but it sends me an error.
// entity.hpp
class Entity {
public:
// Start a empty Entity (without transform component)
Entity(unsigned int id);
// Manage the component entry and exist of the entity
void removeComponent ();
template<typename ComponentType, typename ... ComponentArguments>
void addComponent (ComponentArguments&& ... componentArguments );
// Call the base component functions each frame
void start ();
void update();
private:
// List of component of the Entity
std::vector<Component*> components;
};
// entity.cpp
template<typename ComponentType, typename ... ComponentArguments>
void Entity::addComponent (ComponentArguments&& ... componentArguments) {
// Create the new component without specific type passing his arguments (fancy c++ code) and append his owner
ComponentType *newComponent(new ComponentType(std::forward<ComponentArguments>(componentArguments)...));
// Add the new component to the component list and mask
components.emplace_back(newComponent);
}
The error is this, the truth is that I do not understand very well what is happening
PS C:\Users\josem\Documents\game_engine> make
g++ ./src/main.cpp ./src/ECS/entity.cpp -I ./includes -L ./lib -Wall -lraylib -lopengl32 -lgdi32 -lwinmm -o game.exe -O2
C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:\Users\josem\AppData\Local\Temp\cckj8wGD.o:main.cpp:(.text.startup+0x1e): undefined reference to `void Entity::addComponent<Position>()'
collect2.exe: error: ld returned 1 exit status
make: *** [all] Error 1
I don't know what I have to change to make it work, when I have everything in the file .hpp
it works, but I would like to have everything in 2 files (not sure if that is good practice though, if not please let me know)
The behavior you describe is given by the combination of the following characteristics of the c++ code compilation :
Let's go point by point.
levels of existence.
In c++ an object can conceptually be at three " levels of existence ": declared, defined, or instantiated. But the case of templates is special; Before explaining why I am going to describe the concepts that I have mentioned.
Declaration : It is indicated that the template exists without detailing what it is like; the template name may be used as long as it does not cause an instance to be created. In other words, it describes the template for us without going into details:
Definition : The template is described in detail, it counts as a declaration if there was no previous declaration, the template can be instantiated without limitations. That is, it details the template in full:
Instantiation : When template parameters are indicated (or deduced). In other words, create what corresponds to the indicated characteristics:
Headers are not compiled .
In general header files (
*.hpp
) are used for declarations and code files (*.cpp
) are used for definitions. If you ask a compiler to compile a header file it will not generate code.At a more technical level a code file is a translation unit which is generated by combining all the definitions with all the declarations which arrive in the code file via the clause
#include
.Templates are instantiated inline .
Normally a C++ object is instantiated like this:
But a template can't be instantiated like this, since you need to know what its template parameters are:
We find the case that a template is not instantiated until the template parameters are provided, we can also see that the same template declaration can give rise to multiple definitions which are only known at the moment of instantiating: in the same line on which they are instantiated... that is inline .
Now that we've gone over that, what's going on with the templates separated into code and header files? Let's look at a simple example:
f.hpp
f.cpp
main.cpp
When the translation unit is created
main
, the result of passing the preprocessor through the file is compiledmain.cpp
, which might look like this:In
main
we are instantiating the templatef(const T &)
asf(const int &)
so it looks for the body off(const int &)
but it doesn't find it and it gives an error.If it had been a normal function (not a template function) the function definition would be in a translation unit that would have been compiled and found by the linker. But since it is a template function, the compiler has not compiled anything since in the code file (
*.cpp
) we have not told it which instance should be compiled (templates are instantiated inline ), that has only been said inmain.cpp
!Possible solutions are:
f.hpp
*.cpp
can have an arbitrary extension).main.cpp
It's the way templates work.
A template only exists in the source code.
Only when you use the template, the compiler generates the corresponding code and once compiled it exists as a set of instructions.
You can fix it, in a few ways.
Putting all the code of the class or function that uses a template in the same file (.h). If you prefer, you can write the code in a .cpp file and add a
#include
with that file.For example:
Note that thus the functions
foo
are not in an additional translation unit. Therefore you should not compile the .cpp file directly.Having the definitions in the .how file you can generate the specializations explicitly.
For example:
In this case, the functions
foo
are in another translation unit.If you can't modify the .cpp file, you can create a new one including the other one and writing the specializations. So:
The functions
foo
are also in a different translation unit but you should compile the file with the specializations instead of the original. Otherwise you will get duplicate symbol errors.More information .