This question comes from this one:
c++ pass array to function question
but since the OP accepted an answer I guess nobody will read it now.
I tried this code on g++. It seems that the array does not decay to a pointer when passed to this function (the function returns the proper result):
#include <iostream>
template <typename T>
std::size_t size_of_array (T const & array)
{
return sizeof (array) / sizeof (*array);
}
int main ()
{
int a [5];
std::cout << size_of_array (a) << '\n';
}
Another user (sharptooth) said he have the same behavior on VC++ 10 with inlining off.
Can somebody explain? Thanks.
Array decay doesn't just happen -- it only happens when the program would fail to compile without it. When you pass an array by reference, there simply is no need for decay to kick in.
Note that the function template can also be written without dividing ugly sizeof expressions:
template <typename T, std::size_t N>
std::size_t size_of_array(T (&array)[N])
{
return N;
}
When a client calls size_of_array, T and N are automatically deduced by the template machinery.
You haven't written the function to accept a pointer, you've written it to accept a const reference to exactly the type of argement that's passed to it. Pointer decay only happens if you try to assign to a pointer the value of an array.
Related
#include <iostream>
template <class T>
void call_1(T& in){
printf("%d\n", sizeof(in)); // 12
}
template <int N>
void call_2(int (&in)[N]){
printf("%d\n", sizeof(in)); // 12
}
int main(){
int a[] = {1,2,3};
call_1(a);
call_2(a);
return 0;
}
I have a few questions based on the code snippet above:
1- is call_1 and call_2 both valid ways to pass an entire array by reference?
2- why is T& necessary to have in call_1. If & is omitted it passes a pointer to
the first element of array. Why doesn't T& pass a reference to the first element of the array and instead it references the entire array?
3- Why can't I write template <T N> in call_2 an avoid hard coding int? is this
not a valid syntax?
1. Yes, both are valid.
2. The type of a is int[3]. But arrays in most rvalue contexts, such as when passing by-value, decay to a pointer. The decaying doesn't happen when passing by-reference, so you end up with int(&)[3].
3. Of course you can, but the type of the array dimension is not T, but size_t.
So it should be like this:
template <typename T, std::size_t N>
void call_2(T (&in)[N]){
printf("%d\n", sizeof(in)); // 12
}
1- is call_1 and call_2 both valid ways to pass an entire array by reference?
Yes. 1 also accepts non-array arguments.
2- why is T& necessary to have in call_1
Because without T& you won't have a type for the parameter.
Why doesn't T& pass a reference to the first element of the array and instead it references the entire array?
That's just not how the language works. When a reference is deduced from an array argument, it is deduced to be a reference to an array.
3- Why can't I write template <T N> in call_2 an avoid hard coding int? is this not a valid syntax?
Because you haven't told to the compiler what T is. There's technically no need to hard code the type of the length of the array. You can make it a template parameter like this:
template <class T, T N>
But typically hard-conding it isn't a problem. Note that conventionally, std::size_t is used rather than int.
printf("%d\n", sizeof(in));
%d is not the correct format specifier for std::size_t, so the behaviour of the program is undefined. I recommend using iostreams instead. It's much easier to avoid undefined behaviour with iostreams.
In this MCVE, the compiler complains that processArray can't match the parameter list (arr). The fix is to replace T elements[SIZE] with T (&elements)[SIZE]. Why do I need to do this, and under what circumstance? I wouldn't use & to pass an array into a function ordinarily. (Only reason I thought of it is that's how C++20's new version of istream& operator>> describes its char-array parameter.)
template <typename T, int SIZE>
void processArray(T elements[SIZE])
{
for (int i = 0; i < SIZE; ++i)
elements[i] = 2;
}
int main()
{
int arr[3];
processArray(arr);
return 0;
}
This is because of array decay. Unless you pass an array by reference, it is going to decay into a pointer. That means
void processArray(T elements[SIZE])
is really
void processArray(T* elements)
and there is no way to get what SIZE is for your template since a pointer doesn't know the size of the array it points to.
Once you make the array parameter a reference, you stop this decaying and can get the size out of the array that is passed to the function.
The following code doesn't compile, I am trying to figure out how to calculate the size of an array that is passed into a function and can't seem to get the syntax correct.
The error I am getting is :
Error 1 error C2784: 'size_t getSize(T (&)[SIZE])' : could not deduce template argument for 'T (&)[SIZE]' from 'const byte []' 16 1 sizeofarray
Here is the source code:
#include <cstdint>
#include <stdio.h>
template<typename T, size_t SIZE>
size_t getSize(T (&)[SIZE]) {
return SIZE;
}
typedef std::uint_fast8_t byte;
void processArray(const byte b[])
{
size_t size = getSize(b); // <- line 16 where error occurs
// do some other stuff
}
int main(const int argc, const char* argv[])
{
byte b[] = {1,2,3,4,5,6};
printf("%u\n", getSize(b));
processArray(b);
return 0;
}
If you want this to work, you need to make processArray be a template as well:
template <size_t size>
void processArray(const byte (&b)[size])
{
// do some other stuff
}
C++ does not allow passing arrays by value. If you have a function like this:
void f(int a[5]);
It may look like you are passing an array by value, but the language has a special rule that says a parameter of this form is just another way of saying:
void f(int *a);
So the size of the array is not part of the type at all. This is behavior inhereted from C. Fortunately, C++ has references, and you can pass a reference to an array, like this:
void f(int (&a)[5]);
This way, the size of your array is preserved.
Now, the only remaining trick is to make the function generic, so it can work on any size array.
template <size_t n> void f(int (&a)[n]);
Now, new versions of the function that take references to arrays of different sizes can be generated automatically for you, and the size can be accessed through the template parameter.
As the argument to a function, const byte b[] is treated just like const byte *b. There is no compile-time information about the size of the array that the function was called with.
To pass a reference to the array, you need to make processArray a template and use the same technique. If you don't pass in a reference, the parameter is a pointer, and pointer types don't have array size information.
template<size_t size>
void processArray(const byte (&b)[size]) {
// ...
}
This is the canonical example of why you should use a function template like getSize rather than sizeof, for determining array size.
With sizeof, you'd have gotten the size of a pointer and been none the wiser.
But, this way, you get a compilation error to point out your mistake.
What was the mistake? It was having a function parameter const T arg[] and thinking that this means arg is an array. It's not. This is unfortunate syntax from C that is precisely equivalent to const T* arg. You get a pointer.
This is also why some people think — incorrectly — that "arrays are pointers". They are not. It's just this specific syntax.
The following code returns the size of a stack-allocated array:
template<typename T, int size>
int siz(T (&) [size])
{
return size;
}
but I can't wrap my head around the syntax.
Especially the T (&) [size] part...
but I can't wrap my head around the syntax. Especially the T (&) [size] part...
That part is a reference to an array. There is the "right-left rule" for deciphering any C and C++
declarations.
Because function templates deduce template argument types from the supplied function arguments what this function template does is deduce the type and element count of an array and return the count.
Functions can't accept array types by value, rather only by pointer or reference. The reference is used to avoid the implicit conversion of an array to the pointer to its first element (aka, array decay):
void foo(int*);
int x[10];
int* p = x; // array decay
foo(x); // array decay again
Array decay destroys the original type of the array and hence the size of it gets lost.
Note, that because it is a function call in C++03 the return value is not a compile time constant (i.e. the return value can't be used as a template argument). In C++11 the function can be marked with constexpr to return a compile time constant:
template<typename T, size_t size>
constexpr size_t siz(T(&)[size]) { return size; }
To get the array element count as a compile time constant in C++03 a slightly different form may be used:
template<class T, size_t size>
char(&siz(T(&)[size]))[size]; // no definition required
int main()
{
int x[10];
cout << sizeof siz(x) << '\n';
double y[sizeof siz(x)]; // use as a compile time constant 10
}
In the above it declares a function template with the same reference-to-an-array argument, but with the return value type of char(&)[size] (this is where the "right-left rule" can be appreciated). Note that the function call never happens at run-time, this is why the definition of function template siz is unnecessary. sizeof siz(x) is basically saying "what would be the size of the return value if siz(x) were called".
The old C/C++ way of getting the element count of an array as a compile time constant is:
#define SIZ(arr) (sizeof(arr) / sizeof(*(arr)))
T (&) [size] is a reference to an array. It needs to be a reference because the following program is not legal:
#include <iostream>
int sz(int *) { std::cout << "wtf?" << std::endl; return 0; }
int sz(int [4]) { std::cout << "4" << std::endl; return 0; }
int main() {
int test[4];
sz(test);
}
This program fails to compile with:
test.cc: In function ‘int sz(int*)’:
test.cc:6:5: error: redefinition of ‘int sz(int*)’
test.cc:3:5: error: ‘int sz(int*)’ previously defined here
because int sz(int [4]) is identical to int sz(int *).
The parenthesis are required to disambiguate here because T& [size] looks like an array of references which is otherwise illegal.
Normally if the parameter wasn't anonymous you would write:
template<typename T, int size>
int sz(T (&arr) [size])
To give the array the name arr. In this instance though all your example code cared about was the deduced size and hence the anonymous argument avoids warnings about unused arguments.
It´s an function which becomes a typename (templates can be used with different typenames) and a size from the outside. It then returns this size.
Stack functions often use size, which is an integer number which shows you the size of the stack-size you request with this function. The & tests just which size of stack T is meant.
This question comes from this one:
c++ pass array to function question
but since the OP accepted an answer I guess nobody will read it now.
I tried this code on g++. It seems that the array does not decay to a pointer when passed to this function (the function returns the proper result):
#include <iostream>
template <typename T>
std::size_t size_of_array (T const & array)
{
return sizeof (array) / sizeof (*array);
}
int main ()
{
int a [5];
std::cout << size_of_array (a) << '\n';
}
Another user (sharptooth) said he have the same behavior on VC++ 10 with inlining off.
Can somebody explain? Thanks.
Array decay doesn't just happen -- it only happens when the program would fail to compile without it. When you pass an array by reference, there simply is no need for decay to kick in.
Note that the function template can also be written without dividing ugly sizeof expressions:
template <typename T, std::size_t N>
std::size_t size_of_array(T (&array)[N])
{
return N;
}
When a client calls size_of_array, T and N are automatically deduced by the template machinery.
You haven't written the function to accept a pointer, you've written it to accept a const reference to exactly the type of argement that's passed to it. Pointer decay only happens if you try to assign to a pointer the value of an array.