COMP6771 Week 7.1

Templates, Part 2

Inclusion compilation model

  • What is wrong with this?
  • g++ min.cpp main.cpp -o main
template <typename T>
T min(T a, T b);
template <typename T>
T min(T a, T b) {
  return a < b ? a : b;
}
#include <iostream>

int main() {
  std::cout << min(1, 2) << "\n";
}

min.h

min.cpp

main.cpp

Inclusion compilation model

  • When it comes to templates, we include definitions (i.e. implementation) in the .h file
    • This is because template definitions need to be known at compile time (template definitions can't be instantiated at link time because that would require an instantiation for all types)
  • Will expose implementation details in the .h file
  • Can cause slowdown in compilation as every file using min.h will have to instantiate the template, then it's up the linker to ensure there is only 1 instantiation.
template <typename T>
T min(T a, T b) {
  return a < b ? a : b;
}
#include <iostream>

int main() {
  std::cout << min(1, 2) << "\n";
}

min.h

main.cpp

Inclusion Compilation Model

  • Alternative: Explicit instantiations
  • Generally a bad idea
template <typename T>
T min(T a, T b);
template <typename T>
T min(T a, T b) {
  return a < b ? a : b;
}

template int min<int>(int, int);
template double min<double>(double, double);
#include <iostream>

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

min.h

min.cpp

main.cpp

Inclusion Compilation Model

  • Exact same principles will apply for classes
  • Implementations must be in header file, and compiler should only behave as if one Stack<int> was instantiated
#include <vector>

template <typename T>
class Stack {
 public:
  Stack() {}
  void pop();
  void push(const T& i);
 private:
  std::vector<T> items_;
}

#include "stack.tpp"
template <typename T>
void Stack<T>::pop() {
  items_.pop_back();
}

template <typename T>
void Stack<T>::push(const T& i) {
  items_.push_back(i);
}
int main() {
  Stack<int> s;
}

Stack.h

Stack.tpp

main.cpp

Inclusion Compilation Model

  • Lazy instantiation: Only members functions that are called are instantiated
    • In this case, pop() will not be instantiated
#include <vector>

template <typename T>
class Stack {
 public:
  Stack() {}
  void pop();
  void push(const T& i);
 private:
  std::vector<T> items_;
}

#include "stack.tpp"
template <typename T>
void Stack<T>::pop() {
  items_.pop_back();
}

template <typename T>
void Stack<T>::push(const T& i) {
  items_.push_back(i);
}
int main() {
  Stack<int> s;
  s.push(5);
}

Stack.h

Stack.tpp

main.cpp

Static Members

  • Each template instantiation has it's own set of static members
#include <vector>

template <typename T>
class Stack {
 public:
  Stack();
  ~Stack();
  void push(T&);
  void pop();
  T& top();
  const T& top() const;
  static int numStacks_;
 private:
  std::vector<T> stack_;
};

template <typename T>
int Stack<T>::numStacks_ = 0;

template <typename T>
Stack<T>::Stack() { numStacks_++; }

template <typename T>
Stack<T>:: ~Stack() { numStacks_--; }
#include <iostream>

#include "lectures/week7/stack.h"

int main() {
  Stack<float> fs;
  Stack<int> is1, is2, is3;
  std::cout << Stack<float>::numStacks_ << "\n";
  std::cout << Stack<int>::numStacks_ << "\n";
}

Friends

  • Each stack instantiation has one unique instantiation of the friend
#include <vector>

template <typename T>
class Stack {
 public:
  Stack();
  ~Stack();
  void push(T&);
  void pop();
  friend std::ostream& operator<<(std::ostream& os, const Stack& s);
 private:
  std::vector<T> stack_;
};

template <typename T>
void push(T& t) { 
  stack_.push_back(t);
}

template <typename T>
std::ostream& operator<<(std::ostream& os, const Stack<T>& s) {
  std::cout << "My top item is " << s.stack_.back() << \n";
}
#include <iostream>
#include <string>

#include "lectures/week7/stack.h"

int main() {
  Stack<std::string> ss;
  ss.push("Hello");
  std::cout << ss << "\n":

  Stack<int> is;
  is.push(5);
  std::cout << is << "\n":
}

Default Members

  • We can provide default arguments to template types (where the defaults themselves are types)
  • It means we have to update all of our template parameter lists
#include <vector>

template <typename T, typename CONT = std::vector<T>>
class Stack {
 public:
  Stack();
  ~Stack();
  void push(T&);
  void pop();
  T& top();
  const T& top() const;
  static int numStacks_;
 private:
  CONT stack_;
};

template <typename T, typename CONT>
int Stack<T, CONT>::numStacks_ = 0;

template <typename T, typename CONT>
Stack<T, CONT>::Stack() { numStacks_++; }

template <typename T, typename CONT>
Stack<T, CONT>:: ~Stack() { numStacks_--; }
#include <iostream>

#include "lectures/week7/stack.h"

int main() {
  Stack<float> fs;
  Stack<int> is1, is2, is3;
  std::cout << Stack<float>::numStacks_ << "\n";
  std::cout << Stack<int>::numStacks_ << "\n";
}
Made with Slides.com