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:
- Read right to left starting from the identifier/inner parenthesis
- Scan right applying postfix operators first
const
applies to whatever is on its left. In the event there is nothing to the left ofconst
it then applies to the right- 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++