Reputation: 93
The below code just works fine for me.
#include <iostream>
using namespace std;
template<class T>
T sum_array(T (&a)[10], int size)
{
T result=0;
for(int i=0; i<size; i++)
{
result = a[i] + result;
}
return result;
}
int main()
{
int a[10] = {0,1,2,3,4,5,6,7,8,9};
cout<<sum_array(a, 10)<<endl;
double d[10] = {1.1,1.1,1.1,1.1,1.1,1.1,1.1,1.1,1.1,1.1};
cout<<sum_array(d, 10)<<endl;
cin.get();
}
But if try to make my function more generic by removing the array size as shown below in function it gives a error saying no instance of function template.
template<class T>
T sum_array(T (&a)[], int size)
{
T result=0;
for(int i=0; i<size; i++)
{
result = a[i] + result;
}
return result;
}
At the same time if i remove the reference as shown below it just works fine.
template<class T>
T sum_array(T a[], int size)
{
T result=0;
for(int i=0; i<size; i++)
{
result = a[i] + result;
}
return result;
}
I am relatively new to templates can you please explain the above behavior.
Upvotes: 9
Views: 10231
Reputation: 2776
In order to gives references about what other said see template argument deduction:
Before deduction begins, the following adjustments to P and A are made:
- If P is not a reference type,
a) if A is an array type, A is replaced by the pointer type obtained from array-to-pointer conversion;
b) otherwise, if A is a function type, A is replaced by the pointer type obtained from function-to-pointer conversion;
c) otherwise, if A is a cv-qualified type, the top-level cv-qualifiers are ignored for deduction:
template<class T> void f(T);
int a[3];
f(a); // P = T, A = int[3], adjusted to int*: deduced T = int*
void b(int);
f(b); // P = T, A = void(int), adjusted to void(*)(int): deduced T = void(*)(int)
const int c = 13;
f(c); // P = T, A = const int, adjusted to int: deduced T = int
If P is a cv-qualified type, the top-level cv-qualifiers are ignored for deduction.
If P is a reference type, the referenced type is used for deduction.
If P is an rvalue reference to a cv-unqualified template parameter (so-called forwarding reference), and the corresponding function call argument is an lvalue, the type lvalue reference to A is used in place of A for deduction (Note: this is the basis for the action of std::forward Note: in class template argument deduction, template parameter of a class template is never a forwarding reference (since C++17)):
... Basically, 1-a) means that trying to pass an array by value triggers array to pointer conversion (decay), loosing the static size information, while 3 says that passing by reference is keeping the original full type, with its size. 1-a)
By the way, the same seem to happen in non-template context (needing someone to provide a reference, maybe there: Array-to-pointer conversion).
Here is a possible illustration:
#include <iostream>
template <size_t N>
constexpr size_t Size(const char [N]) {
return N;
}
template <size_t N>
constexpr size_t Size2(const char (&)[N]) {
return N;
}
void Test(const char [5]) {
std::cout << "Passing array by value" << std::endl;
}
template<typename T>
void Test2(const T [3]) {
std::cout << "Template passing array by value" << std::endl;
}
void Testr(const char (&)[5]) {
std::cout << "Passing array by reference" << std::endl;
}
template<typename T>
void Testr2(const T (&)[3]) {
std::cout << "Template passing array by reference" << std::endl;
}
int main() {
// pointer to array decay, N cannot be deduced
// std::cout << Size("Test") << std::endl;
// reference to "sized" array, N can be deduced
std::cout << Size2("Test") << std::endl;
// also pointer to array decay, even in non template context, size provided in signature is not used
Test("Test");
Test("TestTest");
// pointer to array decay, size provided in signature is not used
Test2("Test");
Test2("TestTest");
Testr("Test");
// reference to "sized" array, size provided in signature is checked
// Testr("TestTest");
// Testr2("Test");
return 0;
}
Upvotes: 0
Reputation: 171177
In funciton parameters, []
(without a dimension inside) is just alternate syntax for a pointer, as arrays decay to pointers when passed into functions, unless they're passed by reference.
This means that your working generalised template (the one with T a[]
), is exactly the same as T a*
. If you're passing the size in at runtime anyway, all is fine and you can just use that (and it will work for other things not declared as arrays, such as the return value of std::string::c_str()
).
However, if you want to generalise the tempalte but still keep it limited to actual arrays, you can do this:
template<class T, size_t N>
T sum_array(T (&a)[N], int size)
{
T result=0;
for(int i=0; i<size; i++)
{
result = a[i] + result;
}
return result;
}
That way, only a genuine array can be passed in, but both its type T
and its length N
will be deduced. Depending on your use case, you might get away with removing the size
parameter in such case.
Upvotes: 12
Reputation: 154035
If you want to bind an array by reference, you will absolutely need to know the array's size. You can have the compiler deduce the size, however. Assuming the logic in your code is non-trivial, it is a good idea to immediately delegate to version which is independent of the array's size. Here is an example:
template<typename T>
T sum_array(T const* a, int size)
{
return std::accumulate(a, a + size, T());
}
template <typename T, int Size>
T sum_array(T const (&array)[Size]) {
return sum_array(array, Size);
}
Of course, I couldn't resist to also use std::accumulate()
from <numeric>
: if there is an algorithm for this, it is a good idea to use it.
Since you wondered about removing the reference from the array: when using T[]
for the type of a function parameter, it is equivalent to using T*
. Even if you'd use T[10]
for the type of a function parameter, the compiler would read it as T*
.
Upvotes: 2