What does the parameter printArray(int (&a)[n][m]) mean? Why are the parentheses necessary and why does only 1 value need to be provided to the printArray function? how does the function know n and m when called?
template <size_t n, size_t m>
void printArray(int (&a)[n][m]) {
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
cout << a[i][j] << " ";
}
cout << endl;
}
}
int main(int argc, char* argv[])
{
cout << "Example I:" << endl;
int ab[2][5];
printArray(ab);
cout << "Example II:" << endl;
int b[2][5] = {{1, 2, 3}};
printArray(b);
cout << "Example III:"<< endl;
int c[][5] = {1, 2, 3, 4, 5, 6, 7};
printArray(c);
cout << "Example IV:" << endl;
int d[][5] = {{1, 2, 3, 4}, {5, 6}, {7}};
printArray(d);
}
What does the parameter printArray(int (&a)[n][m]) mean?
It means that a is a reference to an object of type int[n][m]. int[n][m] is an array of m objects of type int[n]. int[n] is an array of n objects of type int. So, a is a reference to a 2d array with dimensions n and m.
n and m are template arguments that were declared in template <size_t n, size_t m>. The type of both argument is size_t, which is an integer type.
Why are the parentheses necessary
Because the &-token binds to left. int& is a reference to int. int& a[n] would syntactically mean that a is an array of references (such arrays are not allowed though). The parentheses are used to disambiguate whether the &-token declares an (array of) reference to int, or a reference to an array.
and why does only 1 value need to be provided to the printArray function?
There is exactly one argument to the function: a. If you pass a value that can be bound to an array reference of appropriate type, then it works. In all your examples the arguments are 2d arrays of integers, so they are correct.
how does the function know n and m when called?
The compiler knows because size of an array is part of the type of the array. And because of template argument deduction. When a template argument is not specified explicitly, it may be deduced from the arguments of the function. In this case, if you pass an argument of type int[2][5], then n is deduced to be 2 and m is deduced to be 5.
You could even add a template type argument and let that be deduced:
template <size_t n, size_t m, typename T>
void printArray(T (&a)[n][m])
T would be deduced to be int, if you were to pass a 2d array of integers.
if an array of references isn't allowed, why can't the compiler deduce that the parenthesis is not necessary.
If int &a[n] would mean a reference to an array, because there can't be arrays of references, then it would confuse programmers that int *a[n] is not a pointer to an array, because there can be arrays of pointers.
Besides, this would complicate the language by adding a special case for references that is unnecessary.
why isn't the form: printArray(int[n][m] &a)
More simply, why can't arrays be declared by int[n] a instead of int a[n]. Because the latter syntax was chosen by (presumably Dennis Ritchie when he was) designer of the C language.
It's simply taking an array by reference. As far as usage of the passed array goes inside the function, you typically will write exactly the same code as you would have otherwise. The only difference is that it preserves its array type.
When you pass arrays "by value" to a function it's actually decayed into a pointer to its first element. Passing it by reference (or by address) prevents this decay in this usage.
In fact, even if you specify an extent for the first dimension in the parameter, the compiler will ignore it if the array is passed "by value".
This is a template programming trick to deduce the extent of the first dimension, which would otherwise by lost if you had written:
template<size_t m>
void printArray(int a[][m]) {...}
Thus the alternative would have been something less tidy like:
template<size_t m>
void printArray(int a[][m], size_t n) {...}
The parenthesis are required because int &a[n][m] is a 2D array of references, which is illegal because you cannot make an array of reference. The parenthesis in int (&a)[n][m] makes this "reference to nxm array of int".
Related
I want to pass a 2d array to a function, and I know how to do it
int function(int a[][10])
The problem is, I dont like working with stuff I dont understand, so I would like to understand why do we have to use "int a[][10]" instead of "int a[10][10]".
First the function parameter a is a pointer to an array of size 10 having elements of type int. That is, the parameter a is not an array as you might be thinking.
This is called type decay as quoted below:
Except when it is the operand of the sizeof operator or the unary & operator, or is a string literal used to initialize an array, an expression that has type ‘‘array of type’’ is converted to an expression with type ‘‘pointer to type’’ that points to the initial element of the array object and is not an lvalue.
You asked
why do we have to use "int a[][10]" instead of "int a[10][10]".
It doesn't matter if you use int a[][10] or int a[10][10] because in both cases the parameter a is a pointer to an array of size 10 having elements of type int.
If you want to pass a 1D array of int elements you can use templates as shown below in version 1:
Version 1: Pass 1D array by reference
#include <iostream>
template<int N>
int function(int (&a)[N])// a is a reference to a 1D array of size N having elements of type int
{
return 5; //return something according to your needs
}
int main()
{
int arr[3] = {0};
function(arr);
return 0;
}
Version 2: Pass a 2D array by reference
#include <iostream>
template<int N, int M>
int function(int (&a)[N][M])// a is a reference to a 2D array
{
return 5; //return something according to your needs
}
int main()
{
int arr[10][10];
function(arr);
return 0;
}
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.
Apparently, we can pass complex class instances to functions, but why can't we pass arrays to functions?
The origin is historical. The problem is that the rule "arrays decay into pointers, when passed to a function" is simple.
Copying arrays would be kind of complicated and not very clear, since the behavior would change for different parameters and different function declarations.
Note that you can still do an indirect pass by value:
struct A { int arr[2]; };
void func(struct A);
Here's another perspective: There isn't a single type "array" in C. Rather, T[N] is a a different type for every N. So T[1], T[2], etc., are all different types.
In C there's no function overloading, and so the only sensible thing you could have allowed would be a function that takes (or returns) a single type of array:
void foo(int a[3]); // hypothetical
Presumably, that was just considered far less useful than the actual decision to make all arrays decay into a pointer to the first element and require the user to communicate the size by other means. After all, the above could be rewritten as:
void foo(int * a)
{
static const unsigned int N = 3;
/* ... */
}
So there's no loss of expressive power, but a huge gain in generality.
Note that this isn't any different in C++, but template-driven code generation allows you to write a templated function foo(T (&a)[N]), where N is deduced for you -- but this just means that you can create a whole family of distinct, different functions, one for each value of N.
As an extreme case, imagine that you would need two functions print6(const char[6]) and print12(const char[12]) to say print6("Hello") and print12("Hello World") if you didn't want to decay arrays to pointers, or otherwise you'd have to add an explicit conversion, print_p((const char*)"Hello World").
Answering a very old question, as Question is market with C++ just adding for completion purposes, we can use std::array and pass arrays to functions by value or by reference which gives protection against accessing out of bound indexes:
below is sample:
#include <iostream>
#include <array>
//pass array by reference
template<size_t N>
void fill_array(std::array<int, N>& arr){
for(int idx = 0; idx < arr.size(); ++idx)
arr[idx] = idx*idx;
}
//pass array by value
template<size_t N>
void print_array(std::array<int, N> arr){
for(int idx = 0; idx < arr.size(); ++idx)
std::cout << arr[idx] << std::endl;
}
int main()
{
std::array<int, 5> arr;
fill_array(arr);
print_array(arr);
//use different size
std::array<int, 10> arr2;
fill_array(arr2);
print_array(arr2);
}
The reason you can't pass an array by value is because there is no specific way to track an array's size such that the function invocation logic would know how much memory to allocate and what to copy. You can pass a class instance because classes have constructors. Arrays do not.
Summery:
Passing the Address of the array's first element &a = a = &(a[0])
New Pointer (new pointer, new address, 4 bytes, in the memory)
Points to the same memory location, in different type.
Example 1:
void by_value(bool* arr) // pointer_value passed by value
{
arr[1] = true;
arr = NULL; // temporary pointer that points to original array
}
int main()
{
bool a[3] = {};
cout << a[1] << endl; // 0
by_value(a);
cout << a[1] << endl; // 1 !!!
}
Addresses:
[main]
a = 0046FB18 // **Original**
&a = 0046FB18 // **Original**
[func]
arr = 0046FB18 // **Original**
&arr = 0046FA44 // TempPTR
[func]
arr = NULL
&arr = 0046FA44 // TempPTR
Example 2:
void by_value(bool* arr)
{
cout << &arr << arr; // &arr != arr
}
int main()
{
bool a[3] = {};
cout << &a << a; // &a == a == &a[0]
by_value(arr);
}
Addresses
Prints:
[main] 0046FB18 = 0046FB18
[func] 0046FA44 != 0046FB18
Please Note:
&(required-lvalue): lvalue -to-> rvalue
Array Decay: new pointer (temporary) points to (by value) array address
readmore:
Rvalue
Array Decay
It was done that way in order to preserve syntactical and semantic compatibility with B language, in which arrays were implemented as physical pointers.
A direct answer to this question is given in Dennis Ritchie's "The Development of the C Language", see the "Critique" section. It says
For example, the empty square brackets in the function declaration
int f(a) int a[]; { ... }
are a living fossil, a remnant of NB's way of declaring a pointer; a is, in this special case only, interpreted in C as a pointer. The notation survived in part for the sake of compatibility, in part under the rationalization that it would allow programmers to communicate to their readers an intent to pass f a pointer generated from an array, rather than a reference to a single integer. Unfortunately, it serves as much to confuse the learner as to alert the reader.
This should be taken in the context of the previous part of the article, especially "Embryonic C", which explains how introduction of struct types in C resulted in rejection of B- and BCPL-style approach to implementing arrays (i.e. as ordinary pointers). C switched to non-pointer array implementation, keeping that legacy B-style semantics in function parameter lists only.
So, the current variant of array parameter behavior is a result of a compromise: one the one hand, we had to have copyable arrays in structs, on the other hand, we wanted to preserve semantic compatibility with functions written in B, where arrays are always passed "by pointer".
The equivalent of that would be to first make a copy of the array and then pass it to the function (which can be highly inefficient for large arrays).
Other than that I would say it's for historical reasons, i.e. one could not pass arrays by value in C.
My guess is that the reasoning behind NOT introducing passing arrays by value in C++ was that objects were thought to be moderately sized compared to arrays.
As pointed out by delnan, when using std::vector you can actually pass array-like objects to functions by value.
You are passing by value: the value of the pointer to the array. Remember that using square bracket notation in C is simply shorthand for de-referencing a pointer. ptr[2] means *(ptr+2).
Dropping the brackets gets you a pointer to the array, which can be passed by value to a function:
int x[2] = {1, 2};
int result;
result = DoSomething(x);
See the list of types in the ANSI C spec. Arrays are not primitive types, but constructed from a combination of pointers and operators. (It won't let me put another link, but the construction is described under "Array type derivation".)
actually, a pointer to the array is passed by value, using that pointer inside the called function will give you the feeling that the array is passed by reference which is wrong. try changing the value in the array pointer to point to another array in your function and you will find that the original array was not affected which means that the array is not passed by reference.
Ref. to my last post and sellibitze's comment to that post on passing array by ref rather than by value, why is it that when I'm passing array by value compiler can deduce arguments but it won't do it if I pass it by value?
template<class T,int row, int col>
void invert(T (&a)[row][col]) //NOTE AMPERSAND
in main with declaration above I can call:
int main(int argc, char* argv[])
{
invert(a);//HERE ARGUMETS ARE AUTOMATICALLY DEDUCED
}
but without ampersand I would have to call it like so:
int main(int argc, char* argv[])
{
invert<int,3,4>(a);
}
#Paul So just to make it clear when I'm declaring fnc:
void f(int a[]);//I'm passing a pointer
but when I'm declaring:
void f(int &a[]);//I'm passing a ref?
Do I understand this correctly now?
That's because when you pass an array "by value" it decays to a pointer. That is, you are in fact passing a pointer to the first element without any size information.
When you have a signature like this:
void foo(int arr[10]);
then the value 10 is completely ignored and you can pass arrays of ints of any size to it. It is exactly the same as
void foo(int arr[]);
or
void foo(int* arr);
As you can see the size information is not preserved, and therefore it cannot be used to deduce the size of the array.
With a two-dimensional array, the first dimension decays. For example: an array of 10 arrays of 20 integers (int arr[10][20]) decays to a pointer to arrays of 20 integers (int (*arr)[20]) etc, so the value 10 cannot be deduced but the size of the second dimension (20) is preserved an can be deduced.
template<class T,int row, int col>
void foo(T (&a)[row][col]) { }
template <class T, int col>
void bar(T arr[][col]) {}
int main()
{
int a[10][20];
foo(a);
bar(a);
}
When you pass something by reference, the type is preserved, the arrays don't decay and all the size information will remain available.
You can't pass array by value, only by pointer (when you pass an array to a function it's automatically converted to a pointer).
I don't really understand what you mean by "deduce arguments"; do you mean the total size of the array? If yes then if you pass it by pointer it's lost because pointers don't carry that kind of information.
Anyways I highly recommend to use std::vector rather than plain old C arrays; many less headaches! They are passed by value by default (as you would expect); they can easily passed by reference and by pointer if you want to, and they never lose information such as the array size. They also protected against buffer overflows and underflows, and they automatically grow as you add more elements.
void f(int &a[]); // I'm passing a ref?
No, here you try to pass an array of references, which does not exist in the type system. You might be tempted to write the following:
void f(int (&a)[]);
But references to arrays of unknown bound are not allowed as function parameters.
When you declare a function parameter as an array of n dimensions, it is rewritten by the compiler as a pointer to an array of n-1 dimensions. The following signatures are equivalent:
void fun(int x[][10]);
void fun(int x[2][10]);
void fun(int x[99][10]);
void fun(int (*x)[10]);
The bound of the first dimension is ignored and thus cannot be inferred by the template mechanism.
What you can do is pass a pointer to the entire 2D array:
template <class T, int row, int col>
void invert(T (*a)[row][col])
{
std::cout << row << " x " << col << std::endl;
T first_entry = (*a)[0][0];
}
int main(int argc, char* argv[])
{
int a[10][20];
invert(&a);
}
This works as expected, but as you can see, the syntax is a little clumsy on both sides.
In the following code
#include<iostream>
template<typename T,size_t N>
void cal_size(T (&a)[N])
{
std::cout<<"size of array is: "<<N<<std::endl;
}
int main()
{
int a[]={1,2,3,4,5,6};
int b[]={1};
cal_size(a);
cal_size(b);
}
As expected the size of both the arrays gets printed. But how does N automatically gets initialized to the correct value of the array-size (arrays are being passed by reference)? How is the above code working?
N does not get "initialized" to anything. It is not a variable. It is not an object. N is a compile-time constant. N only exists during compilation. The value of N as well as the actual T is determined by the process called template argument deduction. Both T and N are deduced from the actual type of the argument you pass to your template function.
In the first call the argument type is int[6], so the compiler deduces that T == int and N == 6, generates a separate function for that and calls it. Let's name it cal_size_int_6
void cal_size_int_6(int (&a)[6])
{
std::cout << "size of array is: " << 6 << std::endl;
}
Note that there's no T and no N in this function anymore. Both were replaced by their actual deduced values at compile time.
In the first call the argument type is int[1], so the compiler deduces that T == int and N == 1, generates a separate function for that as well and calls it. Let's name it cal_size_int_1
void cal_size_int_1(int (&a)[1])
{
std::cout << "size of array is: " << 1 << std::endl;
}
Same thing here.
Your main essentially translates into
int main()
{
int a[]={1,2,3,4,5,6};
int b[]={1};
cal_size_int_6(a);
cal_size_int_1(b);
}
In other words, your cal_size template gives birth to two different functions (so called specializations of the original template), each with different values of N (and T) hardcoded into the body. That's how templates work in C++.
It works because the type of a is "array of length 6 of int" and the type of b is "array of length 1 of int". The compiler knows this, so it can call the correct function. In particular, the first call calls the template instance cal_size<6>() and the second call calls cal_size<1>(), since those are the only template instantiations which match their respective arguments.
If you attempted to call an explicit template instance, it would only work if you got the size right, otherwise the arguments wouldn't match. Consider the following:
cal_size(a); // ok, compiler figures out implicitly that N=6
cal_size<int, 6>(a); // also ok, same result as above
cal_size<int, 5>(a); // ERROR: a is not of type "array of length 5 of int"
when you declare int a[] = {1,2,3} it is the same as (or will be rewritten as) int a[3] = {1,2,3} since the templated function is receiving argument in form of T a[N], then N will have value of 3.