After a fair deal of research I understand why templates classes cannot be separated into header and source files traditionally.
However, this (Why can templates only be implemented in the header file?) seems to imply that you can still have a sort of pseudo-separate-compilation process by including the implementation file at the end of the header file as shown below:
// Foo.h
template <typename T>
struct Foo
{
void doSomething(T param);
};
#include "Foo.tpp"
// Foo.tpp
template <typename T>
void Foo<T>::doSomething(T param)
{
//implementation
}
With the explanation "A common solution to this is to write the template declaration in a header file, then implement the class in an implementation file (for example .tpp), and include this implementation file at the end of the header."
However, when I do that, I get multiple errors stating it is a redefinition of a constructor/method/function. I have been able to prevent this by putting include guards on the .cpp files which seems like bad practice.
My main questions would then be:
What is the proper way to do this with the implementation include at the bottom of the header file?
Would the include guards on my .cpp file prevent the compiler from creating the templates class/function for other types since it can now only be included once?
Isn't part of the reason for using header files in the first place is to prevent code from being recopied every time it is included, to keep a short compilation time? So what is the performance effect of templated functions (since they have to be defined in header) versus simply overloading a function/class? When should each be used?
Below is an abridged version of code for my own simple Node struct:
// Node.hpp
#ifndef NODES_H
#define NODES_H
#include <functional>
namespace nodes
{
template<class Object>
struct Node
{
public:
Node(Object value, Node* link = nullptr);
void append(Node tail);
Object data;
Node* next;
};
template<class Object> void prepend(Node<Object>*& headptr, Node<Object> newHead);
}
// Forward 'declaration' for hash specialization
namespace std
{
template <typename Object>
struct hash<nodes::Node<Object>>;
}
#include "Node.cpp"
#endif
// Node.cpp
// #ifndef NODE_CPP
// #define NODE_CPP
#include "Node.hpp"
template<class Object>
nodes::Node<Object>::Node(Object value, Node* link): data(value), next(link) {}
template<class Object>
void nodes::Node<Object>::append(Node tail) {
Node* current = this;
while (current->next != nullptr) {
current = current->next;
}
current->next = &tail;
}
template<class Object>
void nodes::prepend(Node<Object>*& headptr, Node<Object> newHead) {
Node<Object>* newHeadPtr = &newHead;
Node<Object>* temporary = newHeadPtr;
while (temporary->next != nullptr) {
temporary = temporary->next;
}
temporary->next = headptr;
headptr = newHeadPtr;
}
namespace std
{
template <typename Object>
struct hash<nodes::Node<Object>>
{
size_t operator()(nodes::Node<Object>& node) const
{
return hash<Object>()(node.data);
}
};
}
// #endif