Yolinux.com Tutorial

C++ Templates

C++ function and class templates with examples of class inheritance.

Tutorial Table of Contents:

Related YoLinux Tutorials:

°Linux and C++

°C++ Unions & Structures

°C++ Templates

°C++ STL

°C++ STL Map

°C++ String Class

°C++ Singleton

°C++ Coding Style

°C++ XML Beans

°C/C++ Dynamic Memory

°C++ Memory Corruption

°C/C++ Signal Handling

°Jenkins CI

°YoLinux Tutorials Index




Free Information Technology Magazines and Document Downloads
TradePub link image


C++ Templates: Description

C++ has a mechanism called templates to reduce code duplication when supporting numerous data types. A C++ function or C++ class with functions which operates on integers, float and double data types can be unified with a single template function or class with functions which is flexible enough to use all three data types. This mechanism in C++ is called the "Template".

C++ templates fall under the category of "meta-programming" and auto code generation although one never sees the code generated.


C++ Template Functions:

In the following example we have a single template represent the code to square a number with the data types int, float and double.

Overloaded functions specified for each data type A single template to support all data types
#include <iostream>
using namespace std;

int square (int x)
{
  return x * x;
};

float square (float x)
{
  return x * x;
};

double square (double x)
{
  return x * x;
};

main()
{
   int    i, ii;
   float  x, xx;
   double y, yy;

   i = 2;
   x = 2.2;
   y = 2.2;

   ii = square(i);
   cout << i << ": " << ii << endl;

   xx = square(x);
   cout << x << ": " << xx << endl;

   yy = square(y);
   cout << y << ": " << yy << endl;
}
    
#include <iostream>
using namespace std;

template <class T>
inline T square(T x)
{
   T result;
   result = x * x;
   return result;
};



main()
{
   int    i, ii;
   float  x, xx;
   double y, yy;

   i = 2;
   x = 2.2;
   y = 2.2;

   ii = square<int>(i);
   cout << i << ": " << ii << endl;

   xx = square<float>(x);
   cout << x << ": " << xx << endl;

   // Explicit use of template
   yy = square<double>(y);
   cout << y << ": " << yy << endl;

   // Implicit use of template
   yy = square(y);
   cout << y << ": " << yy << endl;
}
    
Compile: g++ test1.cpp
Run: ./a.out
2: 4
2.2: 4.84
2.2: 4.84
Compile: g++ test2.cpp
Run: ./a.out
2: 4
2.2: 4.84
2.2: 4.84
2.2: 4.84
Note:
  • The code used in the overloaded C++ functions example above, is repeated for each data type. The templated function is specified once.

  • The templated type keyword specifier can be either "class" or "typename":
    • template<class T>
    • template<typename T>
    Both are valid and behave exactly the same. I prefer "typename".

  • The templated function works using either the explicit or implicit template expression square<int>(value) or square(value).

  • In the template definition, "T" represents the data type. The compiler will generate the type specific functions required. This results in a more compact code base which is easier to maintain.

  • The code and logic of the functions only has to be specified once in the templated function and the parameter "T" is used to represent the argument type.

  • The template declaration and definition must reside in the same file, typically an include header file.

  • A "C" macro function could also perform this purpose: #define square(x) (x * x)
    The advantage of the template is that it performs type checking while the macro does not.


Template Specialization:

The following is an extended example of the square function using template specialization to support type string which requires special handling.

Template specialization to support additional data types
#include <iostream>
using namespace std;

template <class T>
inline T square(T x)
{
   T result;
   result = x * x;
   return result;
};

// template specialization
template <>
string square<string>(string ss)
{
   return (ss+ss);
};

main()
{
   int i = 2, ii;
   string ww("Aaa");

   ii = square<int>(i);
   cout << i << ": " << ii << endl;

    cout << square<string>(ww) << endl;
}
    
Compile: g++ test.cpp
Run: ./a.out
2: 4
AaaAaa
Note that a specialized template was created to handle the string class.
Template specialization is used when a different and specific implementation is to be used for a specific data type.


