How to read complicated C++ type declarations when there is no identifier?

Viewed 370

There's the well known spiral and right-left rules etc. for reading complicated C++ types such as

int (*(*foo)(char *,double))[9][20];

Foo is a pointer (change direction, move out of parentheses)

to a function taking a pointer to char and a double, and returning (change direction again)

a pointer to (parentheses again, change direction)

a 2d-array of dimensions 9,20 of (reached right end, spiral outside to the left)

integers.

But how do I deal with types like this if there is no identifier, such as when defining a function parameter's type:

void foo(int *(*(* )(int(* )(int (* )(int))))())
                  ^       ^        ^
                  identifiers omitted

How do I identify the innermost element in an intuitive way? By the way, even the cdecl tool gives a syntax error on this last example, but it does compile.

Surely, the compiler has a well-defined way of parsing gibberish like this. How does it know where to start?

1 Answers

The rules for reading complex types assume that the type has already been parsed (and you know where the "innermost" point is). The rules for parsing work from the outside in, the same as reading complex expressions in math class. When you hit parentheses, give it a name and come back to it (unless it's simple enough to handle on its own). Disclaimer: I used a text editor to locate matching parentheses. ;)

The other consideration that comes into play with this declaration is that once a type is a function, the mess in the parameter list is a separate parse. For example, when parsing void (*)(big old mess), you have a pointer to a function. The big old mess is needed for the function's signature but not for understanding that you are dealing with a function.

Moving on to the example at hand:

void foo(int *(*(* )(int(* )(int (* )(int))))())

After reading void and foo, you hit parentheses with a complex mess inside. Give that mess a name.

void foo( A )

where A is int *(*(* )(int(* )(int (* )(int))))(). So your outermost parse is a unary function returning void, and we still need to parse the parameter, A. Note that we already know what the overall purpose of this text is: it declares a function named foo. The remaining types have no name because names for parameters are optional.

A: int *( B )()

where B is *(* )(int(* )(int (* )(int))). So the parameter to foo is something whose outermost type is a nullary function that returns a pointer to int. Presumably we will discover that the "something" is a pointer, but we still need to parse B to confirm that. (OK, skip ahead a little and see that B starts with an asterisk. It's a pointer to this nullary function.)

B: *(* )( C )

where C is int(* )(int (* )(int)). This is a pointer to a unary function whose parameter is some complex type, and whose return value is a pointer to what we parsed previously (the nullary function). As with the initial parse, we have discovered another place to start reading, as the mess has been pushed into the parameter list. The parameter to foo is a pointer to a unary function whose parameter is some complex type, and whose return value is a pointer to a nullary function that returns a pointer to int.

C: int(* )( D )

where D is int (* )(int). Again, the mess has moved into the parameter list. The thing at this level is a pointer to a unary function that returns an int.

D: int (* )(int)

Finally, simplicity: a pointer to a unary function that takes an int and returns an int.

So....

This declares foo to be a unary function returning void whose parameter is a pointer to a unary function, returning a pointer to a nullary function returning a pointer to int, whose parameter is a pointer to a unary function returning int whose parameter is a pointer to a unary function returning int whose parameter is int.

The English version is about as comprehensible as the code, no? :) Let's try something more structured.

  • foo
    • Returns: void
    • Parameter: pointer to a function
      • Returns: pointer to a function
        • Returns: pointer to int
      • Parameter: pointer to a function
        • Returns: int
        • Parameter: pointer to a function
          • Returns: int
          • Parameter: int

Whew. Don't do this at home, kids. Give your intermediate types names and save the programmers some grief.

Related