Chapter 5
IN THIS CHAPTER
Creating class templates
Using parameters in templates
Deriving with templates
Creating function templates
If C++ programming has any big secret, it would have to be templates, which are entities that define either a family of functions or a family of classes. Templates seem to be the topic that beginning programmers strive to understand because they’ve heard about them and seem to think that templates are the big wall over which they must climb to ultimately become The C++ Guru. This chapter begins by showing you that creating and using basic templates need not be difficult.
The one thing you can be certain of is that knowing how to work with templates will open your abilities to a whole new world, primarily because the entire Standard C++ Library is built around templates. Further, understanding templates can help you understand all that cryptic code that you see other people posting on the Internet. This chapter also helps you understand how to access, use, and extend standard templates.
This section begins by showing you just how simple templates are to understand. It begins with a discussion of type, which you can skip if you already understand the concept fully. The next section deals with the need for creating templates based on a type.
This section begins with the OldGasStation
class. Remember, a class is a type. You can declare variables of the type. Thus you can declare a variable of type OldGasStation
called, for example HanksGetGas
. You can also create another variable of type OldGasStation
; maybe this one would be called FillerUp
. And, of course, you can create a third one; this one might be called GotGasWeCanFillIt
. Each of these variables, HanksGetGas
, FillerUp
, and GotGasWeCanFillIt
, are each instances of the type (or class) OldGasStation
.
In the same way, you can make some instances of an existing type, say int
. You can name one CheckingAccountBalance
and another BuriedTreasuresFound
. Each of these is an instance of the type int
Although int
isn’t a class, it is a type.
Think about this so far: You have the two different types available to you. One is called OldGasStation
and the other is called int
. One of these is a type you make; the other is built into C++.
Focus on the one you create, OldGasStation
. This is a type that you create by declaring it in your application when you write the code. The compiler takes your declaration and builds some data inside the resulting application that represents this type. After the application starts, the type is created, and it doesn’t change throughout the course of the application.
Suppose that you have a class called MyHolder
. This class will hold some integers. Nothing special, but it looks like this:
class MyHolder {
public:
int first;
int second;
int third;
int sum() {
return first + second + third;
}
};
This class is easy to use; you just create an instance of it and set the values of its members. But remember: After the application is running, the class is a done deal. But at runtime, you’re free to create new instances of this class. For example, the following code creates ten instances of the class, calls sum()
, and prints the return value of sum()
:
MyHolder *hold;
int loop;
for (loop = 0; loop < 10; loop++) {
hold = new MyHolder;
hold->first = loop * 100;
hold->second = loop * 110;
hold->third = loop * 120;
cout << hold->sum() << endl;
delete hold;
}
This code creates an instance at runtime, does some work with it, and then deletes the instance. It then repeats this process for a total of ten times. Instances (or variables) are created, changed, and deleted — all at runtime. But the class is created at compile time.
Suppose you’re coding away and you discover that this class MyHolder
is handy, except it would be nice if you had a version of it that holds floats
instead of ints
. You could create a second class just like the first that uses the word float
instead of int
, like this:
class AnotherHolder {
public:
float first;
float second;
float third;
float sum() {
return first + second + third;
}
};
This class works the same way as the previous class, but it stores three float
types instead of int
types. But you can see, if you have a really big class, that this method would essentially require a lot of copying and pasting followed by some search-and-replacing — in other words, busywork. But you can minimize this busywork by using templates. Instead of typing two different versions of the class, type one version of the class that you can, effectively, modify when you need different versions of the class. Look at this code:
template <typename T>
class CoolHolder {
public:
T first;
T second;
T third;
T sum() {
return first + second + third;
}
};
Think of this templated class as a rule for a single class that does exactly what the previous two classes did. (Ignore the template declaration, template <typename T>
, for now; it’s explained in the “Understanding the template keyword” section of the chapter.) In this rule is a placeholder called T
that is a placeholder for a type. Imagine this set of code; then remove the first line and replace all the remaining T
’s with the word int
. If you did that, you would end up with this:
class CoolHolder {
public:
int first;
int second;
int third;
int sum() {
return first + second + third;
}
};
This is, of course, the same as the earlier class called MyHolder
, just with a different name. Now imagine doing the same thing but replacing each T
with the word float
. You can probably see where we’re going with this. Here it is:
class CoolHolder {
public:
float first;
float second;
float third;
float sum() {
return first + second + third;
}
};
Again, this is the same as the earlier class called AnotherHolder
, but with a different name. That’s what a template does: It specifies a placeholder for a class. But it doesn’t actually create a class … yet. You have to do one more thing to tell the compiler to use this template to create a class. You accomplish this task by writing code to create a variable or by using the class somehow. Look at this code:
CoolHolder<int> IntHolder;
IntHolder.first = 10;
IntHolder.second = 20;
IntHolder.third = 30;
This code tells the compiler to create a class by replacing every instance of T
with int
in the CoolHolder
template. In other words, the compiler creates a class named CoolHolder<int>
. These four lines of code create an instance of CoolHolder<int>
called IntHolder
and set its properties. The computer creates this class at compile time. Remember, types are created at compile time, and this example code is no exception to this rule.
The previous section tells how to put a template together based on your requirements, so now it’s time to put that template into action. The CoolHolder
example, shown in Listing 5-1, contains a complete application that uses the CoolHolder
template.
LISTING 5-1: Using Templates to Create Several Versions of a Class
#include <iostream>
using namespace std;
template <typename T>
class CoolHolder {
public:
T first;
T second;
T third;
T sum() {
return first + second + third;
}
};
int main() {
CoolHolder<int> IntHolder;
IntHolder.first = 10;
IntHolder.second = 20;
IntHolder.third = 30;
CoolHolder<int> AnotherIntHolder;
AnotherIntHolder.first = 100;
AnotherIntHolder.second = 200;
AnotherIntHolder.third = 300;
CoolHolder<float> FloatHolder;
FloatHolder.first = 3.1415;
FloatHolder.second = 4.1415;
FloatHolder.third = 5.1415;
cout << IntHolder.first << endl;
cout << AnotherIntHolder.first << endl;
cout << FloatHolder.first << endl;
CoolHolder<int> *hold;
for (int loop = 0; loop < 10; loop++) {
hold = new CoolHolder<int>;
hold->first = loop * 100;
hold->second = loop * 110;
hold->third = loop * 120;
cout << hold->sum() << endl;
delete hold;
}
return 0;
}
When you run this application, you see a bunch of results from calls to sum()
:
10
100
3.1415
0
330
660
990
1320
1650
1980
2310
2640
2970
Look closely at the code. Near the beginning is the same template shown previously. Remember that the compiler doesn’t create a type for this template. Instead, the compiler uses it as a rule to follow to create additional types. That is, the code indeed serves as a template for other types, thus its name.
It’s time to consider the template
keyword in the template definition. Here’s the first line of the template shown in Listing 5-1:
template <typename T>
All this means is that a template class follows and it has a type with a placeholder called T
. Inside the class, anywhere a T
appears, the compiler replaces it with the typename
defined by T
, such as int
or float
.
To use the template, you declare several variables of types based on this template. Here’s one such line:
CoolHolder<int> IntHolder;
This line declares a variable called IntHolder
. For this variable, the compiler creates a type called CoolHolder<int>
, which is a type based on the CoolHolder
template, where T
is replaced by int
. Here’s another line where the code declares a variable:
CoolHolder<int> AnotherIntHolder;
This time, the compiler doesn’t have to create another type because it just created the CoolHolder<int>
type earlier. But again, this line uses the same type based on the template, where T
is replaced by int
.
The example in Listing 5-1 creates another class based on the CoolHolder
template. It’s instantiated at FloatHolder
:
CoolHolder<float> FloatHolder;
When the compiler sees this line, it creates another type by using the template, and it replaces T
with the word float
. So in this case, the first
, second
, and third
properties of FloatHolder
each hold a floating-point number. Also, the sum()
method returns a floating-point number.
The following line uses the CoolHolder<int>
type created earlier to declare a pointer to CoolHolder<int>
, hold
. Yes, you can do that; pointers are allowed:
CoolHolder<int> *hold;
Then the code that follows cycles through a loop to create new
instances of type CoolHolder<int>
by using the line
hold = new CoolHolder<int>;
The code accesses the members using the pointer notation, ->
, like so:
hold->first = loop * 100;
The previous section discusses template basics. However, templates are more flexible and powerful than you might imagine. The following sections discuss how you can move beyond the basics to add flexibility to your code.
In the earlier days of templates and C++, the rule was that you had to put the method code for a class template inside the template itself; you couldn’t put a forward declaration in the template and then put the function code outside the template, as you could do with classes. However, the ANSI standard changed this situation and made putting the code outside the template legal. (It’s important to know this fact because you may encounter convoluted-looking code that puts everything inside.) The ImFree
example, shown in Listing 5-2, shows you how to separate the methods from the template.
LISTING 5-2: Separating a Template from Function Code
#include <iostream>
using namespace std;
template <typename T>
class ImFree {
protected:
T x;
public:
T& getx();
void setx(T);
};
template <typename T>
T &ImFree<T>::getx() {
return x;
}
template <typename T>
void ImFree<T>::setx(T newx) {
x = newx;
}
int main() {
ImFree<int> separate;
separate.setx(10);
cout << separate.getx() << endl;
return 0;
}
Look closely at one of the methods:
template <typename T>
T &ImFree<T>::getx() {
return x;
}
The first line is the same as the first line of the template definition. It’s just the word template
followed by the parameter in angle brackets.
The next line looks almost like you might expect it to. With classes, you put the function prototype, adding the class name and two colons before the function name itself, but after the return type. Here you do that, too; the sticky part is how you write the template name. You don’t just give the name; instead, you follow the name with two angle brackets, with the parameter inside, like this: T &ImFree<T>::getx()
. Note the <T>
part.
ImFree<int> separate;
However, you can create the class with some other class:
ImFree<SomeOtherClass> separate;
When you do that, you don’t really want to return just an instance from the function, as in
T& getx() {
return x;
}
Returning an instance copies the existing instance rather than return the existing instance. Using a reference means that the class user doesn’t have to do any bizarre coding. For example, displaying the output using a cout
is rather straightforward:
cout << separate.getx() << endl;
You can include static members in a template, but you need to be careful when you do so. Remember that all instances of a class share a single static member of the class. You can think of the static member as being a member of the class itself, whereas the nonstatic members are members of the instances.
Now, from a single template, you can potentially create multiple classes. This means that to maintain the notion of static members, you need to either get creative with your rules or make life easy by just assuming that each class based on the template gets its own static members. The easy way is exactly how this process works.
LISTING 5-3: Using Static Members in a Template
#include <iostream>
using namespace std;
template <typename T>
class Electricity {
public:
static T charge;
};
template <typename T>
T Electricity<T>::charge;
int main() {
Electricity<int>::charge = 10;
Electricity<float>::charge = 98.6;
Electricity<int> inst;
inst.charge = 22;
cout << Electricity<int>::charge << endl;
cout << Electricity<float>::charge << endl;
cout << inst.charge << endl;
return 0;
}
Note how you declare storage for the static member; it’s the two lines in between the template and main()
. You supply the same template header you would for the class and then specify the static member type (in this case, T
, which is the template parameter). Next, you refer to the static member by using the usual classname::member name
syntax. But remember that the class name gets the template parameter in angle brackets after it.
This code creates two classes based on the templates Electricity <int>
and Electricity <float>
. Each of these classes has its own instance of the static member; the <int>
version contains 10
and the <float>
version contains 98.6
. Then, just to show that there’s only a single static member per class, the code creates an instance of Electricity<int>
and sets its static member to 22
. Using a cout
statement, you can see that the output for the two Electricity<int>
lines are the same and the Electricity<float>
output is different.
A template consists of a template name followed by one or more parameters inside angle brackets. Then comes the class definition. When you create a new class based on this template, the compiler obliges by making a substitution for whatever you supply as the parameter. Focus your eyes on this template:
template <typename T>
class SomethingForEveryone {
public:
T member;
};
Not much to it: It’s just a simple template with one member called, conveniently enough, member
. However, notice in particular what’s inside the angle brackets. This is the parameter: typename T
. As with parameters in a function, the first is the type of the parameter (typename
), and the second is the name of the parameter (T
). Previous sections have illustrated how this all works. However, you don’t always use typename
; you can use other types, as described in the sections that follow.
It turns out there’s more to using parameters than meets the computer screen. You can put many more keywords inside the parameter beyond just the boring word typename
. For example, suppose you have a class that does some comparisons to make sure that a product isn’t too expensive for a person’s budget. Each person would have several instances of this class, one for each product. This class would have a constant in it that represents the maximum price the person is willing to spend.
But there’s a twist: Although you would have multiple instances of this class, one for each product the person wants to buy, the maximum price would be different for each person. You can create such a situation with or without templates. Here’s a way you can do it with a template:
template <int MaxPrice>
class PriceController {
public:
int Price;
void TestPrice()
{
if (Price > MaxPrice)
{
cout << "Too expensive" << endl;
}
}
};
In this case, the template parameter isn’t a type at all — it’s an integer value, an actual number. Then, inside the class, you use that number as a constant. As you can see in the TestPrice
function, the code compares Price
to the MaxPrice
constant. So this time, instead of using T
for the name of the template parameter, the code views it as a value, not a type. The PriceController
example, shown in Listing 5-4, contains a complete example that uses this template.
LISTING 5-4: Using Different Types for a Template Parameter
#include <iostream>
using namespace std;
template <typename T>
class SomethingForEveryone {
public:
T member;
};
template <int MaxPrice>
class PriceController {
public:
int Price;
void TestPrice(string Name)
{
if (Price > MaxPrice)
{
cout << Name << " too expensive!" << endl;
}
}
};
int main() {
SomethingForEveryone<int> JustForMe;
JustForMe.member = 2;
cout << JustForMe.member << endl;
const int FredMaxPrice = 30;
PriceController<FredMaxPrice> FredsToaster;
FredsToaster.Price = 15;
FredsToaster.TestPrice("Toaster");
PriceController<FredMaxPrice> FredsDrawingSet;
FredsDrawingSet.Price = 45;
FredsDrawingSet.TestPrice("Drawing set");
const int JulieMaxPrice = 60;
PriceController<JulieMaxPrice> JuliesCar;
JuliesCar.Price = 80;
JuliesCar.TestPrice("Car");
return 0;
}
Each person gets a different class that reflects the maximum price they’re willing to pay. You can see that Fred gets a class called PriceController <FredMaxPrice>
. Julie, however, gets a class called PriceController <JulieMaxPrice>
. And remember, these really are different classes. The compiler created two different classes, one for each item passed in as a template parameter. Also notice that the parameters are constant integer values. FredMaxPrice
is a constant integer holding 30
. JulieMaxPrice
is a constant integer holding 60
.
For the first one, PriceController <FredMaxPrice>
, the code creates two instances. For the second one, PriceController <JulieMaxPrice>
, the code creates one instance. In all instances, the code sets the price of the item and then calls TestPrice()
with the item name. If the item is too expensive, the PriceController
outputs a special message. Here’s the output from this example:
2
Drawing set too expensive!
Car too expensive!
error: 'float' is not a valid type for a template non-type parameter
You’re not limited to only one parameter when you create a template. For example, the Standard C++ Library has a template called map
. The map
template works like an array, but instead of storing things based on an index as you would in an array, you store them based on a key and value pair. To retrieve an item from map
, you specify the key, and you get back the value. When you create a class based on the map
template, you specify the two types map
will hold, one for the key and one for the value. These are types, rather than objects or instances. After you specify the types, the compiler creates a class, and inside that class you can put the instances.
To show how this works, instead of using the actual map
template, the following example creates a template that works similarly to a map
. Instances of classes based on this template will hold only as many items as you specify when you create the class, whereas a real map
doesn’t have any limitations beyond the size of the computer’s memory. The MultipleParameters
example, shown in Listing 5-5, demonstrates an alternative map
template.
LISTING 5-5: Using Multiple Parameters with Templates
#include <iostream>
using namespace std;
template<typename K, typename V, int S>
class MyMap {
protected:
K key[S];
V value[S];
bool used[S];
int Count;
int Find(K akey) {
int i;
for (i=0; i<S; i++) {
if (used[i] == false)
continue;
if (key[i] == akey) {
return i;
}
}
return -1;
}
int FindNextAvailable() {
int i;
for (i=0; i<S; i++) {
if (used[i] == false)
return i;
}
return -1;
}
public:
MyMap() {
int i;
for (i=0; i<S; i++) {
used[i] = false;
}
}
void Set(K akey, V avalue) {
int i = Find(akey);
if (i > -1) {
value[i] = avalue;
}
else {
i = FindNextAvailable();
if (i > -1) {
key[i] = akey;
value[i] = avalue;
used[i] = true;
}
else
cout << "Sorry, full!" << endl;
}
}
V Get(K akey) {
int i = Find(akey);
if (i == -1)
return 0;
else
return value[i];
}
};
int main() {
MyMap<char,int,10> mymap;
mymap.Set('X',5);
mymap.Set('Q',6);
mymap.Set('X',10);
cout << mymap.Get('X') << endl;
cout << mymap.Get('Q') << endl;
return 0;
}
When you run this application, you see this output:
10
6
This listing is a good exercise — not just for your fingers as you type it in, but for understanding templates. Notice the first line of the template definition:
template<typename K, typename V, int S>
This template takes three parameters. The first is a type, K
, used as the key for map
. The second is a type, V
, used as the value for map
. The final is S
, and it’s not a type. Instead, S
is an integer value; it represents the maximum number of pairs that map
can hold.
The methods that follow allow the user of any class based on this map
to add items to map
and retrieve items from map
. The example currently lacks functions for removing items; you might think about ways you could add such functions. You might even look at the header files for the map
template in the Standard C++ Library to see how the designers of the library implemented a removal system.
Starting with C++ 11, you can use non-type parameters to define a template. The use of non-type parameters makes it possible to create templates that accept some interesting types of input, yet are more specific in some ways than general template types. Previous sections have shown how to use types for templates; here are some common non-types used for templates:
One of the more interesting non-type parameters is an enumeration. You can use the enumeration to enforce things like kind selection or for verifying that a particular kind is in use. It also comes in handy for comparisons. The NonTypeParm
example, shown in Listing 5-6, demonstrates techniques you can use when working with templates that rely on an enumeration.
LISTING 5-6: Using an Enumeration in a Template
#include <iostream>
using namespace std;
enum StoreType {
Red,
Blue,
Green
};
template <typename V>
struct StoreOut {
V Value;
StoreType Kind;
};
template <StoreType K, typename V>
class StoreIt {
protected:
V Value;
StoreType Kind = K;
public:
StoreIt() {
Value = 0;
}
StoreIt(V value) {
Value = value;
}
StoreOut<V>& getx();
void setx(StoreType, V);
string KindToString();
};
template <StoreType K, typename V>
StoreOut<V>& StoreIt<K, V>::getx() {
StoreOut<V>* Out = new StoreOut<V>();
Out->Value = Value;
Out->Kind = Kind;
return *Out;
}
template <StoreType K, typename V>
void StoreIt<K, V>::setx(StoreType newT, V newV) {
Value = newV;
Kind = newT;
}
template <StoreType K, typename V>
string StoreIt<K, V>::KindToString(){
switch (Kind) {
case Blue: return "Blue";
case Green: return "Green";
case Red: return "Red";
}
return "Not Found";
}
int main() {
StoreIt<StoreType::Blue, int> Test;
Test.setx(StoreType::Red, 5);
StoreIt<StoreType::Red, int> Test2(6);
cout << Test1.KindToString() << "\t" <<
Test1.getx().Value << endl;
if (Test1.KindToString() != "Blue")
cout << "Test1 storage type changed." << endl;
if (Test1.KindToString() == Test2.KindToString())
cout << "Test1 and Test2 are of equal types." << endl;
return 0;
}
This example stores two values: a storage type and a value. The StoreType
enumeration contains the only values you can use as input: Red
, Blue
, and Green
. You provide one of these values when creating the initial object and again when setting a value using setx()
. Using this approach limits the number of object types that a caller can create to those that you expect.
Because the example stores two values, it needs a method for returning two values, which is the purpose of the StoreOut
structure. The getx()
method uses it to return data to the caller.
The actual StoreIt
class declaration protects the two variables: Value
, which can be of any type; and Kind
, which must be a StoreType
enumeration value. It also provides three methods: getx()
, which returns a StoreOut
structure;, setx()
, which accepts the StoreType
and value used to set the object values; and KindToString()
, which provides the utility service of changing a StoreType
value to a string for output. The setx()
and getx()
methods work much the same as their counterparts in Listing 5-2. The KindToString()
method uses a simple switch
statement to perform the required translation.
The code in main()
creates a StoreIt
object, Test1
, stores data in it, and then displays the values onscreen. The main()
code also demonstrates some of the ways in which you might use this template class. For example, you could determine whether the Kind
of Test1
has changed. You could also determine whether Test1
and Test2
are the same Kind
of object. Notice that Test1
and Test2
use different constructor types so that the Kind
is created as part of the template, but Value
is either a default value of 0
or a specific value of 6
in this case. Here’s what you see as output:
Red 5
Test1 storage type changed.
Test1 and Test2 are of equal types.
If there’s a template that you use with particular parameters repeatedly, often just using typedef
is the easiest way to go. For example, if you have a template like this
template <typename T>
class Cluck {
public:
T Chicken;
};
and you use Cluck <int>
repeatedly, employ the following:
typedef Cluck<int> CluckNum;
Then, anytime you need to use Cluck<int>
, you can use CluckNum
instead. Here’s how:
int main() {
CluckNum foghorn;
foghorn.Chicken = 1;
return 0;
}
If you think about it, you can involve a class template in a derivation in at least three ways. You can:
If you want to find out about these techniques, read the following sections.
You can derive a class from a template, and in doing so, specify the parameters for the template. In other words, think of the process like this:
Suppose you have a template called MediaHolder
, and the first two lines of its declaration look like this:
template <typename T>
class MediaHolder
Then you could derive a class from a particular case of this template, as in this header for a class:
class BookHolder : public MediaHolder<Book>
Here you create a new class (based on MediaHolder
) called MediaHolder<Book>
. From that class, you derive a final class, BookHolder
. The ClassFromTemplate
example, shown in Listing 5-7, is an example of the class MediaHolder
.
LISTING 5-7: Deriving a Class from a Class Template
#include <iostream>
using namespace std;
class Book {
public:
string Name;
string Author;
string Publisher;
Book(string aname, string anauthor, string apublisher) :
Name(aname), Author(anauthor), Publisher(apublisher){}
};
class Magazine {
public:
string Name;
string Issue;
string Publisher;
Magazine(string aname, string anissue,
string apublisher) :
Name(aname), Issue(anissue), Publisher(apublisher){}
};
template <typename T>
class MediaHolder {
public:
T *array[100];
int Count;
void Add(T *item)
{
array[Count] = item;
Count++;
}
MediaHolder() : Count(0) {}
};
class BookHolder : public MediaHolder<Book> {
public:
enum GenreEnum
{childrens, scifi, romance,
horror, mainstream, hownotto};
GenreEnum GenreOfAllBooks;
};
class MagazineHolder : public MediaHolder<Magazine> {
public:
bool CompleteSet;
};
int main() {
MagazineHolder dl;
dl.Add(new Magazine(
"Dummies Life", "Vol 1 No 1", "Wile E."));
dl.Add(new Magazine(
"Dummies Life", "Vol 1 No 2", "Wile E."));
dl.Add(new Magazine(
"Dummies Life", "Vol 1 No 3", "Wile E."));
dl.CompleteSet = false;
cout << dl.Count << endl;
BookHolder bh;
bh.Add(new Book(
"Yellow Rose", "Sandy Shore", "Wile E."));
bh.Add(new Book(
"Bluebells", "Sandy Shore", "Wile E."));
bh.Add(new Book(
"Red Tulip", "Sandy Shore", "Wile E."));
bh.GenreOfAllBooks = BookHolder::childrens;
cout << bh.Count << endl;
return 0;
}
When you run this example, you see the magazine count of 3
first, and the book count of 3
second.
A template doesn’t have to be at the absolute top of your hierarchy; a template can be derived from another class that’s not a template. When you have a template and the compiler creates a class based on this template, the resulting class will be derived from another class. For example, suppose you have a class called SuperMath
that isn’t a template. You could derive a class template from SuperMath
. The TemplateFromClass
example, shown in Listing 5-8, demonstrates how you can do this.
LISTING 5-8: Deriving a Class Template from a Class
#include <iostream>
using namespace std;
class SuperMath {
public:
int IQ;
};
template <typename T>
class SuperNumber : public SuperMath {
public:
T value;
T &AddTo(T another) {
value += another;
return value;
}
T &SubtractFrom(T another) {
value -= another;
return value;
}
};
void IncreaseIQ(SuperMath &inst) {
inst.IQ++;
}
int main() {
SuperNumber<int> First;
First.value = 10;
First.IQ = 206;
cout << First.AddTo(20) << endl;
SuperNumber<float> Second;
Second.value = 20.5;
Second.IQ = 201;
cout << Second.SubtractFrom(1.3) << endl;
IncreaseIQ(First);
IncreaseIQ(Second);
cout << First.IQ << endl;
cout << Second.IQ << endl;
return 0;
}
The base class is called SuperMath
, and it has a member called IQ
. From SuperMath
, the example derives a class template called SuperNumber
that does some arithmetic. Later, the example adds an Incredible IQ-Inflating Polymorphism to use in this function:
void IncreaseIQ(SuperMath &inst) {
inst.IQ++;
}
Note what this function takes as a parameter: A reference to SuperMath
. Because the SuperNumber
class template is derived from SuperMath
, any class you create based on the template is, in turn, derived from SuperMath
. That means that if you create an instance of a class based on the template, you can pass the instance into the IncreaseIQ()
function. (Remember, when a function takes a pointer or reference to a class, you can instead pass an instance of a derived class.)
If you have a class template and you want to derive another class template from it, first you need to think about exactly what you’re doing; the process takes place when you attempt to derive a class template from another class template. Remember that a class template isn’t a class: A class template is a cookie-cutter that the compiler uses to build a class. If, in a derivation, the base class and the derived classes are both templates, what you really have is the following:
Now think about this: You create a class based on the base class template. Then you create a second class based on the second template. This process doesn’t automatically mean that the second class derives from the first class. Here’s why: From the first template, you can create many classes. When you create a class from the second template, which of those classes will it derive from?
To understand what’s happening here, look at the TemplateFromTemplate
example, shown in Listing 5-9. To keep the code simple, the example uses basic names for the identifiers. (Notice that we commented out one of the lines. If you’re typing this, type that line in, too, with the comment slashes, because you’ll try something in a moment.)
LISTING 5-9: Deriving a Class Template from a Class Template
#include <iostream>
using namespace std;
template <typename T>
class Base {
public:
T a;
};
template <typename T>
class Derived : public Base<T> {
public:
T b;
};
void TestInt(Base<int> *inst) {
cout << inst->a << endl;
}
void TestDouble(Base<double> *inst) {
cout << inst->a << endl;
}
int main() {
Base<int> base_int;
Base<double> base_double;
Derived<int> derived_int;
Derived<double> derived_double;
TestInt(&base_int);
TestInt(&derived_int);
TestDouble(&base_double);
TestDouble(&derived_double);
//TestDouble(&derived_int);
return 0;
}
Derived<int> derived_int;
You pass this variable to the function that takes a Base<int>
and it compiles. That means that Derived<int>
is derived from Base<int>
. In the same way, Derived<double>
is derived from Base<double>
. When you run this code, it outputs four numbers: two int
values and two double
values.
To see how Derived<int>
relies on Base<int>
, uncomment the line TestDouble(&derived_int)
. When you do this, and you try to compile the listing, you see this message:
error: cannot convert 'Derived<int>*' to 'Base<double>*' for argument '1' to 'void TestDouble(Base<double>*)'
The error message says you can’t pass a pointer to Derived<int>
to a function that takes a pointer to Base<double>
. That’s because Derived<int>
isn’t derived from Base<double>
.
template <typename T>
class Derived : public Base<T>
The clue here is that the Derived
template takes a template parameter called T
. Then the class based on the template is derived from a class called Base<T>
. But in this case, T
is the parameter for the Derived
template. See what happens if you create a class based on Derived
, such as this one:
Derived<int> x;
This line creates a class called Derived<int>
; then, in this case, the parameter is int
. Thus the compiler replaces the T
s so that Base<T>
in this case becomes Base<int>
. So Derived<int>
is derived from Base<int>
.
A function template is a function that allows the user to essentially modify the types used by a function as needed. For example, look at these two functions:
int AbsoluteValueInt(int x) {
if (x >= 0)
return x;
else
return -x;
}
float AbsoluteValueFloat(float x) {
if (x >= 0)
return x;
else
return -x;
}
To take the absolute value of an integer, you use the AbsoluteValueInt()
function. But to take the absolute value of a float, you instead use the AbsoluteValueFloat()
function. Of course, you need yet another function to support double
or other types. Instead of having a separate function for double
and a separate function for every other type, you can use a template like this:
template <typename T> T AbsoluteValue(T x) {
if (x >= 0)
return x;
else
return -x;
}
Now you need only one version of the function, which handles any numeric type, including double
. The users of the function can, effectively, create their own versions of the function as needed. For example, to use an integer version of this function, you put the typename, int
, inside angle brackets after the function name when calling the function:
int n = -3;
cout << AbsoluteValue<int>(n) << endl;
If you want to use the function for a float, you do this:
float x = -4.5;
cout << AbsoluteValue<float>(x) << endl;
Note the function template declaration. The real difference between the function template and a standard function is in the header:
template <typename T> T AbsoluteValue(T x)
Begin with the word template
, a space, and an open angle bracket (that is, a less-than sign). These characters are followed by the word typename
, a closing angle bracket (that is, a greater-than sign), and then an identifier name. Most people like to use the name T
(because it’s the first letter in type). At this point, you add the rest of the function header, which, taken by itself, looks like this:
T AbsoluteValue(T x)
cout << AbsoluteValue<float>(x) << endl;
it creates a function based on the template, substituting float
anywhere it sees T
. However, if you have two lines that use the same type, as in this:
cout << AbsoluteValue<float>(x) << endl;
cout << AbsoluteValue<float>(10.0) << endl;
the compiler creates only a single function for both lines.
If you really want to go out on a limb and create flexibility in your application, you can use overloading with a function template. Remember, overloading a function means that you create two different versions of a single function. What you’re doing is creating two separate functions that have different parameters (that is, either a different number of parameters or different types of parameters), but they share the same name. Look at these two functions found in the FunctionOverloadingAndTemplates
example:
int AbsoluteValue(int x) {
if (x >= 0)
return x;
else
return -x;
}
float AbsoluteValue(float x) {
if (x >= 0)
return x;
else
return -x;
}
These functions are an example of overloading. They take different types as parameters. (One takes an int
; the other takes a float
.) Of course, you could combine these functions into a template:
template <typename T> T AbsoluteValue(T x) {
if (x >= 0)
return x;
else
return -x;
}
There really isn’t any difference between the two examples. After all, you can use the following two lines of code either after the overloaded functions (without the type parameters) or after the function template:
cout << AbsoluteValue<int>(n) << endl;
cout << AbsoluteValue<float>(x) << endl;
In this case, n
is an int
and x
is a float
. However, the template is a better choice. If you use the overloaded form and try this code, you see an error:
cout << AbsoluteValue(10.5) << endl;
Even though 10.5
is a float
you see an error message like this:
error: call of overloaded 'AbsoluteValue(double)' is ambiguous
The message contains AbsoluteValue(double)
, which means that the compiler thinks that 10.5
is a double
, not a float
. You can pass a double
into either a function that takes an int
or a function that takes a float
. The compiler will just convert it to an int
or a float
, whichever it needs. Because the compiler thinks that 10.5
is a double
, it can pass the value to either overloaded function version. So that leaves you with a choice: You can cast it to a float
using (float)10.5
; declare it a float
using 10.5f
; or create a third overloaded version of the function, one that takes a double
.
Creating a template is easier than overcoming these sorts of errors. The second reason the template version is better: If you want a new type of the function, you don’t need to write another version of the function.
However, you can also overload a function template. The OverloadedFunctionTemplate
example, shown in Listing 5-10, contains an overloaded function template.
LISTING 5-10: Overloading a Function Template
#include <iostream>
using namespace std;
template <typename T> T AbsoluteValue(T x) {
cout << "(using first)" << endl;
if (x >= 0)
return x;
else
return -x;
}
template <typename T> T AbsoluteValue(T *x) {
cout << "(using second)" << endl;
if (*x >= 0)
return *x;
else
return -(*x);
}
int main() {
int n = -3;
cout << AbsoluteValue<int>(n) << endl;
float *xptr = new float(-4.5);
cout << AbsoluteValue<float>(xptr) << endl;
cout << AbsoluteValue<float>(10.5) << endl;
return 0;
}
Passing a pointer (as in the second call to AbsoluteValue()
in main()
), uses the second version of the template. And just to be sure which version gets used and at what time during application execution, the example contains a cout
line at the beginning of each function template. Here’s what you see as output:
(using first)
3
(using second)
4.5
(using first)
10.5
From the middle two lines, you can see that the computer did indeed call the second version of the template.
int main() {
int n = -3;
cout << AbsoluteValue(n) << endl;
float *xptr = new float(-4.5);
cout << AbsoluteValue(xptr) << endl;
cout << AbsoluteValue(10.5) << endl;
return 0;
}
This code replaces AbsoluteValue<int>(n)
with AbsoluteValue(n)
. When you run the modified code, you see the same output as when you run Listing 5-10.
When you write a template for a class, you can put function templates inside the class template. You simply declare a function template inside a class, as in the following found in the MemberFunctionTemplate
example:
class MyMath {
public:
string name;
MyMath(string aname) : name(aname) {}
template <typename T> void WriteAbsoluteValue(T x) {
cout << "Hello " << name << endl;
if (x >= 0)
cout << x << endl;
else
cout << -x << endl;
}
};
The WriteAbsoluteValue()
method is a template. It’s preceded by the word template
and a template
parameter in angle brackets. Then it has a return type, void
, the function name, and the function parameter.
When you create an instance of the class, you can call the method, providing a type as need be, as in the following:
int main() {
MyMath inst = string("George");
inst.WriteAbsoluteValue(-50.5);
inst.WriteAbsoluteValue(-35);
return 0;
}
In the first call, the function takes a double
(because, by default, the C++ compiler considers -50.5
a double
). In the second call, the function takes an integer. The compiler then generates two different forms of the function, and they both become members of the class.
'virtual' can only be specified for functions