3

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.

Brett Reinhard
  • 374
  • 2
  • 13
  • 1
    how is farry declared ? – Christophe Nov 01 '16 at 23:17
  • @Christophe Fraction farry[MAX_SIZE]; where max_size is 10 – Brett Reinhard Nov 01 '16 at 23:23
  • Additionally, max size is declared as a const int before main – Brett Reinhard Nov 01 '16 at 23:24
  • You have answered your own question, your `farry` should be declared as `Fraction* farry = new Fraction[MAX_SIZE]` because your function expects a pointer of a pointer and what you are passing is just a pointer to a first element of the array. if you would dereference that farry by using it as `*farry` you would get the first element of the array, if you would declare it as I pointed out you would get a pointer to an array which then points to a first element of an array, simple :-) – XAMlMAX Nov 01 '16 at 23:37
  • @XAMlMAX Do that and things will be even more wrong than they already are. The function as-written expects a sequence of pointers to objects; not a sequence of objects. No allocation of the form `new Fraction[N]` is going to work with that function, *regardless* of whether you pass the pointer referring to those object by address or not. Worse, doing as you describe and passing `&farry` will completely hide the problem until runtime when *undefined behavior* is invoked. – WhozCraig Nov 01 '16 at 23:44
  • @WhozCraig I never said to pass `&farry` to anywhere, I was making a point that the type that he was passing didn't match the argument that was expected by the function. And the function expects a pointer to a pointer if you declare a variable `Fraction** farry` and initialize it properly it would still work for this function. – XAMlMAX Nov 01 '16 at 23:48
  • @XAMlMAX I understand that. You said, "your `farry` should be declared as `Fraction* farry = new Fraction[MAX_SIZE]` because your function expects a pointer of a pointer and what you are passing is just a pointer to a first element of the array." The OP changing their code to do that, and only that, while retaining their call to `display(&farry, fracCounter);` as described in the question (which the compiler will happily accept), is still wrong, but arguably *worse*, because now the problem won't be caught until runtime (if they're lucky). That was my only point. – WhozCraig Nov 01 '16 at 23:59
  • @WhozCraig I see your point now, fair play. – XAMlMAX Nov 02 '16 at 00:01

3 Answers3

5

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)