Multiple Templated Types:

The following is an example of a template supporting multiple types:

#include <iostream>
using namespace std;

template <typename T, typename U>
void squareAndPrint(T x, U y)
{
   T result;
   U otherVar;
   cout << "X: " << x << " " <<  x * x << endl;
   cout << "Y: " << y << " " <<  y * y << endl;
};

main()
{
   int   ii = 2;
   float jj = 2.1;

   squareAndPrint<int,float>(ii, jj);
}
Compile: g++ test.cpp
Run: ./a.out
X: 2 4
Y: 2.1 4.41
A single type can only be specified once.


Non-type parameters:

Non-type template parameters provide the ability to pass a constant expression at compile time. The constant expression may also be an address of a function, object or static class member.
The following is an example of a template function supporting a non-type parameter "count" used for the array size and loop count:

#include <iostream>
using namespace std;

template <typename T, int count>
void loopIt(T x)
{
   T val[count];

   for(int ii=0; ii<count; ii++)
   { 
       val[ii] = x++;
       cout <<  val[ii] << endl;
   }
};

main()
{
   float xx = 2.1;

   loopIt<float,3>(xx);
}
Compile: g++ test.cpp
Run: ./a.out
2.1
3.1
4.1


Specify a default type parameter and default non-type parameter:

#include <iostream>
using namespace std;

template <typename T=float, int count=3>
T multIt(T x)
{
   for(int ii=0; ii<count; ii++)
   {
       x = x * x;
   }
   return x;
};

main()
{
   float xx = 2.1;

   cout << xx << ": " << multIt<>(xx) << endl;;
}
Compile: g++ test.cpp -std=c++0x
Run: ./a.out
2.1: 378.228
Note that multIt<> with no type specified, deferred to the default type: float.

[Potential Pitfall]: You must specify the compiler argument -std=c++0x to avoid the following error:
test.cpp:5:13: error: default template arguments may not be used in function templates without -std=c++0x or -std=gnu++0x


C++ Template Classes:

The concept of template functions can be extended to template classes.

  • A template class takes the form: template <class T> class MyTemplateClass { ... };
  • Class template specialization takes the form: template <> class MyTemplateClass <specific-data-type> { ... };

An example of a C++ template class applied to a 2 by 2 matrix:

File: Matrix2x2.hpp
#ifndef MATRIX_2X2_HPP__
#define MATRIX_2X2_HPP__

using namespace std;

/**
    m(11)  m(12)
    m(21)  m(22)
*/

template <class T>
class Matrix2x2
{
public:
   Matrix2x2(T m11, T m12, T m21, T m22);    //constructor
   Matrix2x2(T m[2][2]);
   Matrix2x2();

   int Add(Matrix2x2 x)
   int Multiply(Matrix2x2 x)
   void Print();
   T m[2][2];
};

template <class T>
Matrix2x2<T>::Matrix2x2(T _m11, T _m12, T _m21, T _m22)
{
   m[0][0] = _m11;
   m[0][1] = _m12;
   m[1][0] = _m21;
   m[1][1] = _m22;
}

template <class T>
Matrix2x2<T>::Matrix2x2(T _m)
{
   m[0][0] = _m[0][0];
   m[0][1] = _m[0][1];
   m[1][0] = _m[1][0];
   m[1][1] = _m[1][1];
}

template <class T>
Matrix2x2<T>::Matrix2x2()
{
   m[0][0] = 0;
   m[0][1] = 0;
   m[1][0] = 0;
   m[1][1] = 0;
}

template <class T>
Matrix2x2<T>::Add(Matrix2x2 _x)
{
    Matrix2x2<T> sum;
    sum.m[0][0] = m[0][0] + _x.m[0][0];
    sum.m[0][1] = m[0][1] + _x.m[0][1];
    sum.m[1][0] = m[1][0] + _x.m[1][0];
    sum.m[1][1] = m[1][1] + _x.m[1][1];
    return sum;
}

