Templates
Templates are basically placeholder types. In other programming languages, templates are also known as generic types.
Using templates, you can use the same definition of a function or a class with different types. For example, you can say that this template parameter T over here may be substituted by a double or an int type when used.
Defining templates
Use template <typename T, typename U, template V> before the function or class definition (specifying as many typename's as necessary inside the <...>).
We normally use single capital letters for the template parameters, e.g. T, U, V.
class vs typename
You can use template <class T, class U> instead of template <typename T, typename U>. (Just another historical stuff.)
There are no differences, but we prefer to use the latter because the template parameters can be substituted with primitive types, not just class types.
Defining template classes
template <[...parameters]> struct ClassName {...}template <[...parameters]> class ClassName {...}
template <typename T, typename U>
struct Tuple {
T a;
U b;
Tuple(T a, U b): a{a}, b{b} {}
};
Defining template functions
template <[...parameters]> [returnType] functionName(...) {...}
template <typename T>
T max(T a, T b) {
return a > b ? a : b;
}
Using auto parameter type as a shorthand for a template function
In C++20, when you use auto as parameter type(s) of a normal function, under the hood, the compiler will convert the function into a template function.
For example, the following code
// Note `(auto x, auto y)`
auto max(auto x, auto y) {
return (x > y) ? x : y;
}
is a shorthand for
// Each `auto`-typed parameter becomes
// an independent template type
template <typename T, typename U>
auto max(T x, U y) {
return (x > y) ? x : y;
}
Instantiating templates
Instantiating means creating actual classes or functions from templates.
When instantiating templates, you specify what actual type each template parameter stands for (e.g. T stands for int, U stands for double).
Instantiating template classes
Use ClassName<[...actualTypes]> as the type.
Tuple<int, double> tuple = { 1, 2.5 };
printf("%ld %lf\n", tuple.a, tuple.b);
//=> 1 2.500000
Instantiating template functions
Use functionName<[...actualTypes]> when calling the function.
double max_value = max<double>(1.75, 4.1);
printf("%lf\n", max_value);
//=> 4.100000
Template type deduction
Be careful with "type narrowing" when you rely on this feature.
For example, a template function that receives actual types of int and double may return an int when you expect it to return double.
Instead of instantiating the templates yourself, you can have the compiler automatically deduce the parameter types based on usage.
// Automatically deduce template class
// (One version: don't put the `<>`)
Tuple tuple = { 1, 2.5 };
printf("%d %lf\n", tuple.a, tuple.b);
// Automatically deduce template function
// (Two versions: put or don't put the `<>`)
double a = max(1.75, 4.1);
double b = max<>(1.75, 4.1);
The difference between the two template function calls:
max(...)— the compiler will considermax<T>template function overloads andmaxnormal function overloadsmax<>(...)— the compiler will only considermax<T>template function overloads
Template specialization
You can create "special cases" when a template class or function is instantiated with a certain type(s).
Template class specialization
template <typename T>
struct MyNumber {
T number = 0;
void printType() {
printf("Generic number\n");
}
};
// Specialization of MyNumber<double>:
// (No more template parameter remaining;
// so we can just put `template <>`)
template <>
struct MyNumber<double> {
double number = 0;
void printType() {
printf("Double\n");
}
};
int main() {
MyNumber<int> a = {};
a.printType();
//=> Generic number
MyNumber<double> b = {};
b.printType();
//=> Double
}
Template function specialization
If you are specializing a member function, you should do it outside the class definition:
template <typename T>
struct MyNumber {
T number = 0;
void printType() {
printf("Generic number\n");
}
};
// Specializing a member function
// Do it outside the class definition
template <>
void MyNumber<double>::printType() {
printf("Double\n");
}
Otherwise, follow a similar pattern like when you specialize a template class:
template <typename T>
void printNumber(T a) {
printf("Integer: %d\n", a);
}
// Specialization of printNumber<double>:
// (No more template parameter remaining;
// so we can just put `template <>`)
template <>
void printNumber<double>(double a) {
printf("Double: %e\n", a);
}
int main() {
// Note: this also works with template type deduction
// (if you don't specify `<int>` or `<double>`)
printNumber<int>(1);
//=> Integer: 1
printNumber<double>(3.14);
//=> Double: 3.140000e+00
}
Partial specialization with pointer types
This kind of code also works:
template <typename T>
class Storage {
...
}
// Specialization of Storage<T*>
// (works if T is a pointer type):
template <typename T>
class Storage<T*> {
...
}
int main() {
int number = 123;
Storage<int> storage_a = { number };
Storage<int*> storage_b = { &number };
}
References
- C++ Crash Course (Josh Lospinoso) — 6. Compile-Time Polymorphism
- 19.1 — Template classes — https://www.learncpp.com/cpp-tutorial/template-classes/
- 8.13 — Function templates — https://www.learncpp.com/cpp-tutorial/function-templates/
- 8.15 — Function templates with multiple template types — https://www.learncpp.com/cpp-tutorial/function-templates-with-multiple-template-types/
- 19.3 — Function template specialization — https://www.learncpp.com/cpp-tutorial/function-template-specialization/
- 19.4 — Class template specialization — https://www.learncpp.com/cpp-tutorial/class-template-specialization/