Types of Polymorphism
Function Templates
Class Tempaltes
Casting
So far, we have covered two types of polymorphism:
Inclusion
run-time polymorphism which involves virtual functions and derived classes
Overloading
compile-time polymorphism which involves two or more functions having a different set of arguments
We will now cover two other types of polymorphism:
Parametric
compile-time polymorphism which involves the use of templates
Coercion
compilation or at run-time polymorphism which involves casting variables from one type to another
C++ introduces parametric polymorphism via templates
Instead of defining the types of each argument, the developer creates a template for them
The compiler creates a version of the templated function for all combinations of arguments that it finds in the source code
#include <iostream>
template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
int main() {
int i1 = 10, i2 = 20;
std::cout << "Max of " << i1 << " and " << i2
<< " is " << max(i1, i2) << std::endl;
double d1 = 10.5, d2 = 20.3;
std::cout << "Max of " << d1 << " and " << d2
<< " is " << max(d1, d2) << std::endl;
char c1 = 'a', c2 = 'z';
std::cout << "Max of " << c1 << " and " << c2
<< " is " << max(c1, c2) << "" << std::endl;
return 0;
}
The line: template<typename identifier>
tells the compiler that the following block of code is a template
It also tells which identifiers to substitute for types during compilation time
Note that templated functions can have more than one type of templated identifier
The compiler cannot always deduct a type for return arguments
#include <iostream>
template <typename T1, typename T2>
void max(T1 a, T2 b) {
if (a > b) {
std::cout << "Max of " << a << " and " << b
<< " is " << a << std::endl;
} else {
std::cout << "Max of " << a << " and " << b
<< " is " << b << std::endl;
}
}
int main() {
int i1 = 10, i2 = 20;
max(i1, i2);
double d1 = 10.5, d2 = 20.3;
max(d1, d2);
char c1 = 'a', c2 = 'z';
max(c1, c2);
//mixing types
max(i1, d2);
max(d1, i2);
max(c1, i2);
max(d1, c2);
return 0;
}
Templates can also be used for classes
It allows for templated member variables
It also allows class member functions to deal with templated arguments
#include <iostream>
template <typename T>
class Vector {
private:
T x_;
T y_;
public:
Vector(T x = 0, T y = 0) : x_(x), y_(y) {}
Vector operator+(const Vector& v) const {
return Vector(this->x_ + v.x_, this->y_ + v.y_);
}
Vector operator-(const Vector& v) const {
return Vector(this->x_ - v.x_, this->y_ - v.y_);
}
Vector operator*(const T& scalar) const {
return Vector(x_ * scalar, y_ * scalar);
}
friend std::ostream& operator<<(std::ostream& os,
const Vector& v) {
os << "(" << v.x_ << ", " << v.y_ << ")";
return os;
}
};
int main() {
Vector<int> i1(4, 5);
Vector<int> i2(2, 3);
Vector<int> i3 = i1 + i2;
Vector<int> i4 = i1 * 2;
std::cout << i1 << " + " << i2 << " = "
<< i3 << std::endl;
std::cout << i1 << " * " << 2 << " = "
<< i4 << std::endl;
Vector<double> d1(1.5, 2.5);
Vector<double> d2(3.0, 4.2);
Vector<double> d3 = d1 + d2;
Vector<double> d4 = d1 * 2.0;
std::cout << d1 << " + " << d2 << " = "
<< d3 << std::endl;
std::cout << d1 << " * " << 2.0 << " = "
<< d4 << std::endl;
return 0;
}
We use class_name<type1, type2, ...>
when declaring a templated object
This allows the compiler to deduct which types to use
Note the friend function that return ostream
in the example!
The compiler records the types of all variables
There are situations in which a variable needs to be recasted into a different type:
→ Upcasting a derived object as a base object
→ Downcasting a base object into a derived object
→ Numerical conversions (e.g., int to double)
→ API interfacing
The following methods are provided to recast types:
static_cast: performs type conversion at compile-time
dynamic_cast: performs type conversion at run-time
const_cast: modifies const or volatile qualifier
reinterpret_cast: type conversion without checks
#include <iostream>
#include <typeinfo>
int main()
{
int num = 10;
std::cout << "10/3 = " << num/3 << std::endl;
double numDouble = static_cast<double>(num);
std::cout << "10/3 = " << numDouble/3 << std::endl;
std::cout << typeid(num).name() << std::endl;
std::cout << typeid(numDouble).name() << std::endl;
return 0;
}
#include <iostream>
class Animal {
public:
virtual void makeSound() const {
std::cout << "Some sound" << std::endl;
}
};
class Dog : public Animal {
public:
void makeSound() const override {
std::cout << "Bark bark!" << std::endl;
}
void fetch() {
std::cout << "Fetching ball!" << std::endl;
}
};
int main() {
Dog fido;
//upcasting
Animal *animal = dynamic_cast<Animal *>(&fido);
animal->makeSound();
//animal->fetch(); //error
return 0;
}
#include <iostream>
class Animal {
public:
virtual void makeSound() const {
std::cout << "Some sound" << std::endl;
}
};
class Dog : public Animal {
public:
void makeSound() const override {
std::cout << "Bark bark!" << std::endl;
}
void fetch() {
std::cout << "Fetching ball!" << std::endl;
}
};
int main() {
Dog fido;
//upcasting
Animal *animal = dynamic_cast<Animal *>(&fido);
//downcasting
Dog *fido_recovered = dynamic_cast<Dog *>(animal);
fido_recovered->makeSound();
fido_recovered->fetch();
return 0;
}