What are Templates in C++

beginner c++11

Templates let us define functions or classes with placeholders for the types of their parameters or members respectively. The compiler then instantiates the function or class for us at compile time based on how the function or class is used elsewhere in the program. Instantiation means to turn a template into a concrete function or class. Because instantiation happens at compile time the full definition of the function or class must be visible at the time of usage, typically in your .cpp files. It’s often the case that templates are fully defined in header files so that they may be visible to the compiler everywhere in your program.

Template Functions

Here’s how to write a template function that cubes anything that can be multiplied by itself.

#include <iostream>

template <typename T>
T cube(T x) {
  return x * x * x;
}

struct Point {
  int x, y, z;
  
  friend Point operator *(const Point& a, const Point& b) {
    return Point{a.x * b.x, a.y * b.y, a.z * b.z};
  }
};

int main() {
  int x = 7;
  std::cout << cube(x) << "\n";

  float pi = 3.14159f;
  std::cout << cube(pi) << "\n";

  Point p{1, 2, 3};
  Point p3 = cube(p);
  std::cout << p3.x << ", " << p3.y << ", " << p3.z << "\n";
}
343
31.0062
1, 8, 27

Inferred Template Types

In the above example the template type T of the cube function template was inferred for us by the compiler. This is because the compiler observes the type of the parameters x, pi, and p to be int, float, and Point respectively at the time of instantiation. In cases where it’s not clear or you wish to be explicit you can use angle brackets (< and >) to specify the type manually.

#include <iostream>

template <typename T>
T cube(T x) {
  return x * x * x;
}

int main() {
  int x = 7;
  std::cout << cube<int>(x) << "\n";

  float pi = 3.14159f;
  std::cout << cube<float>(pi) << "\n";
}
343
31.0062

Template Classes

In the first example we used a Point struct to hold three integer values. We can template the Point struct as well to support other types. We can combine our Point struct template with our cube template function to cube Points of any type.

#include <iostream>

template <typename T>
T cube(T x) {
  return x * x * x;
}

template <typename T>
struct Point {
  T x, y, z;

  friend Point operator *(const Point& a, const Point& b) {
    return Point{a.x * b.x, a.y * b.y, a.z * b.z};
  }
};

int main() {
  Point<int> p{1, 2, 3};
  Point p3 = cube(p);
  std::cout << p3.x << ", " << p3.y << ", " << p3.z << "\n";

  Point<float> pf{1.1, 2.2, 3.3};
  Point pf3 = cube(pf);
  std::cout << pf3.x << ", " << pf3.y << ", " << pf3.z << "\n";
}
1, 8, 27
1.331, 10.648, 35.937

Non-Type Template Parameters

We can also use templates to specify integer values of structs at compile time. std::array<T, N> uses a non-type template parameter N to declare the number of elements it holds. We can implement our own simplified Array to see how this works.

#include <iostream>

template <typename T>
T cube(T x) {
  return x * x * x;
}

template <typename T, size_t N>
class Array {
public:
  T data_[N];

  T& operator[](size_t i) {
    return data_[i];
  }

  T* begin() { return data_; }
  T* end() { return data_ + N; }
};

int main() {
  Array<int, 3> a{1, 2, 3};

  for (auto& it : a) {
    it = cube(it);
  }
  
  std::cout << a[0] << ", " << a[1] << ", " << a[2] << "\n";
}
1, 8, 27

For more C++ By Example, click here.