La règle de la "spirale" est en quelque sorte la conséquence des règles de préséance suivantes :
T *a[] -- a is an array of pointer to T
T (*a)[] -- a is a pointer to an array of T
T *f() -- f is a function returning a pointer to T
T (*f)() -- f is a pointer to a function returning T
L'indice []
et l'appel de fonction ()
ont une priorité plus élevée que les opérateurs unaires *
donc *f()
est interprété comme *(f())
et *a[]
est interprété comme *(a[])
.
Ainsi, si vous voulez un pointeur vers un tableau ou un pointeur vers une fonction, vous devez explicitement regrouper les éléments suivants *
avec l'identifiant, comme dans (*a)[]
o (*f)()
.
Puis vous réalisez que a
et f
peuvent être des expressions plus compliquées que de simples identificateurs ; dans le cas de T (*a)[N]
, a
peut être un simple identifiant, ou un appel de fonction comme (*f())[N]
( a
-> f()
), ou un tableau comme (*p[M])[N]
, ( a
-> p[M]
), ou encore un tableau de pointeurs vers des fonctions telles que (*(*p[M])())[N]
( a
-> (*p[M])()
), etc.
Il serait bien que l'opérateur d'indirection *
était postfixe au lieu d'unaire, ce qui rendrait les déclarations un peu plus faciles à lire de gauche à droite ( void f[]*()*();
est définitivement plus fluide que void (*(*f[])())()
), mais ce n'est pas le cas.
Quand vous tombez sur une déclaration poilue comme celle-là, commencez par trouver la le plus à gauche et appliquer les règles de précédence ci-dessus, en les appliquant récursivement à tous les paramètres de la fonction :
f -- f
f[] -- is an array
*f[] -- of pointers ([] has higher precedence than *)
(*f[])() -- to functions
*(*f[])() -- returning pointers
(*(*f[])())() -- to functions
void (*(*f[])())(); -- returning void
El signal
de la bibliothèque standard est probablement le spécimen type de ce genre de folie :
signal -- signal
signal( ) -- is a function with parameters
signal( sig, ) -- sig
signal(int sig, ) -- which is an int and
signal(int sig, func ) -- func
signal(int sig, *func ) -- which is a pointer
signal(int sig, (*func)(int)) -- to a function taking an int
signal(int sig, void (*func)(int)) -- returning void
*signal(int sig, void (*func)(int)) -- returning a pointer
(*signal(int sig, void (*func)(int)))(int) -- to a function taking an int
void (*signal(int sig, void (*func)(int)))(int); -- and returning void
À ce stade, la plupart des gens disent "utiliser des typedefs", ce qui est certainement une option :
typedef void outerfunc(void);
typedef outerfunc *innerfunc(void);
innerfunc *f[N];
Mais...
Comment voulez-vous utiliser f
dans une expression ? Vous savez que c'est un tableau de pointeurs, mais comment l'utiliser pour exécuter la bonne fonction ? Vous devez passer en revue les typedefs et trouver la syntaxe correcte. En revanche, la version "nue" est assez simple, mais elle vous dit exactement comment faire pour utiliser f
dans une expression (à savoir, (*(*f[i])())();
(en supposant qu'aucune des deux fonctions ne prend d'arguments).