Part E - Polymorphism

Function Templates

Describe how to define a function template
Describe how to define an exception to a function template
Describe the four type casting keywords and their application

"Templates are of great utility to programmers in C++, especially when combined with multiple inheritance and operator overloading. " Wikipedia (2013).

Template Syntax | Explicit Specialization | Class Templates | Casting | Summary | Exercises



C++ implements parametric polymorphism using template syntax.  At compile-time the compiler generates the required function and class definitions from templates that the programmer has defined.  We call each definition that the compiler generates a template specialization

This chapter introduces template syntax with particular reference to functions and describes how to call a templated function, how to define an explicit specialization of a template for a particular type and how to call a specialization explicitly.  The description of class templates is limited to a simple example. 


Template Syntax

The definition of a template is similar to that of a global function with the parentheses replaced by angle brackets.  A template header takes the form

 template<Type identifier, ...>

The keyword template identifies the subsequent code block as a template.  The less than < greater than > pair encloses the template's parameters.  The ellipsis stands for more comma-separated parameters. 

Each parameter declaration within this header consists of a type and an identifier.  Type may be any of

  • typename - to identify a type (fundamental or compound)
  • class - to identify a type (fundamental or compound)
  • int, long, short, char - to identify a non-floating-point fundamental type
  • a template parameter
identifier is a placeholder used throughout the code block to represent the argument passed in the call. 

For example,

 template <typename T>

 // ... template body follows here

     T value; // value is of type T 

 template <class T>

 // ... template body follows here

     T value; // value is of type T 

The compiler replaces T with the argument specified in the function call.

Complete Definition

Consider a function that swaps values in two different memory locations.  The code for a function that swaps the values of two int variables may be defined as follows:

 void swap(int& a, int& b) {
     int c;
     c = a;
     a = b;
     b = c;
 }

The template for all functions that swap values in this way follows by replacing the type int with the type T and adding the template header:

 // Template for swap
 // swap.h

 template<typename T>
 void swap(T& a, T& b) {
     T c;
     c = a;
     a = b;
     b = c;
 }

We define short function templates inside header files; in this case, in swap.h

Function Call

The call to a templated function determines the specialization that the compiler generates.  The simplest call is a normal function call.  The compiler binds the call to the specialization. 

For example, to call the swap() function for two doubles and two longs, we write the following and leave the rest to the compiler:

 // Calling a Templated Function
 // swap.cpp

 #include <iostream>
 #include "swap.h" // template definition 

 int main() { 
     double a = 2.3;
     double b = 4.5;
     long   d = 78;
     long   e = 567;

     swap(a, b); // compiler generates
                 // swap(double, double)

     std::cout << "Swapped values are " << 
          a << " and " << b << std::endl; 

     swap(d, e); // compiler generates
                 // swap(long, long)

     std::cout << "Swapped values are " <<
          d << " and " << e << std::endl; 
 }















 Swapped values are 4.5 and 2.3 





 Swapped values are 567 and 78

 

Note that the arguments in each call are unambiguous in their type and the compiler can specialize the template correctly. 

Ambiguous Calls

If the arguments in a function call are ambiguous, we must specify the template argument(s) explicitly.  For example,

 // Ambiguous Arguments
 // swap.cpp

 #include <iostream>
 #include "swap.h"

 int main() { 
     double a = 2.3;
     double b = 4.5;
     long   d = 78;
     long   e = 5;

     swap<double>(a, d);

     std::cout << "Swapped values are " << 
          a << ", " << d << std::endl; 

     swap<long>(b, e);

     std::cout << "Swapped values are " <<
          b << ", " << e << std::endl; 
 }














 Swapped values are 78, 2.3 




 Swapped values are 5, 4.5

 

Explicit Specialization

A template definition may have exceptions.  We define a separate specialization for each type not covered by the template definition. 

For example, the following template defines functions that return the maximum of two arguments: 

 // Maximum Function
 // maximum.h

 template<typename T>
 T maximum(T a, T b) {
     return a > b ? a : b;
 }

This template applies to all fundamental non-pointer types.  To accomodate the const char* type, we specialize the template explicitly:


 // Maximum Function
 // + explicit specialization for const char*
 // maximum.h

 template<typename T>
 T maximum(T a, T b) {
     return a > b ? a : b;
 }

 template<> // explicit specialization
 const char* maximum<const char*>(const char* a, const char* b) { 
     return strcmp(a, b) > 0 ? a : b;
 }

The empty parameter list identifies an explicit specialization.  Explicit specializations follow the general template definition. 

The second call to maximum binds to the explicit specialization for the const char* type:

 // Maximum of Two Strings
 // maximum.cpp

 #include <iostream>
 #include "maximum.h"

 int main() {
     double a = 2.3;
     double b = 4.5;
     const char d[4] = "abc";
     const char e[4] = "def";

     double c = maximum(a, b);

     std::cout << "Greater of " << 
          a << ", " << b <<
          " is " << c << std::endl; 

     const char* f = maximum(d, e); 

     std::cout << "Greater of " <<
          d << ", " << e <<
          " is " << f << std::endl; 
 }














 Greater of 2.3, 4.5 is 4.5 





 Greater of abc, def is def


 


Class Template

Class templates are similar to function templates.  Consider the following template for generating Array classes of specified size in static memory.  The template parameters are the array's type (T) and its size (N):

 // Template for Array Classes
 // Array.h

 template <class T, int N>
 class Array {
     T a[N];
 public:
     T& operator[](int i) { return a[i%N]; } 
 };

