BTP200

Templates and Casting

Summary

Types of Polymorphism

Function Templates

Class Tempaltes

Casting

Types of Polymorphism

Inclusion and Overloading

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

Parametric and Casting

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

Function Templates

Introduction

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

Example

Function Template

#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;
}

              

Notes

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

Example

Function Template

#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;
}

              

Class Templates

Introduction

Templates can also be used for classes

It allows for templated member variables

It also allows class member functions to deal with templated arguments

Example

Class Template

#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;
}

              

Notes

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!

Casting

Casting

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

Casting

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

Casting

Example of numerical conversion

#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;
}
              

Casting

Example of upcasting

#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;
}

              

Casting

Example of downcasting

#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;
}

              

Suggested Reading

Polymorphism

Templates

Casting