template <class T>
Matrix2x2<T>::Multiply(Matrix2x2 _x)
{
    Matrix2x2<T> sum;
    sum.m[0][0] = m[0][0] * _x.m[0][0] + m[0][1] * _x.m[1][0];
    sum.m[0][1] = m[0][0] * _x.m[0][1] + m[0][1] * _x.m[1][1];
    sum.m[1][0] = m[1][0] * _x.m[0][0] + m[1][1] * _x.m[1][0];
    sum.m[1][1] = m[1][0] * _x.m[0][1] + m[1][1] * _x.m[1][1];
    return sum;
}

template <class T>
Matrix2x2<T>::Print()
{
    cout << "|" << m[0][0] << "  " <<  m[0][1] << "|" << endl;
    cout << "|" << m[1][0] << "  " <<  m[1][1] << "|" << endl;
}

#endif
          

File: TestMatrix2x2.cpp
#include <iostream>

#include "Matrix2x2.hpp"

using namespace std;

int main(int argc, char* argv[])
{
    Matrix2x2<int> X(1,2,3,4);
    Matrix2x2<int> Y(5,6,7,8);

    cout << "X:" << endl;
    X.Print();

    cout << "Y:" << endl;
    Y.Print();

    Matrix2x2<int> A = X.Add(Y);
    cout << "A:" << endl;
    A.Print();

    Matrix2x2<int> B = X.Add(Y);
    cout << "B:" << endl;
    B.Print();
}
          
Compile: g++ -o TestMatrix2x2 TestMatrix2x2.cpp

Run test: ./TestMatrix2x2

X:
|1  2|
|3  4|
Y:
|5  6|
|7  8|
A:
|6  8|
|10  12|
B:
|19  22|
|43  50|


Static member variables of a template class:

Shown side by side with a regular C++ class for comparison.
Static member variable of a C++ class: Static member variable of a C++ template class:
#include <iostream>

using namespace std;


class XYZ
{
public:
    void putPri();
    static int ipub;
private:
    static int ipri;
};


void XYZ::putPri()
{
    cout << ipri++ << endl;
}

// Static variable initialization:
int XYZ::ipub = 1;
int XYZ::ipri = 1;

main()
{
    XYZ aaa;
    XYZ bbb;

    aaa.putPri();
    cout << aaa.ipub << endl;
    bbb.putPri();
}
#include <iostream>

using namespace std;

template <class T> 
class XYZ
{
public:
    void putPri();
    static T ipub;
private:
    static T ipri;
};

template <class T> 
void XYZ<T>::putPri()
{
    cout << ipri++ << endl;
}

// Static variable initialization:
template <class T> T XYZ<T>::ipub = 1;
template <class T> T XYZ<T>::ipri = 1.2;

main()
{
    XYZ<int> aaa;
    XYZ<float> bbb;

    aaa.putPri();
    cout << aaa.ipub << endl;
    bbb.putPri();
}
Compile: g++ staticTest.cpp
Run: ./a.out
Output:
1
1
2
Compile: g++ staticTemplateTest.cpp
Run: ./a.out
Output:
1
1
1.2
Note that each version or "specialization" (data type) of the class will have its own unique copy of the static member. All objects of that specialization will share the same static member. Static member variables are initialized for each specialization/type.


Template template parameters:

#include <iostream>
using namespace std;

template <template <typename T> typename U>
class Xyz
{
    ....
};


Template classes and Inheritance:

There are three forms of inheritance which may occur with C++ template classes:
  1. A templated class may be derived from a regular non-templated C++ base class
  2. A regular non-templated C++ class can be derived from a templated base class
  3. A templated class may be derived from another templated class


1) Example of a templated class derived from a regular non-templated C++ class:
File: Color.hpp (non-templated base class)
#ifndef COLOR_HPP__
#define COLOR_HPP__
#include <string>
enum eColor { none = 0, red, white, blue, yellow, green, black };