Christophe
  • 68,716
  • 7
  • 72
  • 138
  • I also have an array of pointers called fracArray, which was declared, Fraction *fracArray[MAX_SIZE]; I was trying to use the display function for both, more as a means to understand what would happen. @xamimax – Brett Reinhard Nov 01 '16 at 23:40
  • @BrettReinhard So if you add `fracArray[0] = Fraction();` is that still array of pointers? – XAMlMAX Nov 01 '16 at 23:43
  • I think I confused that an address of an array of fractions would be the same as an arrays of fraction pointers. Would this be a fair assumption to say that they are not viewed the same by the compiler ? @christophe I understand that an array is by definition an address. – Brett Reinhard Nov 01 '16 at 23:44
  • You can't have the same funtion processing either values or pointer to values. For fracArray it would work as it was, if you just leave the `&` out of the function call (in fact if you define an array, the name of that array without an indice is the address of that array). If you want to use the same function name to do different processes depending on the parameter type, you have to consider making an overload. – Christophe Nov 01 '16 at 23:44
  • I guess that my question more directly should phrased what does this actually mean: Fraction (*)[10]' and what does this mean: 'Fraction *[]' – Brett Reinhard Nov 01 '16 at 23:48
  • "The parameter `Fraction *fracArr[]` is an array of pointers to fractions. " It is actually a pointer to pointer to `Fraction`. – juanchopanza Nov 01 '16 at 23:48
  • 2
    @BrettReinhard [An outstanding site to answer similar questions](http://cdecl.org/). – WhozCraig Nov 02 '16 at 00:02
  • @juanchopanza you are perfectly right: a [] in a function parameter means indeed a pointer. The equivalent gcc message refers to Fraction** instead of MSVC more polite Fraction*[]. But this doesn't change op's issue and i didn't want to add to the confusion: the main problem is trying to get the adress of an array, and the mismatch in the level of indirection. – Christophe Nov 02 '16 at 00:20
  • @Christophe Actually, arrays don't _always_ decay to raw pointers when passed as parameters. Both `Fraction (&arr)[3]` and `Fraction (*arr)[3]` preserve type information, while `Fraction arr[3]` will indeed decay. – Justin Time - Reinstate Monica Nov 02 '16 at 00:42
  • @Christophe It mens your leading sentence is incorrect and misleading. Fortunately it is easy to change! – juanchopanza Nov 02 '16 at 06:22
  • @juanchopanza you're right ! Let's clarify with "expects" insead" of "is", and an additional paragraph for additional details on the relation between arrays and pointers. – Christophe Nov 02 '16 at 09:58
4

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.

Community
  • 1
  • 1
  • Thank you, this is exactly what I was looking for. – Brett Reinhard Nov 02 '16 at 01:15
  • You're welcome. I also edited in a short section about functions that return pointers or references to arrays, because they look _weird_. – Justin Time - Reinstate Monica Nov 02 '16 at 01:59
  • It is appreciated, the examples helped to fully understand how the * and & work in different scenarios. – Brett Reinhard Nov 02 '16 at 02:42
  • `Fraction*[]` is *not* an array or pointers if it is a function parameter. It is a pointer to pointer. And it is important to get that right in this answer because it is the kind of thing that is confusing OP. – juanchopanza Nov 02 '16 at 06:23
  • @juanchopanza It is indeed an array of pointers, it just immediately decays _into_ a pointer-to-a-pointer when used as a parameter. There's a difference. That said, not only does the answer very specifically demonstrate that (note the output when `func(Type*[])` is called; `typeid` shows that the array of pointers is treated as a `Type**`), but I'm talking in general (and not specifically about parameters). The question is about why `Fraction*[]` and `Fraction(*)[10]` are different types, which is what I answered. I guess I should make a note about array decay, though. – Justin Time - Reinstate Monica Nov 02 '16 at 18:34
  • No, it isn't. It is a function *parameter*. It is the argument that decays when the function gets called. The parameter is pointer to pointer. – juanchopanza Nov 02 '16 at 19:02
  • @juanchopanza I just said "decays into" because it's shorter than "gets replaced by", and I wasn't sure how long that comment was going to be. – Justin Time - Reinstate Monica Nov 02 '16 at 21:24
  • That said, the way I see it, just because the compiler silently _replaces_ an array declaration with a pointer declaration in cases where arrays aren't expected, that doesn't mean that the array declaration itself _is_ a pointer declaration when used in those cases. It just means exactly what it says: the compiler knows that an array isn't allowed here, but a pointer is, and thus it _replaces_ the array declaration with a pointer declaration while compiling. There's a semantic difference, and `T [N]` doesn't mean "array if a variable, or pointer if a parameter". – Justin Time - Reinstate Monica Nov 02 '16 at 21:26
  • To quote [CPPReference](http://en.cppreference.com/w/cpp/language/function), when determining the type of each function parameter, `If the type is "array of T" or "array of unknown bound of T", it is replaced by the type "pointer to T"`; it doesn't say `If the type is "array of T" or "array of unknown bound of T", it is instead interpreted as the type "pointer to T"`. This rather explicitly means that `T [N]` itself is _always_ an array type, but will be _replaced_ with a pointer type if it's used where an array isn't allowed. – Justin Time - Reinstate Monica Nov 02 '16 at 21:30
  • As this answer was explaining the difference being `Fraction*[]` and `Fraction(*)[]`, not about explaining why what he did doesn't work, the semantic difference is very important. If it was explaining why it doesn't work, then clarifying that the parameter itself is a `Fraction**` would be more important. Hence, forgetting to mention array decay until you reminded me. – Justin Time - Reinstate Monica Nov 02 '16 at 21:35
  • That said, I've added a note about array-to-pointer decay, and when it does and doesn't happen. – Justin Time - Reinstate Monica Nov 02 '16 at 21:39
  • So much talk, but your leading sentence is still wrong. Just fix it already. The thing isn't an array, period. – juanchopanza Nov 02 '16 at 21:55
  • Ant "decays to" may be shorted than "is adjusted to", but it also happens to be completely wrong because there is actually a thing called array decay. – juanchopanza Nov 02 '16 at 21:57
  • @juanchopanza `Fraction*[]` is an array declaration, no matter what context it's used in. If specified as a function parameter, however, it is _replaced_ by a `Fraction**` pointer declaration; I both explained and demonstrated this, too. Just because one is _replaced_ by the other in some contexts, that doesn't mean that it _is_ the other, _especially_ not in a general sense. And, as I've already said and you apparently ignored, my answer is speaking in general, not specifically about when used as a function parameter. (And no offense, but I trust CPPReference a lot more than I trust you.) – Justin Time - Reinstate Monica Nov 03 '16 at 16:13
  • Or, in other words: The question was "I understand that this does not work, but I would like to understand what the difference is between `Fraction(*)[]` to `Fraction*[]`." My answer was exactly that: the difference betwen the two, _in a general sense_. It demonstrates use as a function parameter, definition, type aliasing, and use as a return type. Claiming that a `Fraction*[]` is a `Fraction**` would be wrong, because it isn't; saying that when used as a function parameter, it is _replaced_ by a `Fraction**`, which I did in the section on array decay, is correct. – Justin Time - Reinstate Monica Nov 03 '16 at 16:19
  • If it is replaced (the terminology in the standard is "type adjustment") such that it is a pointer to a pointer, then it makes no sense to say it is an array of pointers. It is pointer to pointer, and all the array-ness is lost. You can pass it a simple pointer to a pointer to an object. You could try defining the same function with the two equivalent syntaxes and get a nice multiple definition error. It makes no sense to think of the parameter as an array, because it is adjusted to pointer. But if you want to live in a world of confusion, then go ahead. – juanchopanza Nov 03 '16 at 16:21
  • @juanchopanza No offense, but I don't think you understand that my answer _isn't **only** about when it's used as a function parameter_. See [here](http://ideone.com/lFO5A7). Note, if you will, that the _only_ time it is replaced by a pointer is when used as a function parameter. So, no, it _wouldn't_ be correct to say that `Fraction*[]` is actually a `Fraction**`, _because that isn't always true_. To be correct, the answer needs to explain what the first is, then explain _when_ it's replaced with the other, which is what it does. – Justin Time - Reinstate Monica Nov 03 '16 at 16:33
  • It so happens that the question is about `Fraction*[]` being a function parameter. That is the only thing OP will be thinking of when reading your answer. – juanchopanza Nov 03 '16 at 16:40
  • @juanchopanza I know what the question asks, but my answer is intended to be more general, in case someone else is curious about the difference in the future, but in a different context. The second part, which I added after you reminded me, is intended to describe circumstances in which it'll be replaced by a pointer and the array will decay. – Justin Time - Reinstate Monica Nov 12 '16 at 18:43
2

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.

David G
  • 113
  • 8