Using template parameter values
The templates defined so far have had types as the parameters of the template, but you can also provide integer values. The following is a rather contrived example to illustrate the point:
template<int size, typename T>
T* init(T t)
{
T* arr = new T[size];
for (int i = 0; i < size; ++i) arr[i] = t;
return arr;
}
There are two template parameters. The second parameter provides the name of a type where T is a placeholder used for the type of the parameter of the function. The first parameter looks like a function parameter because it is used in a similar way. The parameter size can be used in the function as a local (read-only) variable. The function parameter is T and so the compiler can deduce the second template parameter from the function call, but it cannot deduce the first parameter, so you must provide a value in the call. Here is an example of calling this template function for an int for T and a value of 10 for size:
int *i10 = init<10>(42);
for (int i = 0; i < 10; ++i) cout << i10[i] << ' ';
cout << endl;
delete [] i10;
The first line calls the function with 10 as the template parameter and 42 as the function parameter. Since 42 is an int, the init function will create an int array with ten members and each one is initialized to a value of 42. The compiler deduced int as the second parameter, but this code could have called the function with init<10,int>(42) to explicitly indicate that you require an int array.
The non-type parameters must be constant at compile time: the value can be integral (including an enumeration), but not a floating point. You can use arrays of integers, but these will be available through the template parameter as a pointer.
Although in most cases the compiler cannot deduce the value parameter, it can if the value is defined as the size of an array. This can be used to make it appear that a function can determine the size of a built-in array, but of course, it can't because the compiler will create a version of the function for each size needed. For example:
template<typename T, int N> void print_array(T (&arr)[N])
{
for (int i = 0; i < N; ++i)
{
cout << arr[i] << endl;
}
}
Here, there are two template parameters: one is the type of the array, and the other is the size of the array. The parameter of the function looks a little odd, but it is just a built-in array being passed by a reference. If the parentheses are not used then the parameter is T& arr[N], that is, an N-sized built-in array of references to objects of type T, which is not what we want. We want an N-sized built-in array objects of type T. This function is called like this:
int squares[] = { 1, 4, 9, 16, 25 };
print_array(squares);
The interesting thing about the preceding code is that the compiler sees that there are five items in the initializer list. The built-in array has five items, thus calls the function like this:
print_array<int,5>(squares);
As mentioned, the compiler will instantiate this function for every combination of T and N that your code calls. If the template function has a large amount of code, then this may be an issue. One way around this is to use a helper function:
template<typename T> void print_array(T* arr, int size)
{
for (int i = 0; i < size; ++i)
{
cout << arr[i] << endl;
}
}
template<typename T, int N> inline void print_array(T (&arr)[N])
{
print_array(arr, N);
}
This does two things. First, there is a version of print_array that takes a pointer and the number of items that the pointer points to. This means that the size parameter is determined at runtime, so versions of this function are only instantiated at compile time for the types of the arrays used, not for both type and array size. The second thing to note is that the function that is templated with the size of the array is declared as inline and it calls the first version of the function. Although there will be a version of this for each combination of type and array size, the instantiation will be inline rather than a complete function.