COMP6771 Week 6.2

Function Templates, Class Templates

Polymorphism & Generic Programming

  • Polymorphism: Provision of a single interface to entities of different types
  • Two types - :
    • Static (our focus):
      • Function overloading
      • Templates (i.e. generic programming)
        • std::vector<int>
        • std::vector<double>
    • Dynamic:
      • Related to virtual functions and inheritance - see week 8
  • Genering Programming: Generalising software components to be independent of a particular type
    • STL is a great example of generic programming

Function Templates

Without generic programming, to create two logically identical functions that behave in a way that is independent to the type, we have to rely on function overloading.

int min(int a, int b) {
  return a < b ? a : b;
}
double min(double a, double b) {
  return a < b ? a : b;
}

int main() {
  std::cout << min(1, 2) << "\n"; // calls line 1
  std::cout << min(1.0, 2.0) << "\n"; // calls line 4
}

Explore how this looks in Compiler Explorer

Function Templates

  • Function template: Prescription (i.e. instruction) for the compiler to generate particular instances of a function varying by type
    • The generation of a templated function for a particular type T only happens when a call to that function is seen during compile time
template <typename T>
T min(T a, T b) {
  return a < b ? a : b;
}

int main() {
  std::cout << min(1, 2) << "\n"; // calls int min(int, int)
  std::cout << min(1.0, 2.0) << "\n"; // calls double min(double, double)
}

Explore how this looks in Compiler Explorer

Some Terminology

template type parameter

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

template parameter list

Type and Nontype Parameters

  • Type parameter: Unknown type with no value
  • Nontype parameter: Known type with unknown value
#include <iostream>

template <typename T, int size>
T findmin(const std::array<T, size> a) {
  T min = a[0];
  for (int i = 1; i < size; ++i) {
    if (a[i] < min) min = a[i];
  }
  return min;
}

int main() {
  std::array<int, 3> x{ 3, 1, 2 };
  std::array<double, 4> y{ 3.3, 1.1, 2.2, 4.4 };
  std::cout << "min of x = " << findmin(x) << "\n";
  std::cout << "min of x = " << findmin(y) << "\n";
}

Compiler deduces T and size from a

Type and Nontype Parameters

  • The above example generates the following functions at compile time
  • What is "code explosion"? Why do we have to be weary of it?
int findmin(const std::array<int, 3> a) {
  int min = a[0];
  for (int i = 1; i < 3; ++i) {
    if (a[i] < min) min = a[i];
  }
  return min;
}

double findmin(const std::array<double, 4> a) {
  double min = a[0];
  for (int i = 1; i < 4; ++i) {
    if (a[i] < min) min = a[i];
  }
  return min;
}

Class Templates

  • How we would currently make a Stack type
  • Issues?
    • Administrative nightmare
    • Lexical complexity (need to learn all type names)
class IntStack {
 public:
  void push(int&);
  void pop();
  int& top();
  const int& top() const;
 private:
  std::vector<int> stack_;
};
class DoubleStack {
 public:
  void push(double&);
  void pop();
  double& top();
  const double& top() const;
 private:
  std::vector<double> stack_;
};

Class Templates

  • Creating our first class template
// stack.h
#ifndef STACK_H
#define STACK_H

#include <iostream>
#include <vector>

template <typename T>
class Stack {
 public:
  friend std::ostream& operator<<(std::ostream& os, const Stack& s) {
    for (const auto& i : s.stack_) os << i << " ";
    return os;
  }
  void push(T&);
  void pop();
  T& top();
  const T& top() const;
 private:
  vector<T> stack_;
};

#endif // STACK_H
// stack.h (continued)

template <typename T>
void Stack<T>::push(const T &item) {
  stack_.push_back(item);
}

template <typename T>
void Stack<T>::pop() {
  stack_.pop_back();
}

template <typename T>
T& Stack<T>::top() {
  return stack_.back();
}

template <typename T>
const T& Stack<T>::top() const {
  return stack_.back();
}

template <typename T>
bool Stack<T>::empty() const {
  return stack_.empty();
}

Class Templates

Main function

#include "stack.h"

#include <iostream>
#include <string>

int main() {
  Stack<int> s1; // int: template argument
  s1.push(1);
  s1.push(2);
  Stack<int> s2 = s1;
  std::cout << s1 << s2 << '\n';
  s1.pop();
  s1.push(3);
  std::cout << s1 << s2 << '\n';
  s1.push("hello"); // Fails to compile.
 
  Stack<std::string> string_stack;
  string_stack.push("hello");
  string_stack.push(1); // Fails to compile.
}

Class Templates

template <typename T>
Stack<T>::Stack() { }

template <typename T>
Stack<T>::Stack(const Stack<T> &s) : stack_{s.stack_} { }

template <typename T>
Stack<T>::Stack(Stack<T> &&s) : stack_(std::move(s.stack_)); { }

template <typename T>
Stack<T>& Stack<T>::operator=(const Stack<T> &s) {
  stack_ = s.stack_;
}

template <typename T>
Stack<T>& Stack<T>::operator=(Stack<T> &&s) {
  stack_ = std::move(s.stack_);
}

template <typename T>
Stack<T>:: ̃Stack() { }

Default rule-of-five

default

class Stack {
 public:
  // Why won't this work if we uncomment the line below?
  // Stack(int i) { stack_.push_back(i); }
  void push(int&);
  void pop();
  int& top();
  const int& top() const;
 private:
  std::vector<int> stack_;
};

int main() {
  Stack s;
}
Made with Slides.com