Existem algumas partes que permitem que todas essas combinações de operadores funcionem da mesma maneira.
A razão fundamental pela qual todos esses trabalhos é que uma função (como foo) é implicitamente conversível em um ponteiro para a função. É por isso que void (*p1_foo)() = foo;funciona: fooé implicitamente convertido em um ponteiro para si mesmo e esse ponteiro é atribuído p1_foo.
O unário &, quando aplicado a uma função, gera um ponteiro para a função, assim como gera o endereço de um objeto quando é aplicado a um objeto. Para ponteiros para funções comuns, é sempre redundante devido à conversão implícita de função em função de ponteiro. De qualquer forma, é por isso que void (*p3_foo)() = &foo;funciona.
O unário *, quando aplicado a um ponteiro de função, produz a função apontada para, assim como produz o objeto apontado para quando é aplicado a um ponteiro comum a um objeto.
Essas regras podem ser combinadas. Considere o seu penúltimo exemplo **foo:
- Primeiro,
fooé implicitamente convertido em um ponteiro para si mesmo e o primeiro *é aplicado a esse ponteiro de função, produzindo a função foonovamente.
- Em seguida, o resultado é novamente convertido implicitamente em um ponteiro para si mesmo e o segundo
*é aplicado, produzindo novamente a função foo.
- Em seguida, é convertido implicitamente em um ponteiro de função novamente e atribuído à variável.
Você pode adicionar quantos *s quiser, o resultado é sempre o mesmo. Quanto mais *s, melhor.
Também podemos considerar seu quinto exemplo &*foo:
- Primeiro,
fooé implicitamente convertido em um ponteiro para si mesmo; o unário *é aplicado, cedendo foonovamente.
- Em seguida,
&é aplicado a foo, produzindo um ponteiro para foo, que é atribuído à variável.
A &só pode ser aplicado a uma função no entanto, não a uma função que foi convertido para um ponteiro de função (a menos que, evidentemente, o ponteiro de função é uma variável, caso em que o resultado é um ponteiro-para-um-pointer- para uma função; por exemplo, você pode adicionar à sua lista void (**pp_foo)() = &p7_foo;).
É por isso &&fooque não funciona: &foonão é uma função; é um ponteiro de função que é um rvalue. Contudo,&*&*&*&*&*&*foo funcionaria da mesma forma &******&foo, porque nessas duas expressões o &sempre é aplicado a uma função e não a um ponteiro de função rvalue.
Observe também que você não precisa usar o unário *para fazer a chamada pelo ponteiro de função; ambos (*p1_foo)();e (p1_foo)();têm o mesmo resultado, novamente devido à conversão de função em função de ponteiro.
&foopega o endereço defoo, o que resulta em um ponteiro de função apontando parafoo, como seria de esperar.