class Color
{
public:
    Color(eColor color);
    void setColor(eColor color);
    eColor getColor() { return mColor; };
    std::string getStrColor();

protected:
    eColor mColor;
};

Color::Color(eColor _color)
{
   mColor = _color;
}

void Color::setColor(eColor _color)
{
    mColor = _color;
}

std::string Color::getStrColor()
{
    switch(mColor)
    {
       case red:
           return "red";
       case white:
           return "white";
       case blue:
           return "blue";
       case yellow:
           return "yellow";
       case green:
           return "green";
       case black:
           return "black";
       case none:
       default:
           return "none";
    }
}
#endif
          

File: Circle.hpp (templated base class)
#ifndef CIRCLE_HPP__
#define CIRCLE_HPP__
#include <math.h>
#include <string>

#include "Color.hpp"

template <typename T>
class Circle : public Color
{
public:
    Circle(T centerX, T centerY, T radius, eColor color);
    Circle(T centerX, T centerY, T radius);
    Circle(T radius);
    Circle();

    T area();
    T circumference();
    T getX();
    T getY();
    T getRadius();

protected:
    T x;
    T y;
    T radius;
};

template <typename T>
Circle<T>::Circle(T _x, T _y, T _radius, eColor _color)
: Color(_color)
{
    x = _x;
    y = _y;
    radius = _radius;
}

template <typename T>
Circle<T>::Circle(T _x, T _y, T _radius)
: Color(none)
{
    x = _x;
    y = _y;
    radius = _radius;
}

template <typename T>
Circle<T>::Circle(T _radius)
: Color(none)
{
    x = static_cast<T>(0);
    y = static_cast<T>(0);
    radius = _radius;
}

template <typename T>
Circle<T>::Circle()
: Color(none)
{
    x = static_cast<T>(0);
    y = static_cast<T>(0);
    radius = static_cast<T>(1);
}

template <typename T>
T Circle<T>::area()
{
    return M_PI * radius * radius;
}

template <typename T>
T Circle<T>::circumference()
{
    return static_cast<T>(2) * M_PI * radius;
}
#endif
          

File: testCircle.cpp
#include <iostream>
#include "Circle.hpp"

using namespace std;

int main(int argc, char* argv[])
{
    Circle<float> circleA(0.0, 0.0, 10.0, white);
    cout << "Area: "  << circleA.area() << endl;
    cout << "Color: " << circleA.getStrColor() << endl;
}
          

Compile and run:
g++ -o testCircle testCircle.cpp
./testCircle
Area: 314.159
Color: white


2) Example of a regular non-templated C++ class derived from a templated base class:


    (Note that this example uses Color.hpp and Circle.hpp from example one)

File: Sphere.hpp (derived class)
#ifndef SPHERE_HPP__
#define SPHERE_HPP__

#include "Circle.hpp"

class Sphere : public Circle<float>
{
public:
    Sphere(float centerZ, float centerX, float centerY, float radius, eColor color);
    Sphere(float radius);
    Sphere();

    float surfaceArea();
    float volume();
    float getZ();

private:
    float z;
};

Sphere::Sphere(float _x, float _y, float _z, float _radius, eColor _color)
: Circle<float>::Circle (_x, _y, _radius, _color)
{
    this->z = _z;
}

Sphere::Sphere(float _radius)
: Circle::Circle (_radius)
{
    // Defaults from Circle(_radius) constructor can also initialize x, y, z
    this->x = static_cast<float>(0);
    this->y = static_cast<float>(0);
    this->z = static_cast<float>(0);
    this->radius = _radius;
}

Sphere::Sphere()
{
    // Defaults from Circle() default constructor can also initialize values
    this->x = static_cast<float>(0);
    this->y = static_cast<float>(0);
    this->z = static_cast<float>(0);
    this->radius = static_cast<float>(1);
}

float Sphere::surfaceArea()
{
    return static_cast<float>(4) * M_PI * this->radius * this->radius;
}

