Brett Reinhard Brett Reinhard - 1 month ago 7
C++ Question

What is the difference between 'struct (*)[]' and 'struct *[]'?

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 I would like to understand what the difference is between
Fraction(*)[]
to
Fraction*[]
.

I do not need to fix this error I just simply want to understand the differences between the two, seemingly similar structures.

This following code block caused the error:

const int max_size = 10;
Fraction farry[max_size];
...
display(&farry, fracCounter); // <=== particularly this line


This is the function I was calling:

void display(Fraction *fracArr[], int fracCounter){
for (int i = 0; i < fracCounter; i++){
cout << fracArr[i]->num << "/" << fracArr[i]->den << endl;
}
}

Answer

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.

Comments