The template facility in C++ doesn't only allow you to parameterise with types (such as the
int
in
std::vector<int>
), but also with values. Non-type template parameters can be of the following types
[1]:
- Integral (or enum) value
- Pointer to object/function
- Reference to object/function
- Pointer to member
I'm going to look at the first of these types - integers - and how template parameter deduction behaves with arrays.
Template parameter deduction is the facility whereby the compiler determines how to instantiate a template when a template parameter is unspecified, e.g:
1 2
|
std::vector<int> vi;
std::sort(vi.begin(), vi.end());
| |
Although we aren't specifying the type of iterator for
std::sort()
to use, the compiler works it out from the parameters we provide.
Array dimensions as template parameters
We can create a function that is templated on an array's dimensions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
#include <iostream>
#include <string>
template<int N>
void fun(std::string s[N])
{
for (int i(0); i < N; ++i)
std::cout << i << ": " << s[i] << std::endl;
}
int main()
{
std::string s[2] = {"hello", "world"};
fun<2>(s);
}
| |
$> ./a.out
0: hello
1: world
|
Note that omitting the explicit template parameter in this implementation, calling in
fun(s)
instead, will yield a build error:
$> g++ broken.cpp
broken.cpp: In function ‘int main()’:
broken.cpp:14:9: error: no matching function for call to ‘fun(std::string [2])’ |
This confused me for some time, since I was under the impression that the template parameter was deducible from the array dimension.
(NB: as an aside, the above would also work if you wrote fun<500>(s)
; I think this is down to the array decaying to a pointer, which can then readily initialise the array parameter.)
Deduction of template parameters from array dimensions
Stroustrup's TCPL states that
[2] "
a compiler can deduce..a non-type template argument, I
, from a template function argument with a type..type[I]
", which implies to me that the above should work fine.
I puzzled for a while over why the parameter couldn't be deduced, and eventually hit on the answer. The standard states that a value of type "array of
N T
" (e.g. "array of 5
int
") can be converted to an rvalue of type "pointer to
T
".
[3] This means that the array size is lost in the instantiation, and as such the value of
N
cannot be deduced, the template instantiation fails, and - in our example above -
fun()
cannot be resolved.
The way to prevent this conversion (known as 'decay') is to declare the function parameter as a
reference to an array by changing
fun(string s[N])
to
fun(string (&s)[N])
:
1 2 3 4 5 6 7 8 9 10 11 12
|
template<int N>
void fun(string (&s)[N])
{
for (int i(0); i < N; ++i)
cout << i << ": " << s[i] << endl;
}
int main()
{
string s[2] = {"hello", "world"};
fun(s);
}
| |
And it works!
Multi-dimensional arrays
Interestingly, although I haven't declared a reference to an array in this alternate implementation with a multidimensional array, it still works fine:
1 2 3 4 5 6 7 8 9 10 11 12
|
template<int N>
void fun(string s[1][N])
{
for (int i(0); i < N; ++i)
cout << i << ": " << s[0][i] << endl;
}
int main()
{
string s[1][2] = {{"hello", "world"}};
fun(s);
}
| |
The reason for this is that array decay does not happen recursively, so in the call to
fun()
,
int[1][2]
decays to a pointer to an array of 2 ints, and no further, therefore still carries the size information.
(NB: I could not find authoritative evidence of this; it may be implicit in that the standard doesn't state that it should happen recursively.)
This article originally appeared at
The other branch.
Footnotes
- This is the list as specified for C++98 and 03 (cf. ISO C++ standard 14882 14.1.4); C++11 has a few additions.
- Stroustrup - The C++ Programming Language, Special Edition; Appendix C.13.4 - Deducing Function Template Arguments
- ISO C++ standard 14882 4.2.1.