Search

13.3 — Template classes

In the previous two lessons, you learn how 13.1 -- Function templates, which get instantiated into 13.2 -- Function template instances, allow us to generalize functions to work with many different data types. While this is a great start down the road to generalized programming, it doesn’t solve all of our problems. Let’s take a look at an example of one such problem, and see what templates can further do for us.

Templates and container classes

In the lesson on 10.6 -- Container classes, you learned how to use composition to implement classes that contained multiple instances of other classes. As one example of such a container, we took a look at the IntArray class. Here is a simplified example of that class:

While this class provides an easy way to create arrays of integers, what if we want to create an array of doubles? Using traditional programming methods, we’d have to create an entirely new class! Here’s an example of DoubleArray, an array class used to hold doubles.

Although the code listings are lengthy, you’ll note the two classes are almost identical! In fact, the only substantive difference is the contained data type (int vs double). As you likely have guessed, this is another area where templates can be put to good use, to free us from having to create classes that are bound to one specific data type.

Creating template classes works pretty much identically to creating template functions, so we’ll proceed by example. Here’s our array class, templated version:

Array.h:

As you can see, this version is almost identical to the IntArray version, except we’ve added the template declaration, and changed the contained data type from int to T.

Note that we’ve also defined the getLength() function outside of the class declaration. This isn’t necessary, but new programmers typically stumble when trying to do this for the first time due to the syntax, so an example is instructive. Each templated member function declared outside the class declaration needs its own template declaration. Also, note that the name of the templated array class is Array<T>, not Array -- Array would refer to a non-templated version of a class named Array.

Here’s a short example using the above templated array class:

This example prints the following:

11     11.5
10     10.5
9       9.5
8       8.5
7       7.5
6       6.5
5       5.5
4       4.5
3       3.5
2       2.5
1       1.5
0       0.5

Template classes are instanced in the same way template functions are -- the compiler stencils out a copy upon demand, with the template parameter replaced by the actual data type the user needs, and then compiles the copy. If you don’t ever use a template class, the compiler won’t even compile it.

Template classes are ideal for implementing container classes, because it is highly desirable to have containers work across a wide variety of data types, and templates allow you to do so without duplicating code. Although the syntax is ugly, and the error messages can be cryptic, template classes are truly one of C++’s best and most useful features.

Template classes in the standard library

Now that we’ve covered template classes, you should understand what std::vector<int> means now -- std::vector is actually a template class, and int is the type parameter to the template! The standard library is full of predefined template classes available for your use. We’ll cover these in later chapters.

Splitting up template classes

A template is not a class or a function -- it is a stencil used to create classes or functions. As such, it does not work in quite the same way as normal functions or classes. In most cases, this isn’t much of a issue. However, there is one area that commonly causes problems for developers.

With non-template classes, the common procedure is to put the class definition in a header file, and the member function definitions in a similarly named code file. In this way, the source for the class is compiled as a separate project file. However, with templates, this does not work. Consider the following:

Array.h:

Array.cpp:

main.cpp:

The above program will compile, but cause a linker error:

unresolved external symbol "public: int __thiscall Array::getLength(void)" (?GetLength@?$Array@H@@QAEHXZ)

In order for the compiler to use a template, it must see both the template definition (not just a declaration) and the template type used to instantiate the template. Also remember that C++ compiles files individually. When the Array.h header is #included in main, the template class definition is copied into main.cpp. When the compiler sees that we need two template instances, Array<int>, and Array<double>, it will instantiate these, and compile them as part of main.cpp. However, when it gets around to compiling Array.cpp separately, it will have forgotten that we need an Array<int> and Array<double>, so that template function is never instantiated. Thus, we get a linker error, because the compiler can’t find a definition for Array<int>::getLength() or Array<double>::getLength().

There are quite a few ways to work around this.

The easiest way is to simply put all of your template class code in the header file (in this case, put the contents of Array.cpp into Array.h, below the class). In this way, when you #include the header, all of the template code will be in one place. The upside of this solution is that it is simple. The downside here is that if the template class is used in many places, you will end up with many local copies of the template class, which can increase your compile and link times (your linker should remove the duplicate definitions, so it shouldn’t bloat your executable). This is our preferred solution unless the compile or link times start to become a problem.