float Sphere::volume()
{
    float three = 3;
    float four  = 4;
    return four * M_PI * this->radius * this->radius * this->radius / three;
}
#endif
Note the use of "this" to show the class dependency. The use of "this" is required.

File: testSphere.cpp
#include <iostream>
#include "Sphere.hpp"

using namespace std;

int main(int argc, char* argv[])
{
    Sphere sphereA(0.0, 0.0, 0.0,10.0, blue);
    cout << "Volume: " << sphereA.volume() << endl;
    cout << "Color: "  << sphereA.getStrColor() << endl;
}

Compile and run:
g++ -o testSphere testSphere.cpp
./testSphere
Volume: 4188.79
Color: blue


3) Example of a templated class derived from another templated class:


    (Note that this example uses Color.hpp and Circle.hpp from example one)

File: Sphere.hpp (derived class)
#ifndef SPHERE_HPP__
#define SPHERE_HPP__

#include "Circle.hpp"

template <typename T>
class Sphere : public Circle<T>
{
public:
    Sphere(T centerZ, T centerX, T centerY, T radius, eColor color);
    Sphere(T radius);
    Sphere();

    T surfaceArea();
    T volume();
    T getZ();

private:
    T z;
};

template <typename T>
Sphere<T>::Sphere(T _x, T _y, T _z, T _radius, eColor _color)
: Circle<T>::Circle (_x, _y, _radius, _color)
{
    this->z = _z;
}

template <typename T>
Sphere<T>::Sphere(T _radius)
: Circle<T>::Circle (_radius)
{
    // Defaults from Circle(_radius) constructor can also initialize x, y, z
    this->x = static_cast<T>(0);
    this->y = static_cast<T>(0);
    this->z = static_cast<T>(0);
    this->radius = _radius;
}

template <typename T>
Sphere<T>::Sphere()
{
    // Defaults from Circle() default constructor can also initialize values
    this->x = static_cast<T>(0);
    this->y = static_cast<T>(0);
    this->z = static_cast<T>(0);
    this->radius = static_cast<T>(1);
}

template <typename T>
T Sphere<T>::surfaceArea()
{
    return static_cast<T>(4) * M_PI * this->radius * this->radius;
}

template <typename T>
T Sphere<T>::volume()
{
    T three = 3;
    T four  = 4;
    return four * M_PI * this->radius * this->radius * this->radius / three;
}
#endif
Note the use of "this" to show the class dependency. The use of "this" is required.

File: testSphere.cpp
#include <iostream>
#include "Sphere.hpp"

using namespace std;

int main(int argc, char* argv[])
{
    Sphere<float> sphereA(0.0, 0.0, 0.0,10.0, blue);
    cout << "Volume: " << sphereA.volume() << endl;
    cout << "Color: "  << sphereA.getStrColor() << endl;
}

Compile and run:
g++ -o testSphere testSphere.cpp
./testSphere
Volume: 4188.79
Color: blue


Links:

Books:

C++ How to Program
by Harvey M. Deitel, Paul J. Deitel
ISBN #0131857576, Prentice Hall

Fifth edition. The first edition of this book (and Professor Sheely at UTA) taught me to program C++. It is complete and covers all the nuances of the C++ language. It also has good code examples. Good for both learning and reference.

Amazon.com
Exceptional C++: 47 Engineering Puzzles, Programming Problems and Solutions
by Herb Sutter
ISBN #0201615622, Addison-Wesley Professional

Advanced C++ features and STL.

Amazon.com
More Exceptional C++
by Herb Sutter
ISBN #020170434X, Addison-Wesley Professional

Amazon.com
Effective C++: 50 Specific Ways to Improve Your Programs and Design (2nd Edition)
by Scott Meyers
ISBN #0201924889, Addison-Wesley Professional

Amazon.com
More Effective C++: 35 New Ways to improve your Programs and Designs
by Scott Meyers
ISBN #020163371X, Addison-Wesley Professional

Amazon.com

   

    Bookmark and Share


Advertisements




Copyright © 2012 - 2014 by Greg Ippolito