-1

I am trying to learn C++ function templates.I am passing an array as pointer to my function template. In that, I am trying to find the size of an array. Here is the function template that I use.

template<typename T>
T* average( T *arr)
{
    T *ansPtr,ans,sum = 0.0;    

    size_t sz = sizeof(arr)/sizeof(arr[0]);
    cout<<"\nSz is "<<sz<<endl;

    for(int i = 0;i < sz; i++)
    {
        sum = sum + arr[i];
    }
    ans = (sum/sz);
    ansPtr = &ans;
    return ansPtr;
}

The cout statement displays the size of arr as 1 even when I am passing the pointer to an array of 5 integers. Now I know this might be a possible duplicate of questions to which I referred earlier but I need a better explanation on this.

Only thing I could come up with is that since templates are invoked at runtime,and sizeof is a compile time operator, compiler just ignores the line

   int sz = sizeof(arr)/sizeof(arr[0]);

since it does not know the exact type of arr until it actually invokes the function. Is it correct or am I missing something over here? Also is it reliable to send pointer to an array to the function templates?

Recker
  • 1,915
  • 25
  • 55
  • 4
    This has nothing to do with function templates, you'll get the same thing with a plain function that takes a pointer. A pointer isn't an array. – Mat Jun 28 '12 at 06:48
  • possible duplicate of [How to find the sizeof(a pointer pointing to an array)](http://stackoverflow.com/questions/492384/how-to-find-the-sizeofa-pointer-pointing-to-an-array) – Bo Persson Jun 28 '12 at 06:56
  • 5
    "templates are invoked at runtime" Wait, **what ?!** – ereOn Jun 28 '12 at 07:06
  • I really need to take compilers class now... – Recker Jun 28 '12 at 07:16
  • @abhinole: not really compilers class, just get a good mental picture of the C++ compilation/runtime model. It's a complicated one so nothing to be ashamed of. – Matthieu M. Jun 28 '12 at 07:51
  • I should correct myself. From wikipedia Templates page.... Templates are considered type-safe; that is, they require type-checking at compile time. Hence, the compiler can determine at compile time whether the type associated with a template definition can perform all of the functions required by that template definition. – Recker Jun 28 '12 at 14:26

4 Answers4

6
 T *arr

This is C++ for "arr is a pointer to T". sizeof(arr) obviously means "size of the pointer arr", not "size of the array arr", for obvious reasons. That's the crucial flaw in that plan.

To get the size of an array, the function needs to operate on arrays, obviously not on pointers. As everyone knows (right?) arrays are not pointers.

Furthermore, an average function should return an average value. But T* is a "pointer to T". An average function should not return a pointer to a value. That is not a value.

Having a pointer return type is not the last offense: returning a pointer to a local variable is the worst of all. Why would you want to steal hotel room keys?

template<typename T, std::size_t sz>
T average( T(&arr)[sz])
{
    T ans,sum = 0.0;    

    cout<<"\nSz is "<<sz<<endl;

    for(int i = 0;i < sz; i++)
    {
        sum = sum + arr[i];
    }
    ans = (sum/sz);
    return ans;
}
Community
  • 1
  • 1
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • Thanks.I should have mentioned that I wanted to return pointer/reference of average value.But it seems that its not recommended. Further while passing an array, should I use T average(T arr[]) so that I can find the size inside of template function? – Recker Jun 28 '12 at 07:04
  • 1
    @abhinole ` T average(T arr[])` and ` T average(T* arr)` are exactly the same to the compiler. – Luchian Grigore Jun 28 '12 at 07:11
  • @abhinole Why do you want to return a pointer or reference to the average value? *The average value doesn't exist beforehand*. It's created in this function. Returning a pointer to it is not going to work well. Returning it by value is the best option. – R. Martinho Fernandes Jun 28 '12 at 07:11
  • Ohh.I understand now.It will go out of scope. – Recker Jun 28 '12 at 07:14
4

If you want to be able to access the size of a passed parameter, you'd have to make that a template parameter, too:

template<typename T, size_t Len>
T average(const T (&arr)[Len])
{
    T sum = T();
    cout<<"\nSz is "<<Len<<endl;
    for(int i = 0;i < Len; i++)
    {
        sum = sum + arr[i];
    }
    return (sum/Len);
}

You can then omit the sizeof, obviously. And you cannot accidentially pas a dynamically allocated array, which is a good thing. On the downside, the template will get instantiated not only once for every type, but once for every size. If you want to avoid duplicating the bulk of the code, you could use a second templated function which accepts pointer and length and returns the average. That could get called from an inline function.

template<typename T>
T average(const T* arr, size_t len)
{
    T sum = T();
    cout<<"\nSz is "<<len<<endl;
    for(int i = 0;i < len; i++)
    {
        sum = sum + arr[i];
    }
    return (sum/len);
}

template<typename T, size_t Len>
inline T average(const T (&arr)[Len])
{
    return average(arr, Len);
}

Also note that returning the address of a variable which is local to the function is a very bad idea, as it will not outlive the function. So better to return a value and let the compiler take care of optimizing away unneccessary copying.

MvG
  • 57,380
  • 22
  • 148
  • 276
  • 1
    For most types that you're going to be averaging, the copy will be cheaper than using a pointer. – James Kanze Jun 28 '12 at 07:26
  • 1
    You could also use `accumulate(arr, arr+len, T())/len` as the implementation of the average function. See the [docs of `accumulate`](http://www.sgi.com/tech/stl/accumulate.html). – MvG Jun 28 '12 at 07:38
3

Arrays decay to pointers when passed as a parameter, so you're effectively getting the size of the pointer. It has nothing to do with templates, it's how the language is designed.

Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
1

Others have pointed out the immediate errors, but IMHO, there are two important points that they haven't addresses. Both of which I would consider errors if they occurred in production code:

First, why aren't you using std::vector? For historical reasons, C style arrays are broken, and generally should be avoided. There are exceptions, but they mostly involve static initialization of static variables. You should never pass C style arrays as a function argument, because they create the sort of problems you have encountered. (It's possible to write functions which can deal with both C style arrays and std::vector efficiently. The function should be a function template, however, which takes two iterators of the template type.)

The second is why aren't you using the functions in the standard library? Your function can be written in basically one line:

template <typename ForwardIterator>
typename ForwardIterator::value_type
average( ForwardIterator begin, ForwardIterator end )
{
    return std::accumulate( begin, end, 
                            typename::ForwardIterator::value_type() )
                / std::distance( begin, end );
}

(This function, of course, isn't reliable for floating point types, where rounding errors can make the results worthless. Floating point raises a whole set of additional issues. And it probably isn't really reliable for the integral types either, because of the risk of overflow. But these are more advanced issues.)

James Kanze
  • 150,581
  • 18
  • 184
  • 329