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

93 comments to 13.3 — Template classes

  • Trong Nguyen Van

    For GCC compiler, it's ok to use only header file for template. But, with Visual Compiler, it doesn't allow to write template in only header file. So I think we must prefer to the solution separate definition of template and add instance as well at the end of cpp file.

  • Nick Eh 30

    Which solution use std::vector or std::array to solve the linker error problem?

    • Nick Eh 30

      Of the solutions you told us above.
      (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:

  • kossi

    In the three files workaround. Only #include "Array.cpp" in template.cpp (not #include "Array.h"); same as instantiating template in Array.cpp file.

    Example 1:
    Array.cpp

    Example 2: Three files solution
    template.cpp

    From isocpp.org

  • Jack

    Is there a way to template a class for some data types, but not for others? For example, your Array.h works well for numerical types, such as ints, doubles, floats, etc. However, there may be functions in there that do not play nice with other data types. I can't think of an example off the top of my head, perhaps having a function in the template that performs a mathematical operation on the values in the array? Something like:

    Array.h:

    main.cpp:

    This will output:

    3       3.5                                                                                                                                                
    2       2.5                                                                                                                                                
    1       1.5                                                                                                                                                
    0       0.5                                                                                                                                                
                                                                                                                                                              
                                                                                                                                                              
    6       7                                                                                                                                                  
    4       5                                                                                                                                                  
    2       3                                                                                                                                                  
    0       1

    However, keeping the same array.h, something like this in @main.cpp will not compile:

    Is there anyway to stop people from creating instances of our array class with specific data types to avoid the above? I know I can avoid this by simply not calling 'multiply' on an instance of Array with a string datatype, but I feel there could be something better than this.

    • References
      * <type_traits> - http://www.cplusplus.com/reference/type_traits/

  • Ritesh

    Hey Alex
    In chapter class codes and header files you said that normal functions when defined below the class are subject to one definition rule but template functions are not why is that
    also you said that in three file approach we explicitly instantiate the class but what if the type is a user defined data type, how will we handle that.
    and also are vectors and arrays implemented the same way you described with three files, because you said that they require maintaining the template.cpp for each program

    • Alex

      Template functions aren't real functions. They're templates for creating real functions, and they're governed by a different set of rules.
      There's no problem if the class is a user defined data type. Just have your third file #include the header of the user defined type and instantiate the template with the user defined type.
      In Visual Studio std::array and std::vector both look to be implemented entirely in the header file.

  • KKR

    Hi Alex!

    Can you explain now or add some notes regarding inheritance of template classes..I find the syntax quite hard.

    Thanks!

    • Alex

      Can you give me an example where you tried to inherit from a templated class and it didn't work like you expected (or the syntax was confusing)?

      I've certainly run into similar issues myself, but it's been long enough since I've done this that I don't have any examples at hand to work from.

  • Pashka2107

    Hi Alex,
    I've run into a big problem with this topic.

    I'm not sure, whether posting all my code will help (it's not so small), so will try to describe in details.

    I had four non-templated classes, two of them are just user-defined types (Fraction and Complex), third was a container of Fractions (named LinearEquation), and fourth was a container of LinearEquations (Matrix).

    Firstly all this worked with the Fraction only, and worked fine. Writing my Complex class was a reason to rewrite this containers in template manner. For that time, I've already split classes declarations and definitions, and, obviously, after compiling something related, like

    ..., I've got a LNK2019 error. Then I tried to handle it with your methods, but the error were not resolving. I then was seeking through Stackoverlow and other resourses, but without result. Then I've just tried to return definitions back into .h files, even right under declarations, but nothing of this helped.

    main() :

    definition of this constructor in Matrix.h :

    Same definition in LinearEquation.h :

    Linker error:
    unresolved external symbol "class std::basic_ostream<char,struct std::char_traits<char> > & __cdecl operator<<(class std::basic_ostream<char,struct std::char_traits<char> > &,class LinearEquation<class Fraction> const &)" (??6@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@std@@AAV01@ABV?$LinearEquation@VFraction@@@@@Z) in function "public: virtual class std::basic_ostream<char,struct std::char_traits<char> > & __thiscall Matrix<class Fraction>::print(class std::basic_ostream<char,struct std::char_traits<char> > &)const " (?print@?$Matrix@VFraction@@@@UBEAAV?$basic_ostream@DU?$char_traits@D@std@@@std@@AAV23@@Z)

    Can you help with that? I can post all code if needed.
    Sorry for bad english and thanks in advance.

    • nascardriver

      Hi Pashka!

      It looks like you're overriding the << operator in LinearEquation but never defining it.
      Try setting up a minimal example that will cause the error and post it, you might even find the problem on your own while writing the minimal example.

      • Pashka2107

        Hi there,
        Firstly, main() does not uses operator<< in this example.
        Secondly, actually, there is overloaded operator<< for LinearEquation as well as for Matrix.

        ...And despite all this, I tried to replace operators<< definitins back under its declarations (earlier to this they were lying in .h files, but outside of the class)
        and it suddenly compiled! That's the point where I can say, that I absolutly do not understand this language!

        For now I'm testing each function, if something go wrong, I'll post here, but do you have any ideas, how to make it work withous placing definitions right in class body? (Since it something I would like to avoid)

        Thanks for your reply.

      • Pashka2107

        Hi again,
        Maybe, my VS were bugging, since now Matrix/LinearEquation can be constructed without placing operator<< definition into class body. Many functions are working properly now, but how I can see, non of my friend operators isn't (really non). Maybe I do not know syntax for templated friend functions?
        For now, e.q. operator<< declaration for Matrix<T> looks like this:

        It seems to be okay, but something is wrong

        • Pashka2107

          I digged in it and found out, that syntax for friend functions of template classes is surprisingly tricky :

          ...and now it works as expected (I can now hold definition of this function outside of the class).
          In other word, compiler require define one more(or more, if needed) template parameter for operators overloaded as friends

  • Tim Hotehrsall

    I have been learning c++ in my spare time for around 3 years now, and only really got my head around templates recently.  I must admit to using Val's solution as the code that I am templating will never get reused in a million years, but would like to chip in that if you are going to do this then it must be right at the end of the cpp file.  I have no idea why, but stick it anywhere else and you will get linker errors galore.

    I have just reviewed some code that I wrote back in the early days that was in all honesty pretty procedural; and with polymorhism and templates I have now saved myself around 2K lines of code, got rid of 6 individual classes and a lot of major maintenance headaches.  This stuff clearly gets overused in certain circles, but the huge difference that it has made to the ease that my code can be maintained should not be overlooked.

    I would just like to thank you Alex for providing and updating this resource, because this is something that I could never have learned in an educational environment.  I am about 3 years in now and still have a fair bit to learn but this resource provides me with enough information to make something work and an excellent reference for properly understanding how it works in due course. Making something work is one thing but having an understanding of how it works takes considerably longer than any structured education course would make provision for.

  • TuttyFruty

    Firstly, sorry for long code part( in fact not that confusing ).
    I can't figure out why outside of the template class, template function declarations don't cause any multiple definition error.

    And also, even though I created objects Array<int>, when they are compiled in function1.cpp and function2.cpp, the getLength() function definition for <int> can not be seen by main.cpp. It's obvious that if it was, there were compile error.

    Thanks.

    -------Codes---------

    Main.cpp

    function1.cpp

    function2.cpp

    /* Basically a copy of your template class Array code */
    class.h

    • Alex

      Really interesting observation and question! I believe template functions are treated as inline. This means these functions don't have external linkage, so the two different definitions don't conflict during the link stage.

  • Benjamin

    Hi Alex!

    Since the chapter about object relations, I was wondering, whether there is a way to realize something like the metamorphism of an object. I am puzzled how to do so, but since I know about class templates, I found a way to at least partly accomplish it. Therefore, I decided to post here. Think about the following example:

    A frog is-an amphibian. But throughout its lifecycle it undergoes a metamorphism in which it develops from a larva to an adult. It is justified to give Larvas and Adults extra classes: A Larva breathes through gills, whereas adults have lungs. Salamanders are Amphibians too and can undergo the same metamorphism. It makes sense that both Salamanders and Frogs can inherit from the Larva or the Adult class. You would need an inheritance chain like "A Frog is-a Larva is-an Amphibian" and after metamorphism "A Frog is-an Adult is-an Amphibian". In an ideal case metamorphism means you change the inheritance of an object's underlying class during the lifecycle of the object. Here is what I did:

    While this basically works, I am unhappy about one thing: It doesn't make sense for the object larva to exist after the metamorphism happened. The only workaround I could think of was to use dynamic allocation so I can explicitly "delete" the larva. However, the main program doesn't know about the "delete". I created a dangling pointer! Also it leads to a counter-intuitive syntax: I have to create an Frog<Adult> that I cast my Frog<Larva> to. To the user it is obvious that this is an entirely new object. I would be happier with something like

    which conceals the cast a little bit and leaves us with the same name for the object.

    Do you have any suggestions how I can improve my approach? Or is my solution even entirely bad?

    • Alex

      I've never actually seen anyone use a template parameter for a base class. That's creative and unconventional!

      I don't think what you're trying to do is actually possible (have a Frog Larva dynamically change types).

      One way you could try to make it more transparent is to have a class named Frog that holds a pointer to a FrogLarva or FrogAdult (if they're both inherited from the same base class, this is possible). Frog can act as an interface, and you can have a metamorph() member function that instantiates a FrogAdult, copies whatever data is necessary, and deletes the FrogLarva. Since this would all happen internal to the class, the user of Frog wouldn't even see it.

  • Val

    Got it. Thank you for explanation!

  • Val

    I didn't use the three files approach. Instead what I did is this:

    Array.h:

    Array.cpp:

    And all works just fine. Why do we need a third file?

    Thank you very much!

    • Alex

      The goal is to separate the reusable parts from the program-specific parts. In the three-file solution, the Array.h and Array.cpp are totally reusable. Only the third file (templates.cpp) needs to be modified per program.

      With your solution, you'd need to potentially modify array.cpp for each program, which introduces risk of breaking either array.cpp or a dependent program.

      Also, what if you wanted an Array of some user-defined data structure? With your solution, you'd have to have Array.cpp include that user-defined data structure, which then means that user-defined data structure will be included in all your other programs that use Array.cpp. Not good. With the three-file solution, only the third file needs to include the user-defined data structure, which is fine because that file is program specific anyway.

Leave a Comment

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