If you feel that putting the Array.cpp code into the Array.h header makes the header too long/messy, an alternative is to rename Array.cpp to Array.inl (.inl stands for inline), and then include Array.inl from the bottom of the Array.h header. That yields the same result as putting all the code in the header, but helps keep things a little cleaner.

Other solutions involve #including .cpp files, but we don’t recommend these because of the non-standard usage of #include.

Another alternative is to use a three-file approach. The template class definition goes in the header. The template class member functions goes in the code file. Then you add a third file, which contains all of the instantiated classes you need:

templates.cpp:

The “template class” command causes the compiler to explicitly instantiate the template class. In the above case, the compiler will stencil out both Array<int> and Array<double> inside of templates.cpp. Because templates.cpp is inside our project, this will then be compiled. These functions can then be linked to from elsewhere.

This method is more efficient, but requires maintaining the templates.cpp file for each program.


13.4 -- Template non-type parameters
Index
13.2 -- Function template instances

161 comments to 13.3 — Template classes

  • srt1104

    How is splitting of template classes done in real world programs?
    Because in all the three ways mentioned above, the implementation is there for anybody to steal.
    For instance, how is it done in C++ STL?

    • nascardriver

      You cannot hide template definitions unless you explicitly instantiate them in the source file, which makes them a lot less useful.

      You have the header files of your standard library on your system, you can explore them.

  • chai

    "The downside here is that if the template class is used in many places, you will end up with many local copies of the template class, which can increase your compile and link times... "
    I am a bit lost with this.
    My understanding is that the compiler will stencil out one version per data type ( or class), regardless of how many time you use it. Why would using template class in many places will create many local copies?

    Thanks.

    • nascardriver

      If you only include the header in 1 file, you're right. The compiler stencils out 1 version per type no matter how often you use it.

      But the compiler only compiles individual files. If you have 2 files that both include the header, the header will be copied to both files and the compiler compiles the header twice (Once in each file).

  • Dimi

    The template instatiations can be added directly at the end of "array.cpp" eliminating the need for an extra file (templates.cpp).
    I feel this is the simplest approach.

  • For a future addition/reference: at the end of this 13.3 section I would add a subsection on "Friend functions of template class" taking the operator<< overloading as example.

    Thank you very much for this tutorial. It's extremely helpful.

  • allright

    "In order for the compiler to use a template, it must see both the template definition (not just a declaration) and the template type used to instantiate the template."

    which solution is used for std::vector ?

  • F. Wu

    "If you feel that putting the Array.cpp code into the Array.h header makes the header too long/messy, an alternative is to rename Array.cpp to Array.inl (.inl stands for inline), and then include Array.inl from the bottom of the Array.h header."

    Should I include the .h file in the .inl file?

  • Amir

    "Other solutions involve #including .cpp files, but we don’t recommend these because of the non-standard usage of #include." what about if we put inside precompiled hfile?

  • salah

    When defining out functions below the .h file template functions are not subject to one definition rule like normal functions?

  • koe

    It is just as common to put function declarations in headers as it is to put member function definitions in code files. So, function templates have the same problem as class templates. According to my tests the 'write function template definitions inline in the header', and '#include the .inl file' solutions both work for function templates. Is there a 'three file solution' equivalent for function templates? So instead of 'template class', something like 'template function' (I tested and 'template function' isn't real).

  • Tushar

    If I am making a double linked list and thus creating a node class then
    1. Which is better to use class or struct?
    2. If I am using a class and making a node :

    What type should I use ?? T or Node[class name]?

    • nascardriver

      If the `Node` has member functions, `class`, otherwise `struct`.

      > "Node[class name]"
      I don't understand what you mean. Can you provide an example?

  • Kwonk

    Hi, you (probably) made a mistake in the last Array.h:

  • P. Lam

    Howdy,
    So I tried reimplementing the Array<T> template class to instantiate using an initializer-list instead. The problem is that printed values for doubles come out as integers (right side). Do you know why this might be?
    (For the record, the example provided prints correctly)

    1   1
    2   2
    3   3
    4   4
    5   5

    • nascardriver

      Your array is storing double correctly, they only appear the same as integers when you print them. If you store numbers that aren't integers, you'll see that it's working.

Leave a Comment

Put all code inside code tags: [code]your code here[/code]