C++ Template Patterns

2 minute read

Function Template

template<typename T>
T clamp(T x, T lower, T upper) {
  return (x > upper)? upper : (x < lower)? lower : x;
}

Template functions can be implicitly instantiated when they are called:

double pos = clamp(x, 0.0, 10.0);

Providing the definition of inBounds without specifying the type of T (i.e/ inBounds<T>) is called “Template argument deduction”.

Non-Type template parameters

Template parameters don’t have to be types, we can also use values:

template<unsigned int power, typename T>
T pow(T x) {
  T y = 1.0;
  for (unsigned int i = 0; i < power; i++) {
    y = y * x;   
  }
  return y;
}

Run on cpp.sh

Note: floating point non-type template params are only supported for C++20 onwards

Class Member Template

Class memeber templates work the same as function templates, but can be created in the class:

class Canvas {
  // ...
  template<typename Shape>
  void draw(Shape shape) {
    // draw stuff using shape properties
  }

Constructor templates are a little bit tricker - we cannot call them like a normal function template:

MyClass<double> object = MyClass<int>(value); // nope

Instead we have to rely on type deduction, for example the following constructor works because we can deduce the template type T:

template<typename T>
explicit Statue(const T& animal) {
  _name = animal.name;
}

Run on cpp.sh

Class Templates

Class templates are similar to function templates, but the parameters are applied to the definition of the entire class:

template <typename T>
class Vector2{
public:
  Vector2(T x, T y) : x(x), y(y) {};
  T dot(Vector2<T> v) { 
    return x * v.x + y * v.y; 
  }
  T x{};
  T y{};
};

Run on cpp.sh

Type Traits

Most of the time we will want to limit what template arguments can be used. This can be done with the <type_traits> library.

For example, if we want to constrain some math functions to only take floating point types, we can use is_floating_point:

template <typename T, 
          typename = typename std::enable_if_t<
          std::is_floating_point<T>::value>>
class Vector2{
public:
  Vector2(T x, T y) : x(x), y(y) {};
  T dot(Vector2<T> v) { 
    return x * v.x + y * v.y; 
  }
  T x{};
  T y{};
};

Vector2 will not compile with any types which aren’t floating point.

Run on cpp.sh

Using extern

When templated classes or functions are used across libraries; we can ensure only one instantiation of each specialization is generated by using explicit instantiation with the extern keyword.

Using extern for a Class template

In the header file:

template <typename T>
SomeClass { /* ... */ };

extern template class SomeClass<double>;

In the source file:

template class SomeClass<double>;

Using extern for a Function template

In the header file:

template <typename T>
T someFunction(T x) { /* ... */ };

extern template int someFunction(int x);

In the source file:

template int someFunction(int x);