Separating CPP Template Declaration and Implementation

Home / Separating CPP Template Declaration and Implementation

Templates are a feature of the C++ programming language that allow function and classes to operate with generic types. This allows a function or class to work on many different data types without being rewritten for each one. The C++ Standard Library specify a special section for heavy use of template, called Standard Template Library.

Defining a template is like defining a blueprint. When we need a specific instance of function or class, the instance can be made from the generic blueprint. However, the common procedure in C++ which put the definition in a C++ header file and the implementation in a C++ source file can’t be applied to template. This is the behavior which is true for all compiler.

In this article we will discuss about how to separate declaration and implementation of template object (class / function). The article will be divided into three sections: how template is parsed, compilation issue, and the solution methods.

How Template is Parsed

Unlike other code, templates are parsed not only once, but twice. This process is explicitly defined in the C++ standard and although some compilers do ignore this, they are, in effect, non-compliant and may behave differently to what this article describes.

So what are those two processes? They are, let’s say, Point of Declaration (PoD) and Point of Instantiation (PoI).

1. Point of Declaration (PoD)

During the first parse, or Point of Declaration, the compiler checks the syntax of the template and match it with C++ grammar. This way, compiler does not consider the dependent types (the template parameters that form the templates types within the templates).

In real world, we can consider this phase as checking the grammar of a paragraph without checking the meaning of the words (the semantics). Grammatically the paragraph can be correct but the the arrangement of words may have no useful meaning. During the grammar checking phase, we don’t are about the meaning of the words. Our intention only toward the correct syntax.

Now, consider following template code:

template <typename T>
void foo (T const & t)
{
    t.bar();
}

This is syntactically correct. However, at this point we have no idea what type the dependent type T is so we just assume that in all cases of T it is correct to call member bar() on it. Of course, if type T doesn’t have this member then we have a problem but until we know what type T is we don’t know if there is a problem so this code is ok for now.

2. Point of Instantiation (PoI)

At PoD we have declare a template, which we have the blueprint. The instantiation process start at Point of Instantiation. At this point we actually define a concrete type of our template. So consider these 2 concrete instantiations of the template defined above…

foo(1); // this will fail the 2nd pass because an int (1 is an int) does not have a member function called bar()
foo(b); // Assuming b has a member function called bar this instantiation is fine

It is perfectly legal to define a template that won’t be corrected under all circumstances of instantiation. Since code for a template is not generated unless it is instantiated the compiler won’t complain unless we try to instantiate it.

At this point, the semantics are now checked against the known dependent type to make sure that the generated code will be correct. To do this, the compiler must be able to see the full definition of template. If the definition of the template is defined in a different place where it is being instantiated the compiler has no way to perform this check. Thus, the template won’t be instantiated and an error message will be delivered.

Remember that compiler can only see and process one translation unit (module / source code) at a time. Now, if the template is only used in one translation unit and the template is defined in same translation unit, no problem rises.

Now let’s recap what we got so far. If the template definition is in translation unit A and we try to instantiate it in translation unit B the compiler won’t be able to instantiate the template because it can’t see the full definition so it will result in linker errors (undefined symbols). If everything is in one place then it will work. but it is not a good way to write templates.

Template Compilation-Issue

From the first section we know that between template definition should be visible and on the same translation unit with the instantiation. Thus, the following code works:

/** BigFile.cpp **/
#ifndef _FOO_HPP_
#define _FOO_HPP_

template<typename T>
class Foo {
public:
   Foo() { }
   void setValue (T obj_i) { }
   T getValue () { return m_Obj; }

private:
   T m_Obj;
}

#endif

The above code is legal, but we can’t afford to this everytime. Sooner or later we’ll probably end up using the template in other translation units because it is highly unlikely (although not improbable) that we’d go to all the effort of creating a generic template class/function that we’ll only ever use in one place.

Our first effort to separate the declaration and implementation (or definition) would be:

/** Foo.hpp **/
#ifndef _FOO_HPP_
#define _FOO_HPP_

template<typename T>
class Foo {
public:
   Foo();
   void setValue (T obj_i);
   T getValue ();

private:
   T m_Obj;
}

#endif

and

#include "Foo.hpp"

Foo::Foo()
{
}

void Foo::setValue (T obj_i)
{
}

T Foo::getValue()
{
   return m_Obj;
}

Now if we try to implement the template class like a normal class implementation as shown above, it will generate a set of compilation error.

