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 foo
novamente.
- 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 foo
novamente.
- 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 &&foo
que não funciona: &foo
nã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.
&foo
pega o endereço defoo
, o que resulta em um ponteiro de função apontando parafoo
, como seria de esperar.