Declarations in C/C++

The OG syntax to declare a simple variable in C++ is as such:

//<data_type> <identifier> [= initializer];
unsigned long myUnsignedLong = 4204206969UL;

It is rather trivial to look at this declaration and determine for yourself that it is indeed of type “unsigned long”. We aren’t out of the woods yet, however; as
the * and & operators (or the pointer and reference operators respectfuly), the const modifier, and [] and () postfix operators (or the array and function operators respecfully). Consider the type of foo in the below example:

// foo is a pointer to a function that accepts a const pointer to a char array size 3 returning void
void (*foo)(char (*const)[3]) {nullptr};

auto functionToBePointedTo(char (*const cpachars)[3]) -> void {
    // do stuff
    char firstCharOfArray = {(*cpachars)[0]};
    char secondCharOfArray = {(*cpachars)[1]};
    std::cout << "Two chars in array: " << firstCharOfArray << ", " << secondCharOfArray;
}

int main() {
    // set foo to the memory address of the function
    foo = &functionToBePointedTo;

    char someString[] {'Y', '0', '\0'};
    char (*const pSomeString)[3] = &someString; 
    foo(pSomeString);

    // Two chars in array: Y, O
}

Precedence of declaration operators

As a general rule of thumb, when reading a declaration that has some fluff to it:

  1. Read right to left starting from the identifier/inner parenthesis
  2. Scan right applying postfix operators first
  3. const applies to whatever is on its left. In the event there is nothing to the left of const it then applies to the right
  4. everything else is read in the order it is encountered moving from right to left

Another Example

int (*const (*foo)())[3] {nullptr};
// applying the rules of thumb, we can read this type out loud as;
/*
int (*const (*>>foo<<)())[3];
"foo is a..."

int (*const (>>*<<foo)())[3];
"foo is a pointer to a ..."

int (*const (*foo)>>()<<)[3];
"foo is a pointer to a function that accepts no parameters and returns a..."

int (>>*const<< (*foo)())[3];
"foo is a pointer to a function that accepts no parameters and returns a constant pointer to a..."

int (*const (*foo)())>>[3]<<;
"foo is a pointer to a function that accepts no parameters and returns a constant pointer to a size 3 array of..."

>>int<< (*const (*foo)())[3];
"foo is a pointer to a function that accepts no parameters and returns a constant pointer to a size 3 array of integers"
*/

// integer array
int integerArray[] {4, 2, 0};

// constant pointer to an integer array, initialized with the memory address of integer array
int (*const cpIntegerArray)[3] {&integerArray};

// a function that returns the constant pointer to the integer array
auto getConstantPointerToIntArray() -> int (*const)[3] {
    return cpIntegerArray;
}

int main() {
  foo = &getConstantPointerToIntArray; // OK
  int (*const intArr)[3] {foo()}; // this is the integerArray from above
  std::cout << "First int + Second int: " << (*intArr)[0] + (*intArr)[1];
  // stdout: First int + Second int: 6
}

In reality, you shouldn’t encounter code like this in any serious enterprise environment however in any regard it is useful knowledge to have when reasoning about types in general in C/C++