#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.
Related
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.
Here is the case of using std::swap to interchange values of two arrays.
int arr1[3]={1,2,3};
int arr2[3]={4,5,6};
std::swap(arr1,arr2);
//Then arr1 becomes {4,5,6} and arr2 becomes {1,2,3}
The swap function is declared as
template <class T, size_t N> void swap(T (&a)[N], T (&b)[N])
noexcept (noexcept(swap(*a,*b)));
I am curious about the mechanisms of the size_t N deduction, how is it accomplished? Since a pointer of an int array doesn't have any information about its length.
A reference to array is only allowed to bind to an array of the correct extent. For example a int (&)[10] cannot bind to an int array of size other than 10, nor can it bind to an int*. This is also true when you have a parameter of reference to array type.
When you pass an array to a function and the parameter is not a reference, the argument is decayed to a pointer to the array's first element. But when you pass an array by reference, for the purposes of template parameter deduction, the argument type is not decayed. This is because a reference to array parameter can't bind to a pointer. Since the argument type isn't decayed, the template parameter N can be correctly deduced.
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.
Consider following class of custom array
template <typename element, unsigned int size>
class array
{
private:
element (&data)[size];
public:
array(element (&reference)[size]):data(reference){}
array():data(new element [size]){} // This is where my problem arises
};
Obviously, in initialization list of constructor evaluates the rvalue expression (the new-expression) to rvalue of pointer instead of reference type. So in MSVC (Microsoft Visual Studio 2012 RC) prompts the error about type conversion from element * to element (&)[size].
However, I think that if I can perhaps convert the rvalue from pointer to reference, it should compile. Is there any chance I can make it there in initialization list of constructor? Or perhaps it just all conflicts with the principle of C++ language? I've investiaged into reinterpret_cast, but the only rule I find relevant is
"An lvalue expression of type T1 can be converted to reference to
another type T2. The result is an lvalue or xvalue referring to the
same object as the original lvalue, but with a different type." - cppreference.com
which clearly states that the conversion applies to lvalue expression.
OK, I know what you might think now - why do you need such class of custom array and why don't you just composite pointer instead of the problemtic reference? Well...you always need different approaches to choose before knowning the best don't you?
What's this about C++11 and rvalue references? To get a lvalue expression to the value pointed to by a pointer, just use the unary * operator. It's been part of the language since the beginning. All you have to do here is make sure you get a pointer to an array, rather than a pointer to the first element of the array.
template <typename element, unsigned int size>
class array
{
private:
element (&data)[size];
public:
array() : data(*new element[1][size]) { }
array(element (&reference)[size]) : data(reference) { }
};
As mentioned by Mat in his comment, the parameterless constructor here is a very bad idea, because the memory will not be freed. Hopefully, having a working code sample will allow you to experiment a bit and come to the same conclusion.
You are not at all using R-Value references in this code. There is not a single usage of && r-value symbol.
EDIT:
template <typename element, unsigned int size>
class array
{
private:
element (&data)[size];
static element DefaultArrRef[size];
public:
//array(element (&reference)[size]):data(reference){}
array():data(DefaultArrRef){}
};
template <typename element, unsigned int size>
element array<element,size>::DefaultArrRef[size];
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.