C++ Template Patterns
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;
}
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;
}
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{};
};
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.
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);