Search

Enter your search terms

Submit search form

13.3 — Template classes

By Alex on June 16th, 2008 | last modified by Alex on August 6th, 2017

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:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

#ifndef INTARRAY_H

#define INTARRAY_H

#include <assert.h> // for assert()

classIntArray

{

private:

intm_length;

int*m_data;

public:

IntArray()

{

m_length=0;

m_data=nullptr;

}

IntArray(intlength)

{

assert(length>0);

m_data=newint[length];

m_length=length;

}

~IntArray()

{

delete[]m_data;

}

voidErase()

{

delete[]m_data;

// We need to make sure we set m_data to 0 here, otherwise it will

// be left pointing at deallocated memory!

m_data=nullptr;

m_length=0;

}

int&operator[](intindex)

{

assert(index>=0&&index<m_length);

returnm_data[index];

}

intgetLength(){returnm_length;}

};

#endif

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.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

#ifndef DOUBLEARRAY_H

#define DOUBLEARRAY_H

#include <assert.h> // for assert()

classDoubleArray

{

private:

intm_length;

double*m_data;

public:

DoubleArray()

{

m_length=0;

m_data=nullptr;

}

DoubleArray(intlength)

{

assert(length>0);

m_data=newdouble[length];

m_length=length;

}

~DoubleArray()

{

delete[]m_data;

}

voidErase()

{

delete[]m_data;

// We need to make sure we set m_data to 0 here, otherwise it will

// be left pointing at deallocated memory!

m_data=nullptr;

m_length=0;

}

double&operator[](intindex)

{

assert(index>=0&&index<m_length);

returnm_data[index];

}

intgetLength(){returnm_length;}

};

#endif

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.

template<classT>// This is a template class, the user will provide the data type for T

classArray

{

private:

intm_length;

T*m_data;

public:

Array()

{

m_length=0;

m_data=nullptr;

}

Array(intlength)

{

m_data=newT[length];

m_length=length;

}

~Array()

{

delete[]m_data;

}

voidErase()

{

delete[]m_data;

// We need to make sure we set m_data to 0 here, otherwise it will

// be left pointing at deallocated memory!

m_data=nullptr;

m_length=0;

}

T&operator[](intindex)

{

assert(index>=0&&index<m_length);

returnm_data[index];

}

// The length of the array is always an integer

// It does not depend on the data type of the array

intgetLength();// templated getLength() function defined below

};

template<typenameT>// member functions defined outside the class need their own template declaration

intArray<T>::getLength(){returnm_length;}// note class name is Array<T>, not Array

#endif

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.

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:

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:

1

2

3

4

5

6

7

8

9

10

// Ensure the full Array template definition can be seen

#include "Array.h"

#include "Array.cpp" // we're breaking best practices here, but only in this one place

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.

82 comments to 13.3 — Template classes

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

1

2

3

4

intmain()

{

Matrix<Fraction>m{{Fraction(1,2),2,3},{1,2,3}};

}

..., 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.

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.

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)

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:

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

1

2

template<classY>

friendostream&operator<<(ostream&,constMatrix<Y>&);

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

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.

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.

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.

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:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

#include <iostream>

#include <string>

classAmphibian

{

std::stringm_name;

protected:

Amphibian(std::stringname):m_name(name){}

virtual~Amphibian(){}

public:

conststd::string&getName(){returnm_name;}

virtualstd::stringgetType()=0;

};

// forward declaration of Frog class so that I can forward declare the metamorphism function in Larva class

template<classT>

classFrog;

// imagine meaningful classes instead of int for the bodyparts in the following

classAdult:publicAmphibian

{

intm_lung;

protected:

Adult(std::stringname="",intlung=0):Amphibian(name),m_lung(lung){}

virtual~Adult(){}

public:

virtualstd::stringgetType()override{return"an adult";}

};

classLarva:publicAmphibian

{

intm_gill;

protected:

Larva(std::stringname="",intgill=0):Amphibian(name),m_gill(gill){}

virtual~Larva(){}

public:

virtualstd::stringgetType()override{return"a larva";}

Frog<Adult>*metamorphism();

};

// Definition of Frog class as a class template that realizes different heritages depending on template parameter

// Probably I have to use class template specialization as soon as Adult and Larva have different structures.

// The metamorphism happens here. As just Larvas can do metamorphosis to Adults, I implemented it as a member function of Larva

Frog<Adult>*Larva::metamorphism()

{

// add some protection to include possible failure of the dynamic_cast

Frog<Adult>*a=newFrog<Adult>(dynamic_cast<Frog<Larva>*>(this));

std::cout<<"Metamorphism happened!n";

delete this;

returna;

}

intmain()

{

Frog<Larva>*larva=newFrog<Larva>("Kermit");

std::cout<<larva->getName()<<" the "<<larva->getOrder()<<" is "<<larva->getType()<<".n";

Frog<Adult>*adult=larva->metamorphism();

std::cout<<adult->getName()<<" the "<<adult->getOrder()<<" is now "<<adult->getType()<<".n";

return0;

}

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

1

2

3

Frog<Larva>*kermit=newFrog<Larva>("Kermit");

kermit->metamorphism();

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?

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.

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.