In the following program, the compiler generates a class definition for type int and size 5 using the Array class template.  The results of executing this program are shown on the right:

 // Class Template
 // Template.cpp

 #include <iostream>
 #include "Array.h"

 int main() {
     Array<int, 5> a, b;

     for (int i = 0; i < 5; i++)
         a[i] = i * i;

     b = a;

     for (int i = 0; i < 5; i++)
         std::cout << b[i] << ' ';
     std::cout << std::endl;
}















 0 1 4 9 16



Type Casting

C++ supports type casting through template syntax.  Type casting should be avoided wherever possible since casting a value from one type to another type circumvents the type checking facilities, which are fundamental to the language.  We implement casts only where absolutely unavoidable. 

Where implemented, a standard cast should use one of the following keywords:

  • static_cast<Type>(expression)
  • reinterpret_cast<Type>(expression)
  • const_cast<Type>(expression)
  • dynamic_cast<Type>(expression)

Type refers to the destination type.  This syntax replaced the older and less discriminant C-style and C++ function-style syntax.

Related Types

The static_cast<Type>(expression) keyword converts an expression from its evaluated type to a related type. 

For example, to cast minutes to a float type, we write:

 // Cast to a Related Type
 // static_cast.cpp

 #include <iostream>

 int main() {
     double hours;
     int minutes;

     std::cout << "Enter minutes : ";
     std::cin >> minutes;
     hours = static_cast<double>(minutes)/ 60;  // int and float are related 
     std::cout << "In hours, this is " << hours;
 }

static_cast<Type>(expression) performs some type checking.  It rejects conversions between pointer and non-pointer types. 

For example, the following cast generates a compile-time error:

 #include <iostream>

 int main() {
     int x = 2;
     int* p;

     p = static_cast<int*>(x);  // FAILS: unrelated types 

     std::cout << p;
 }

Some static casts are portable across different platforms. 

Unrelated Types

The reinterpret_cast<Type>(expression) keyword converts an expression from its evaluated type to an unrelated type.  It may produce a value that has the same bit pattern as the evaluated expression. 

For example, to cast an int type to a pointer to an int type, we write:

 // Cast to an Unrelated Type
 // reinterpret_cast.cpp

 #include <iostream>

 int main( ) {
     int x = 2;
     int* p;

     p = reinterpret_cast<int*>(x);  // int and int* are unrelated 

     std::cout << p;
 }

reinterpret_cast<Type>(expression) performs minimal type checking.  It rejects conversions between related types. 

For example, the following cast generates a compile-time error:

 #include <iostream>

 int main( ) {
     int x = 2;
     double y;

     y = reinterpret_cast<double>(x);  // FAILS types are related 

     std::cout << y;
 }

Few reinterpret casts are portable. 

Unmodifiable Types

The const_cast<Type>(expression) keyword removes the const status from an expression:

 // Strip const status from an Expression
 // const_cast.cpp

 #include <iostream>

 void foo(int* p) {
     std::cout << *p << std::endl;
 }

 int main( ) {
     const int x = 3;
     const int* a = &x;
     int* b;

     // foo expects int* and not const int*
     b = const_cast<int*>(a);  // remove const status 
     foo(b);
 }

const_cast<Type>(expression) performs minimal type checking.  It rejects conversions between different types. 

For example, the following code generates a compile-time error:

 #include <iostream>

 int main( ) {
     const int x = 2;
     double y;

     y = const_cast<double>(x); // FAILS 

     std::cout << y;
 }

Inherited Types

The dynamic_cast<Type>(expression) keyword converts the value of the expression from its current type to another type within the same hierarchy. 

For example, to cast a pointer to derived object d to a pointer to its base class part, we write:

 // Cast to a Type within the Hierarchy
 // dynamic_cast.cpp

 #include <iostream>

 class Base {
   public:
     void display() const { std::cout << "Base\n"; }
 };
 class Derived : public Base {
   public:
     void display() const { std::cout << "Derived\n"; }
 };

 int main( ) {
     Base* b;
     Derived* d = new Derived;

     b = dynamic_cast<Base*>(d);  // in the same hierarchy 
     b->display();
     d->display();
     delete d;
 }



















 Base
 Derived 


dynamic_cast<Type>(expression) performs some type checking.  It rejects conversions from a base class pointer to a derived class pointer if the object is monomorphic. 

For example, the following cast generates a compile-time error:

 #include <iostream>

 class Base {
   public:
     void display() const { std::cout << "Base\n"; }
 };
 class Derived : public Base {
   public:
     void display() const { std::cout << "Derived\n"; } 
 };

 int main( ) {
     Base* b = new Base;
     Derived* d;

     d = dynamic_cast<Derived*>(b);  // FAILS 
     b->display();
     d->display();
     delete d;
 }

Note that a static_cast works here and may produce the result shown on the right.  However, the Derived part of the object would then be incomplete.  That is, static_cast does not check if the object is complete and leaves the responsibility to do so in the programmer's hands.

 #include <iostream>

 class Base {
   public:
     void display() const { std::cout << "Base\n"; }
 };
 class Derived : public Base {
   public:
     void display() const { std::cout << "Derived\n"; } 
 };

 int main( ) {
     Base* b = new Base;
     Derived* d;

     d = static_cast<Derived*>(b);  // OK 
     b->display();
     d->display();
     delete d;
 }
















 Base
 Derived 



Summary

  • a template header consists of the keyword template followed by the template parameters
  • the compiler generates the template specialization based on the argument types in the function call
  • in cases of ambiguity specify the template's argument types explicitly
  • an explicit specialization is an exception to a template
  • avoid type casting since it bypasses the language's type-checking facilities
  • if type casting is necessary, use one of the four type cast keywords

Exercises




Previous Reading  Previous: Abstract Base Classes Next: The ISO/IEC Standard   Next Reading


  Designed by Chris Szalwinski   Copying From This Site   
Logo
Creative Commons License