1

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:

  1. What is the proper way to do this with the implementation include at the bottom of the header file?

  2. 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?

  3. 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
Community
  • 1
  • 1
  • Please try to create a [Minimal, Complete, and Verifiable Example](http://stackoverflow.com/help/mcve) and show us. And also include the actual errors you get, in full, complete and unedited. – Some programmer dude Nov 18 '16 at 07:24
  • Two possible issues, but you really need a [mcve]. Your build system may be trying to build the `.cpp` files, but it shouldn't. Best rename them to `.icpp` or something like that. Also, you don't need to include the `.hpp` in there. `#include` is basically copy-pasta. – juanchopanza Nov 18 '16 at 07:24
  • You don't need to `#include` the header into the cpp file, if the cpp is already included in the header. They will be compiled as a single unit anyway. – Bo Persson Nov 18 '16 at 07:28
  • Regarding question 3: The important point that is only 'copied' at compile time, the source is still only in file (2 in your case), and if you need to change it you have to change it only in one place. Separation of header and source files *can* be used to minimize compile time, but IMHO the main reason for the separation is information hiding: You only show the interface and hide the implementation. Yes, templates make compilation slower, but the resulting code is fast(er). – Rene Nov 18 '16 at 07:36
  • Besides your questions: You are storing the addresses of temporary variables in your list. This template will crash gloriously once you get it to compile and run. – Rene Nov 18 '16 at 07:36

1 Answers1

0

What is the proper way to do this with the implementation include at the bottom of the header file?

Put the include guards into your header file, including the implementation #include directive:

#ifndef __FOO_H
#define __FOO_H
// Foo.h
template <typename T>
struct Foo
{
    void doSomething(T param);
};

#include "Foo.tpp"

#endif

You may also add the guards to Foo.tpp, but in the situation you posted it will not make much sense.

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?

Typically you don't need include guards in *.cpp files at all, as you don't include them anywhere. Include guards are only needed in those files which are included into multiple translation units. And of course, those guards will not prevent instantiating the templates for other types, since it is what templates are designed for.

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?

Here you raise a big, platform-dependent and a bit opinion-based topic. Historically, include files were used to prevent code copying, as you said. It was enough to include function declarations (headers, not definitions) into multiple translation units, and then link them with a single copy of the compiled code for included functions.

Templates compile much slower than non-template functions, so implementing template export (separate header/implementation compilation for templates) isn't worth saving compilation time.

For some discussions and good answers on template performance, check these questions:

In short, sometimes templates allow you to make some decisions in compile time instead of runtime, making the code faster. Anyway, the only proper way to determine if the code became faster or not, is to run performance tests in real-world environment.

Finally, templates are more about design, not about performance. They allow you to significantly reduce code duplication and conform DRY principle. A banal example of it are functions like std::max. A more interesting example is Boost.Spirit, which uses templates for building parsers entirely in compile time.

Community
  • 1
  • 1
Sergey
  • 7,985
  • 4
  • 48
  • 80