¿Cuál es la diferencia entre las diferentes formas de pasar una función como argumento a otra función?

8

Tengo la situación en la que una función llama a una de varias funciones posibles. Este parece ser un buen lugar para pasar una función como parámetro. En esta respuesta de Quoara de Zubkov hay tres formas de hacer esto.

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

¿Cuándo se debe utilizar qué método? ¿Qué hay diferencias? Prefiero el enfoque más simple, ¿por qué no debería usarse siempre la primera forma?

Para mi situación, la función solo se llama una vez y me gustaría que sea simple. Lo tengo trabajando con pasar por puntero y simplemente lo llamo con g(myFunc)dónde myFuncestá la función que se llama al último.

norteño
fuente
3
Ninguno de ellos. Utiliza un parámetro de plantilla.
LF
2
Los dos primeros son completamente equivalentes. El tercero es casi el mismo que los dos primeros, excepto que requiere un valor l. g(+f);funciona para los dos primeros, pero no para el tercero.
Raymond Chen el
@RaymondChen "Los dos primeros son completamente equivalentes", entonces, en mi opinión, el primero es obviamente la opción correcta, ya que es más simple. ¿Por qué complicarlo con un puntero?
norteño
1
Por otro lado, en int g(int x(int)), xes un puntero a pesar de que no se parece a uno. La declaración global correspondiente int x(int);declara una función, no un puntero de función.
Raymond Chen el
Un enlace de Godbolt para respaldar el reclamo de @ RaymondChen . Tenga en cuenta que las etiquetas de ensamblaje emitidas también xcomo puntero.
Eric

Respuestas:

3

Ampliando el comentario de LF, a menudo es mejor evitar por completo los punteros de función y trabajar en términos de objetos invocables (cosas que definen operator()). Todo lo siguiente le permite hacer eso:

#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); }

Es importante destacar que todos estos pueden usarse en funciones lambda con capturas, a diferencia de cualquiera de sus opciones:

int y = 2;
int ret = g([y](int v) {
    return y + v;
});
Eric
fuente
¿Cómo se llaman estos? Además, ¿cómo son estos mejores?
norteño
Se llaman exactamente de la misma manera que sus firmas anteriores. Son mejores porque trabajan con lambdas y otras funciones con estado. Tenga en cuenta que todos <algorithm>utilizan este enfoque para aceptar funciones de devolución de llamada.
Eric
Okay. Para confirmar, ¿no necesita llamar a la función con plantilla con un identificador de plantilla? Por ejemplo, no necesitas g<int>(myFunc)solo g(myFunc)?
norteño
Correcto, la idea es dejar que se infiera el parámetro tyoe
Eric
¿Seguirían funcionando las plantillas si está pasando más de una función como parámetro?
norteño