Consider the following piece of code:
#include <iostream>
#include <typeinfo>
void use_pointer(int *ptr)
{
std::cout << typeid(ptr).name() << std::endl;
}
void use_array(int arr[])
{
std::cout << typeid(arr).name() << std::endl;
}
int main()
{
int *ptr = nullptr;
// std::cout << typeid(int *).name() << std::endl; // output: Pi
int arr[1];
// std::cout << typeid(int[]).name() << std::endl; // output: A_i
use_pointer(arr);
use_array(ptr);
}
Compiling this program using g++ 6.5.0 outputs:
$ g++ -std=c++11 arrays_2.cpp -Wall
$ ./a.out
Pi
Pi
Now, when calling use_pointer(arr) the array is being decayed to a pointer. (Is that correct? The word "decay" is new to me.)
And the C++ Standard says at [conv.array#1]:
An lvalue or rvalue of type “array of N T” or “array of unknown bound
of T” can be converted to a prvalue of type “pointer to T”. The
temporary materialization conversion ([conv.rval]) is applied. The
result is a pointer to the first element of the array.
I think I understand that my "array of int" converts to a "pointer to int". (Is that correct?)
Now, what happens exactly when calling use_array(ptr). Since the type of the parameter in this case is array of int, did the "pointer to int" convert to an "array of int"?
A pointer to the standard would be much appreciated.
Pointers are pointers, and arrays are arrays. However, arrays naturally decays to pointers to their first element. So when you pass the array arr to any of the function you have, it will decay to &arr[0].
Also note that when declaring function arguments, array-notation (like int arr[]) doesn't mean that it's an array, it's still translated by the compiler as a pointer (i.e. int* arr).
Regarding the decay from array to pointer, it can't happen the other way. Once you have a pointer, all you have is that pointer and the single element it points to.
Your code has little to do with pointer decay. Rather, it demonstrates type adjustment of function parameters.
When used as a type of a function parameter, T[] is being adjusted to T*. This is not a decay, not a conversion, it's a rewrite. The program is rewritten to use T*, as if no T[] was ever there in the source code.
To reiterate, there are no function parameters of type T[] in C++. When the programmer writes one, the compiler immediately substitutes T* and forgets that T[] was ever there. This is in contrast with normal arrays and their decay to pointers. There are most definitely arrays in C++ which are very different from pointers. They decay to pointers only in (many but not all) expressions. It's a one way street: a pointer never un-decays to an array
Can a pointer convert to an array during a function call?
No. See the last example when passing by reference.
An array type has a size (that includes all elements). When it decays into a pointer this size information is lost. So there is no way to convert a pointer back into an array (as you don't know how big the array is).
You probably noticed that array and pointer parameters are actually synonyms for each other to the compiler:
> cat b.cpp
void use_pointer(int *ptr)
{}
void use_pointer(int (arr)[])
{}
> g++ b.cpp
b.cpp:8:6: error: redefinition of 'use_pointer'
void use_pointer(int (arr)[])
^
b.cpp:4:6: note: previous definition is here
void use_pointer(int *ptr)
It does not matter to the compiler these are both the same thing.
So when you pass an array as a parameter it will usually decay into a pointer (for the address of the first element of the array).
Now you can pass an array by reference but the syntax is different.
void use_pointerref(int (&arr)[1]) // You have to specify the exact size
{} // And lace the ampersand in there.
But you will notice that a pointer will not bind to this function.
use_arrayref(arr);
use_arrayref(ptr);
> g++ b.cpp
b.cpp:31:5: error: no matching function for call to 'use_arrayref'
use_arrayref(ptr);
^~~~~~~~~~~~
b.cpp:14:6: note: candidate function not viable: no known conversion from
'int *' to 'int (&)[1]' for 1st argument
void use_arrayref(int (&arr)[1])
Some notes:
But let us think about that. C++ semantics are normally pass by value. So if you had succeeded (or should I say if the language allowed) in passing the array you would have made a copy of the array that would have been passed to the function. This is probably not desirable so apssing the pointer is an efficient way of passing by value (though you do . get a type change).
Note that the pass by reference is very limited as it must know the exact type. We usually get around the size issue by making it a template.
template<int S>
void use_pointerref(int (&arr)[S])
{
std::cout << typeid(arr).name() << " Size: " << S << std::endl;
}
Related
I'm new to c++ and I am confused by the idea of passing a pointer array into a function.
Here's the function
void func(int *a){ a[0]=999;}
which is used by the main
int main()
{
int a[5]={1,2,3,4,5};
func(a);
std::cout << a[0]<< std::endl;
}
I understand this works perfectly as the name of an array is just the address of its first element.
Based on my understanding, &a refers to the address of the entire array while a refers to the address of the first element of the array, which should be identical to &a.
However, if I use
int main(){
int a[5]={1,2,3,4,5};
func(&a);
cout<< a[0]<< endl;
}
It returns the compiler error: no known conversion from 'int (*)[10]' to 'int *' for 1st argument; remove &
Could anyone please explain what's going on here?
Case 1
I understand this works perfectly as the name of an array is just the address of its first element.
The above statement is not technically correct. The first example worked because in many contexts(including when passing an array as an argument by value to a function) the array decays to a pointer to its first element due to type decay. This means, in example 1, when you passed a as an argument, there was an implicit array-to-pointer conversion which converted your array a of type int[5] to a pointer to its first element which is the type int*. Thus in example 1, the type of the argument and the type of the parameter matches and example 1 succeeded.
Note also that even though the decayed pointer and the address of the array both have the same value their types are different. The decayed pointer is of type int* while the &a is of type int (*)[5].
Case 2
Could anyone please explain what's going on here?
This is because a is of type int [5] which means &a is of type int (*)[5] which doesn't match with the type of the parameter int*.
That is, when you modified your code, you were passing &a which is of type int (*)[5] but the function parameter is of type int* and hence there is a mismatch in the type of the parameter and the argument you're passing and as there is no implicit conversion from a int (*)[5] to an int* we get the mentioned error saying:
no known conversion from 'int (*)[5]' to 'int *'
I understand this works perfectly as the name of an array is just the address of its first element.
This is a subtly wrong understanding. The array int[10] can implicitly convert to a pointer to (address of) the first element int*. This implicit conversion happens when you call the function that accepts int* parameter and passing int[10] argument.
&a refers to the address of the entire array while a refers to the address of the first element of the array, which should be identical to &a
The value of the pointers is identical. But the type of the pointers is different. One is a pointer to an array int (*)[10] and the other is a pointer to an element of such array int*. Neither type implicitly converts to the other, and hence you cannot pass &a as an argument to the function that expects int*. The type system of the language prevents you from making a mistake. This is explained by the compiler diagnostic message.
See a as a pointer. It points to the first element of the array, a[0] (or *a).
Then &a is the address of the pointer, which is unrelated to the above.
Note that (&a)[0] or *(&a) return the pointer, while (&a)[0][0] or (*(&a))[0] or *((&a)[0]) or **(&a) return the first element of the array.
void func1(int* ptr)
{
printf("func1 :%d\n",++ptr);
}
int main()
{
int arr[4] = {0,1,2,3};
printf("Addr enter code here`f arr: %d\n",arr);
func1(arr); // first way:
func1(&arr); // second way: How this will be different from func1(arr).
}
The difference between &arr and arr is that the type of former is a pointer to an array int (*)[4], and the type of latter is an array int[4] which can decay to pointer to the first element int*. Both the pointer to the array, and the decayed pointer to the first element point to the same address because the first byte of the array is also the first byte of the first element of the array.
The difference between func1(&arr) and func1(arr) is that former is ill-formed because int (*)[4] doesn't implicitly convert to int*. In order to be able to call func1(&arr), you would have to accept a pointer of correct type:
void func1(int (*ptr)[4])
printf("func1 :%d\n",++ptr);
printf("Addr enter code here`f arr: %d\n",arr);
Both of these calls result in undefined behaviour. The type of the variadic argument must match the type required by the format specifier. You used the format specifier %d which requires the argument to be of type int (or similar). The type of the (decayed) argument here is int* which isn't int.
Arrays decay to pointer. Any of those pointers reference the same address in memory. The difference is in the type of this pointer.
int arr[5];
arr and &arr[0] have type pointer to int (int *)
&arr has type of pointer to five elements integer array ( int (*)[5])
We have a function named func1 that accepts a pointer to an int that is, its parameter is int*. Now lets see what is happening in each of the cases.
Case I
In this case we have func1(arr);
Here the variable arr is an array of size 4 with elements of type int (which is int [4]) but it decays to a pointer to the first element(which is int*) due to type decay.
So when you wrote,
func1(arr);
essentially you're passing an int* as an argument which matches with the type of the function parameter.
Case II
In this case we have func1(&arr);
Here &arr means a pointer to an array of size 4 with elements of type int.(which is int (*)[4]).
Now when you wrote,
func1(&arr);
essentially you're passing an int (*)[4] as an argument to the function func1. But note that the parameter of func1 is of type int*. So there is mismatch in type of argument and parameter.
Now in the screenshot you posted, the program seems to be compiling and executed successfully. But note that since in the second case there is mismatch in argument and parmeter type you will get undefined behavior.
Undefined behavior means anything1 can happen including but not limited to the program giving your expected output. But never rely on the output of a program that has undefined behavior.
1For a more technically accurate definition of undefined behavior see this where it is mentioned that: there are no restrictions on the behavior of the program.
Trying to figure out string literal types in C/CPP
printf("%s\n", typeid("abc").name());
printf("%s\n", typeid(const char[]).name());
print
A4_c
A_c
Not familiar with C, is different length/capacity of array means different type in C? if yes, why could we pass char[n] as char[] when passing function parameters?
is different length/capacity of array means different type in C?
Yes.
if yes, why could we pass char[n] as char[] when passing function parameters?
It is actually not possible to accept an array value as a function argument. When a function argument is declared to be an array type, that declaration is adjusted by the language to mean a pointer to an element of that array type. I.e. array of char becomes pointer to char. Same applies to return types. Example:
void function(char argument[N]); // this
void function(char *argument); // actually means this
Similarly, when an array name is used as a value argument, that array name implicitly converts to a pointer to the first element of the array. This implicit conversion is called decaying. Example:
void function(char *argument);
char array[N];
function(array); // array decays to pointer to first element
Note that this adjustment is only applied to "toplevel" arrays. Pointers to arrays and references to arrays are not adjusted to be pointers or references to pointers to element of that array even in function argument declarations.
What's the difference between char[] and char[n] in C/CPP?
char[n] is an array type. It is an array of n elements. It is a complete type. It is possible to create arrays of this type.
char[] is an array of unknown bound. It is an incomplete type. It is not possible to create an array of this type. This type can only be used in contexts where it is adjusted to another type. In a function declaration, it is adjusted to a pointer to element.
In a declaration of a non-argument array, it is adjusted to the the actual array type of known bound that is deduced from an initialiser:
char arr[] = {'1', '\0'}; // type of arr is adjusted to char[2]
char arr[]; // ill-formed declaration
Since the typeid().name() output varies from compiler to compiler, piping output to c++filt is a better way to see the type's name.
This code:
#include <iostream>
int main(void)
{
std::cout << typeid("abc").name() << std::endl;
std::cout << typeid(const char[]).name() << std::endl;
return (0);
}
compiled and ran as ./a.out outputs:
A4_c
A_c
but when ran as ./a.out | c++filt
outputs the following:
char [4]
char []
For char[] vs char[4] its minimal, but for big nested types and auto in later standards of C++ it becomes a real handy tool for seeing whats happening under the hood.
(Sorry for not being a direct answer but oh how I cringe to see unformatted typeid names in any context, especially since they are compiler specific)
I am playing around with pointers, arrays of pointers, and arrays.
I created a struct called Fraction and tried passing an array of Fraction into a function that takes an array of Fraction pointers. I get the error:
Error 1 error C2664: 'void display(Fraction *[],int)' : cannot convert
argument 1 from 'Fraction (*)[10]' to 'Fraction *[]'
I understand that this does not work, but what you you call each of these? Fraction(*)[] and Fraction*[]. for example: int[] is an array of integers, and an int* is an integer pointer. The above code, looks identical to me aside from the parenthesis around the *.
I do not need to fix this error I just simply want to understand the differences between the two, seemingly similar structures.
Cause of the error
The parameter Fraction *fracArr[] expects an array of pointers to fractions.
You have defined Fraction farry[max_size]; meaning that farry is an array of fractions.
When you call the function providing &farry as first argument, you are trying to take a pointer to an array (Fraction (*)[10]) instead of an array of pointers (Fraction *[]). Therefore the mismatch error.
Solution
If your goal is to work with an array of fractions, just change your function as follows:
void display(Fraction fracArr[], int fracCounter){
for (int i = 0; i < fracCounter; i++){
cout << fracArr[i].num << "/" << fracArr[i].den << endl;
}
}
and call it with display(farry, fracCounter);.
Additional remarks:
More generally, an argument of type array of unknown size T arg[] is passed as a pointer T *arg pointing to the first element.
Defining your argument Fraction *arg[] or Fraction **arg would result in the same code. The [] just hides this technical detail and make the intent clearer (i.e. working with an array of pointers vs. working with a pointer to pointer)
Fraction*[] is an array of Fraction* (an array of pointers). Fraction(*)[] is a pointer to Fraction[] (pointer to an array). The difference is that parentheses isolate the "pointer" from the Fraction, because otherwise the two would bind to each other and give you a different type than intended.
Mechanically, a * or a & would much rather bind to a type name than be isolated and represent the entire thing, so you have to use parentheses to isolate it from the element type. This is also true when declaring function pointers: int*(int, int) is a function that takes two ints and returns an int*, while int(*)(int, int) is a pointer to a function that takes two ints and returns an int.
Consider this simple program:
#include <iostream>
#include <typeinfo>
struct Type {};
// 1: Array of Type*.
void func(Type *arr [3]) {
std::cout << "Type* array.\n"
<< typeid(arr).name() << "\n\n";
}
// 2: Array of Type&.
// Illegal.
// void func(Type &arr [3]) {
// std::cout << "Type& array.\n"
// << typeid(arr).name() << "\n\n";
// }
// 3: Pointer to array of Type.
void func(Type (*arr) [3]) {
std::cout << "Pointer to Type array.\n"
<< typeid(arr).name() << "\n\n";
}
// 4: Reference to array of Type.
void func(Type (&arr) [3]) {
std::cout << "Reference to Type array.\n"
<< typeid(arr).name() << "\n\n";
}
int main() {
// Array of Type.
Type t_arr[3] = {};
// Array of Type*.
Type* tp_arr[3] = { &t_arr[0], &t_arr[1], &t_arr[2] };
// Array of Type&.
// Illegal.
// Type& tr_arr[3] = { t_arr[0], t_arr[1], t_arr[2] };
std::cout << "Type[3]: " << typeid(t_arr).name() << "\n\n";
func(t_arr); // Calls #4.
func(&t_arr); // Calls #3.
func(tp_arr); // Calls #1.
}
Depending on the compiler used, it'll output either mangled or unmangled types for arr, and the output shows that all three are different types:
// MSVC:
Type[3]: struct Type [3]
Reference to Type array.
struct Type [3]
Pointer to Type array.
struct Type (*)[3]
Type* array.
struct Type * *
// GCC:
Type[3]: A3_4Type
Reference to Type array.
A3_4Type
Pointer to Type array.
PA3_4Type
Type* array.
PP4Type
This syntax is a bit wonky if you're not used to it, and can be somewhat easy to mistype, so it may be a good idea to make a type alias if you need to use it.
// Array.
typedef Type Type_arr_t[3];
// Pointer.
typedef Type (*Type_arr_ptr_t)[3];
// Reference.
typedef Type (&Type_arr_ref_t)[3];
// ...
// Without typedefs.
Type arr [3];
Type (*arr_p)[3] = &arr;
Type (&arr_r)[3] = arr;
// With typedefs.
Type_arr_t arr2;
Type_arr_ptr_t arr2_p = &arr2;
Type_arr_ref_t arr2_r = arr2;
This is extremely useful when declaring functions that return pointers or references to arrays, because they look silly without typedefs, and are really easy to get wrong and/or forget the syntax for.
typedef Type (*Type_arr_ptr_t)[3];
typedef Type (&Type_arr_ref_t)[3];
// Without typedefs.
Type (*return_ptr())[3];
Type (&return_ref())[3];
// With typedefs.
Type_arr_ptr_t return_ptr_2();
Type_arr_ref_t return_ref_2();
For more information regarding how to parse something like this, see the clockwise spiral rule.
Note: When an array is passed by value as a function parameter, and in many other situations (specifically, in any situation where an array isn't expected, but a pointer is), type and dimension information is lost, and it is implicitly converted to a pointer to the first element of the array; this is known as the array decaying into a pointer. This is demonstrated in func(Type*[3]) above, where the compiler takes a parameter type of Type*[3], an array of Type*, and replaces it with Type**, a pointer to Type*; the [3] is lost, and replaced with a simple *, because functions can take pointers but not arrays. When func() is called, the array will decay due to this. Due to this, the following signatures are treated as identical, with the parameter being Type** in all three.
void func(Type*[3]);
void func(Type*[] ); // Dimension isn't needed, since it'll be replaced anyways.
void func(Type** );
This is done because it's more efficient than trying to pass the entire array by value (it only needs to pass a pointer, which easily fits inside a single register, instead of trying to load the entire thing into memory), and because encoding the array type into the function's parameter list would remove any flexibility from the function regarding the size of the array it can take (if a function were to take Type[3], then you couldn't pass it a Type[4] or a Type[2]). Due to this, the compiler will silently replace a Type[N] or Type[] with a Type*, causing the array to decay when passed. This can be avoided by specifically taking a pointer or reference to the array; while this is as efficient as letting the array decay (the former because it still just passes a pointer, the latter because most compilers implement references with pointers), it loses out on flexibility (which is why it's usually paired with templates, which restore the flexibility, without removing any of the strictness).
// Will take any pointer to a Type array, and replace N with the number of elements.
// Compiler will generate a distinct version of `func()` for each unique N.
template<size_t N>
void func(Type (*)[N]);
// Will take any reference to a Type array, and replace N with the number of elements.
// Compiler will generate a distinct version of `func()` for each unique N.
template<size_t N>
void func(Type (&)[N]);
Note, however, that C doesn't have the luxury of templates, and thus any code that is intended to work with both languages should either use the C idiom of passing a "size" parameter along with the array, or be written specifically for a certain size of array; the former is more flexible, while the latter is useful if you will never need to take an array of any other size.
void func1(Type *arr, size_t sz);
void func2(Type (*arr)[3]);
Also note that there are situations where an array won't decay into a pointer.
// Example array.
Type arr[3];
// Function parameter.
void func(Type arr[3]);
void func(Type (*arr)[3]);
void func(Type (&arr)[3]);
// Function template parameter.
template<typename T>
void temp(T t);
// Class template parameter.
template<typename T>
struct S { typedef T type; };
// Specialised class template parameter.
template<typename T> struct S2;
template<typename T, size_t Sz>
struct S2<T[Sz]> { typedef T type[Sz]; };
func(arr); // C: arr decays into Type*.
// C++: arr either binds to a Type(&)[3], or decays into Type*.
// If both are available, causes error due to ambiguous function call.
func(&arr); // C/C++: No decay, &arr is Type(*)[3].
sizeof(arr); // C/C++: No decay, evaluates to (sizeof(Type) * 3).
alignof(arr); // C/C++: No decay, evaluates to alignof(Type).
decltype(arr); // C++: No decay, evaluates to Type[3].
typeid(arr); // C++: No decay, evaluates to a std::type_info for Type[3].
for (Type& t : arr); // C++: No decay, ranged-based for accepts arrays.
temp(arr); // C++: arr decays to Type* during function template deduction.
temp<Type[3]>(arr); // C++: No decay, deduction isn't required.
// For class templates, deduction isn't performed, so array types used as template parameters
// don't decay.
S<Type[3]>::type; // C++: No decay, type is Type[3].
S2<Type[3]>::type; // C++: No decay, type is Type[3].
// String literals are arrays, too.
decltype("Hello."); // C++: No decay, evaluates to const char[7].
char c_arr[] = "Hello."; // C/C++: No decay, c_arr is a local array, of type char[7],
// containing copy of "Hello."
const char* c_ptr = "Hello."; // C/C++: const char[7] "Hello." is stored in read-only
// memory, and ptr points to it.
// There may be other cases in which arrays don't decay, which I'm currently not aware of.
So, in short, while, say, Type[3] is an array type, and Fraction*[5] is an array type, there are cases where a declaration of the two will be silently replaced with a Type* or Fraction**, respectively, by the compiler, and where type and dimension information will be lost due to this; this loss is known as array decay or array-to-pointer decay.
Thanks go to juanchopanza for reminding me to mention array-to-pointer decay.
This is one of these places where the compiler outputting a raw type versus a parameter declaration causes a little confusion. If you re-insert the variable names, the comparison is now between:
Fraction (*farray)[10]
and:
Fraction *farray[]
At this point the error becomes obvious if you are willing to accept that declarations have a precedence just like regular expressions.
According to C/C++'s precedence table, [] as the array index operator binds more tightly than unary * the pointer de-reference operator.
If you apply this same rule to the declarations, the second becomes an array of pointers, while the first one has the "pointer" bound more tightly due to the parentheses, therefore it is a pointer to an array.
Consider the code:
void foo(char a[]){
a++; // works fine, gets compiled
//...
}
Now, consider this:
void foo(){
char a[50];
a++; // Compiler error
//...
}
I heard an array is equivalent to a constant pointer and can't be incremented as it is not a lvalue...
Then why does first code gets compiled, is it so because array arguments to functions are passed as a pointer, i.e. T[] is converted to T* for passing..
So, foo(a) passes a as a pointer.
But is it not back converted to T[] again because is declared as:
void foo(char a[]);
When you pass an array as an argument to a function, it decays to a pointer.
So the thing you increment inside the function body is a pointer, not an array.
This is a rather unfortunate feature inherited from the C language, with the rather yucky name of "decay." Since C once did not allow passing compound types by value, they decided to allow programmers to specify arrays as function parameter types, but only cosmetically. The array type decays to a pointer type, implementing a sort of pass-by-reference semantic different from the rest of the language. Ugly.
To recap (and others have already said this), the signature
void foo(char a[]); // asking for trouble
is unceremoniously mangled into
void foo(char *a);
… all for the sake of compatibility with ancient C code. Since you aren't writing ancient C code, you should not make use of this "feature."
However, you can cleanly pass a reference to an array. C++ requires that the size of the array be known:
void foo( char (&a)[ 50 ] );
Now a cannot be modified within the function (edit: its contents can, of course — you know what I mean), and only arrays of the right size may be passed. For everything else, pass a pointer or a higher-level type.
I heard an array is equivalent to a constant pointer
You can think of it that way, but they're not equivalent.
An array decays to a pointer when passed to a function, that's why inside the function it's valid.
Just because the signature is void foo(char a[]) doesn't make a an array.
Outside the function, it's just an array, and you can't do pointer arithmetics on it.
In C++, any function parameter of type "array of T" is adjusted to be "pointer to T". Try this code:
void foo(char a[]) {}
void foo(char* a) {} //error: redefinition
They are indeed the same function.
So, why can we pass an array argument to a function as a pointer parameter? Not because an array is equivalent to a constant pointer, but because an array type can be implicitly converted to an rvalue of pointer type. Also note the result of the conversion is an rvalue, that's why you can't apply the operator ++ to an array, you can only apply this operator to an lvalue.
I heard an array is equivalent to a constant pointer and can't be incremented as it is not a lvalue...
Almost.
An array expression is a non-modifiable lvalue; it may not be an operand to operators such as ++ or --, and it may not be the target of an assignment expression. This is not the same thing as a constant pointer (that is, a pointer declared as T * const).
An array expression will be replaced with a pointer expression whose value is the address of the first element of the array except when the array expression is an operand of the sizeof or unary & operators, or when the array expression is a string literal being used to initialize another array in a declaration.
When you call a function with an array argument, such as
int a[N];
...
foo(a);
the expression a is converted from type "N-element array of int" to "pointer to int" and this pointer value is what gets passed to foo; thus, the corresponding function prototype should be
void foo (int *arr) {...}
Note that in the context of a function parameter declaration, T a[] and T a[N] are identical to T *a; in all three cases, a is declared as a pointer to T. Within the function foo, the parameter arr is a pointer expression, which is a modifiable lvalue, and as such it may be assigned to and may be the operand of the ++ and -- operators.
Remember that all these conversions are on the array expression; that is, the array identifier or other expression that refers to the array object in memory. The array object (the chunk of memory holding the array values) is not converted.
When you pass an array a[] to a function, it passes the value of 'a', an address, into the function. so you can use it as a pointer in the function. If you declare an array in the function, 'a' is constant because you can't change its address in memory.