Writing your first templates
It is now time to see how templates are written in the C++ language. In this section, we will start with three simple examples, one for each of the snippets presented earlier.
A template version of the max
function discussed previously would look as follows:
template <typename T> T max(T const a, T const b) { return a > b ? a : b; }
You will notice here that the type name (such as int
or double
) has been replaced with T
(which stands for type). T
is called a type template parameter and is introduced with the syntax template<typename T> or typename<class T>
. Keep in mind that T
is a parameter, therefore it can have any name. We will learn more about template parameters in the next chapter.
At this point, this template that you put in the source code is only a blueprint. The compiler will generate code from it based on its use. More precisely, it will instantiate a function overload for each type the template is used with. Here is an example:
struct foo{}; int main() { foo f1, f2; max(1, 2); // OK, compares ints max(1.0, 2.0); // OK, compares doubles max(f1, f2); // Error, operator> not overloaded for // foo }
In this snippet, we are first calling max
with two integers, which is OK because operator>
is available for the type int
. This will generate an overload int max(int const a, int const b)
. Second, we are calling max
with two doubles, which again is all right since operator>
works for doubles. Therefore, the compiler will generate another overload, double max(double const a, double const b)
. However, the third call to max
will generate a compiler error, because the foo
type does not have the operator>
overloaded.
Without getting into too many details at this point, it should be mentioned that the complete syntax for calling the max
function is the following:
max<int>(1, 2); max<double>(1.0, 2.0); max<foo>(f1, f2);
The compiler is able to deduce the type of the template parameter, making it redundant to write it. There are cases, however, when that is not possible; in those situations, you need to specify the type explicitly, using this syntax.
The second example involving functions from the previous section, Understanding the need for templates, was the quicksort()
implementation that dealt with void*
arguments. The implementation can be easily transformed into a template version with very few changes. This is shown in the following snippet:
template <typename T> void swap(T* a, T* b) { T t = *a; *a = *b; *b = t; } template <typename T> int partition(T arr[], int const low, int const high) { T pivot = arr[high]; int i = (low - 1); for (int j = low; j <= high - 1; j++) { if (arr[j] < pivot) { i++; swap(&arr[i], &arr[j]); } } swap(&arr[i + 1], &arr[high]); return i + 1; } template <typename T> void quicksort(T arr[], int const low, int const high) { if (low < high) { int const pi = partition(arr, low, high); quicksort(arr, low, pi - 1); quicksort(arr, pi + 1, high); } }
The use of the quicksort
function template is very similar to what we have seen earlier, except there is no need to pass pointers to callback functions:
int main() { int arr[] = { 13, 1, 8, 3, 5, 2, 1 }; int n = sizeof(arr) / sizeof(arr[0]); quicksort(arr, 0, n - 1); }
The third example we looked at in the previous section was the vector
class. A template version of it will look as follows:
template <typename T> struct vector { vector(); size_t size() const; size_t capacity() const; bool empty() const; void clear(); void resize(size_t const size); void push_back(T value); void pop_back(); T at(size_t const index) const; T operator[](size_t const index) const; private: T* data_; size_t size_; size_t capacity_; };
As in the case of the max
function, the changes are minimal. There is the template declaration on the line above the class and the type int
of the elements has been replaced with the type template parameter T
. This implementation can be used as follows:
int main() { vector<int> v; v.push_back(1); v.push_back(2); }
One thing to notice here is that we have to specify the type of the elements when declaring the variable v
, which is int
in our snippet because the compiler would not be able to infer their type otherwise. There are cases when this is possible, in C++17, and this topic, called class template argument deduction, will be discussed in Chapter 4, Advanced Template Concepts.
The fourth and last example concerned the declaration of several variables when only the type was different. We could replace all those variables with a template, as shown in the following snippet:
template<typename T> constexpr T NewLine = T('\n');
This template can be used as follows:
int main() { std::wstring test = L"demo"; test += NewLine<wchar_t>; std::wcout << test; }
The examples in this section show that the syntax for declaring and using templates is the same whether they represent functions, classes, or variables. This leads us to the next section where we will discuss the types of templates and template terminology.