Qual é a diferença entre diferentes maneiras de passar uma função como argumento para outra função?

8

Eu tenho a situação em que uma função chama uma das várias funções possíveis. Este parece ser um bom lugar para passar uma função como parâmetro. Nesta resposta quoara de Zubkov, existem três maneiras de fazer isso.

int g(int x(int)) { return x(1); }
int g(int (*x)(int)) { return x(1); }
int g(int (&x)(int)) { return x(1); }
...
int f(int n) { return n*2; }
g(f); // all three g's above work the same

Quando deve ser usado o método? Quais são as diferenças? Prefiro a abordagem mais simples, por que a primeira maneira nem sempre deve ser usada?

Para minha situação, a função é chamada apenas uma vez e eu gostaria de mantê-la simples. Eu tenho que trabalhar com passar pelo ponteiro e eu apenas chamo com g(myFunc)onde myFuncé a função que é chamada por último.

nortista
fonte
3
Nenhum deles. Use um parâmetro de modelo.
LF
2
Os dois primeiros são completamente equivalentes. O terceiro é quase o mesmo que os dois primeiros, exceto que requer um valor l. g(+f);funciona para os dois primeiros, mas não para o terceiro.
Raymond Chen
@RaymondChen "Os dois primeiros são completamente equivalentes", então, na minha opinião, o primeiro é obviamente a escolha correta, pois é mais simples. Por que complicar com um ponteiro?
Northerner
1
Por outro lado, in int g(int x(int)), xé um ponteiro, mesmo que não pareça um. A declaração global correspondente int x(int);declara uma função, não um ponteiro de função.
Raymond Chen #
Um link godbolt para fazer backup da reivindicação de @ RaymondChen . Observe que a montagem emitida também etiqueta xcomo ponteiro.
Eric

Respostas:

3

Expandindo o comentário de LF, geralmente é melhor evitar completamente os ponteiros de função e trabalhar em termos de objetos invocáveis ​​(coisas que definem operator()). Todos os seguintes permitem que você faça isso:

#include <type_traits>

// (1) unrestricted template parameter, like <algorithm> uses
template<typename Func>
int g(Func x) { return x(1); }

// (2) restricted template parameter to produce possibly better errors
template<
    typename Func,
    typename=std::enable_if_t<std::is_invocable_r_v<int, Func, int>>
>
int g(Func x) { return std::invoke(x, 1); }

// (3) template-less, trading a reduction in code size for runtime overhead and heap use
int g(std::function<int(int)> x) { return x(1); }

É importante ressaltar que tudo isso pode ser usado em funções lambda com capturas, diferente de qualquer uma das suas opções:

int y = 2;
int ret = g([y](int v) {
    return y + v;
});
Eric
fonte
Como eles são chamados? Além disso, como eles são melhores?
Northerner
Eles são chamados exatamente da mesma maneira que suas assinaturas acima. Eles são melhores porque trabalham com lambdas e outras funções com estado. Observe que todos <algorithm>usam essa abordagem para aceitar funções de retorno de chamada.
Eric
Está bem. Para confirmar, você não precisa chamar a função de modelo com um identificador de modelo? Por exemplo, você não precisa g<int>(myFunc)apenas g(myFunc)?
Northerner
Correta, a idéia é deixar o parâmetro tyoe ser inferida
Eric
Os modelos ainda funcionariam se você passasse mais de uma função como parâmetro?
nortista