To compile the class without any errors, we need to put the template specific declaration in a source file (.cpp file)

#include "Foo.hpp"

template <typename T>
Foo<T>::Foo()
{
}

template <typename T>
void Foo<T>::setValue (T obj_i)
{
}

template <typename T>
T Foo<T>::getValue()
{
   return m_Obj;
}

We pass the compilation phase (the PoD). However the next problem we have is in linking. With above code, after resolving all the compilation errors, we may face linking error issue while we create an object of this class in any file other than Foo.cpp.

For example we have a client source code which instantiate the template (in different translation unit):

/** Client.cpp **/
#include "Foo.hpp"

int main() {
   Foo<int> temp;

   return 0;
}

We have known that the declaration+definition and instantiation are in different translation unit. The client code in Client.cpp can’t access the template implementation source (Foo.cpp) thus compiler refuse to instantiate the template. Compiler have no idea how to construct the Foo member function. And if we have put the implementation in a source file and made it a separate part of the project, the compiler won’t be able to find it when it is trying to compile the client source file.

The inclusion of Foo header file won’t be sufficient at this time. It only tells the compiler how to allocate the object data and how to build the calls to the member functions, not how to build the member functions. The compiler won’t complain. It will assume that these functions are provided elsewhere, and leave it to the linker to find them. So, when it’s time to link, you will get “unresolved references” to any of the class member functions that are not defined “inline” in the class definition.

So how do we structure our code so that the compiler can see the definition of the template in all translation units where it is instantiated? The solution is really quite simple, put the templates definition somewhere that is visible to all PoIs and that is. All the methods will be described at next section.

Solution Methods

There are different methods to solve our presented problem. We can select from any of these methods depending on which suitable for our application design.

Method 1

We can create an object of a template class in the same source file where it is implemented (TestTemp.cpp). So, there is no need to link the object creation code with its actual implementation in some other file. This will cause the compiler to compile these particular types so the associated class member functions will be available at link time. Here is the sample code:

/** Foo.hpp **/
#ifndef _FOO_HPP_
#define _FOO_HPP_

template<typename T>
class Foo {
public:
   Foo();
   void setValue (T obj_i);
   T getValue ();

private:
   T m_Obj;
}

#endif

and

#include "Foo.hpp"

Foo::Foo()
{
}

void Foo::setValue (T obj_i)
{
}

T Foo::getValue()
{
   return m_Obj;
}

/* We define a function but no need to call this temporary function.
 * It is used only to avoid link error
 *
void TemporaryFunction()
{
   Foo<int> temp;
}

also the client:

/** Client.cpp **/
#include "Foo.hpp"

int main() {
   Foo<int> temp;
   temp.setValue(3);
   int value = temp.getValue();
   return 0;
}

The temporary function in Foo.cpp will solve the link error. No need to call this function because it’s global.

Method 2

In this method we include source file that implements our template in our client source file.

/** Foo.hpp **/
#ifndef _FOO_HPP_
#define _FOO_HPP_

template<typename T>
class Foo {
public:
   Foo();
   void setValue (T obj_i);
   T getValue ();

private:
   T m_Obj;
}

#endif

and

#include "Foo.hpp"

Foo::Foo()
{
}

void Foo::setValue (T obj_i)
{
}

T Foo::getValue()
{
   return m_Obj;
}

also the client:

/** Client.cpp **/
#include "Foo.hpp"
#include "Foo.cpp"

int main() {
   Foo<int> temp;
   temp.setValue(3);
   int value = temp.getValue();
   return 0;
}

Method 3

In this method we include the source file that implements our template class (Foo.cpp) in our header file that declare the template class (Foo.hpp), and remove the source file form the project, not from the folder.

/** Foo.hpp **/
#ifndef _FOO_HPP_
#define _FOO_HPP_

template<typename T>
class Foo {
public:
   Foo();
   void setValue (T obj_i);
   T getValue ();

private:
   T m_Obj;
}

#include "Foo.cpp"

#endif

and

Foo::Foo()
{
}

void Foo::setValue (T obj_i)
{
}

T Foo::getValue()
{
   return m_Obj;
}

also the client:

/** Client.cpp **/
#include "Foo.hpp"

int main() {
   Foo<int> temp;
   temp.setValue(3);
   int value = temp.getValue();
   return 0;
}

,

About Author

about author

xathrya

A man who is obsessed to low level technology.

Leave a Reply

Your email address will not be published. Required fields are marked *

Social media & sharing icons powered by UltimatelySocial