Pasando lambda como puntero de función

¿Es posible pasar una función lambda como un puntero a la función? Si es así, debo estar haciendo algo incorrectamente porque estoy obteniendo un error de comstackción.

Considera el siguiente ejemplo

using DecisionFn = bool(*)(); class Decide { public: Decide(DecisionFn dec) : _dec{dec} {} private: DecisionFn _dec; }; int main() { int x = 5; Decide greaterThanThree{ [x](){ return x > 3; } }; return 0; } 

Cuando bash comstackr esto , aparece el siguiente error de comstackción:

 In function 'int main()': 17:31: error: the value of 'x' is not usable in a constant expression 16:9: note: 'int x' is not const 17:53: error: no matching function for call to 'Decide::Decide()' 17:53: note: candidates are: 9:5: note: Decide::Decide(DecisionFn) 9:5: note: no known conversion for argument 1 from 'main()::' to 'DecisionFn {aka bool (*)()}' 6:7: note: constexpr Decide::Decide(const Decide&) 6:7: note: no known conversion for argument 1 from 'main()::' to 'const Decide&' 6:7: note: constexpr Decide::Decide(Decide&&) 6:7: note: no known conversion for argument 1 from 'main()::' to 'Decide&&' 

Ese es un maldito mensaje de error para digerir, pero creo que lo que constexpr de él es que el lambda no puede tratarse como un constexpr por lo que no puedo pasarlo como un puntero a la función. He intentado hacer x const también, pero eso no parece ayudar.

Una lambda solo puede convertirse a un puntero de función si no captura, del borrador de la sección estándar de C ++ 11 5.1.2 [expr.prim.lambda] dice ( énfasis mío ):

El tipo de cierre para una expresión lambda sin captura lambda tiene una función pública no virtual de conversión constante no virtual para señalar a la función que tiene el mismo parámetro y tipos de retorno que el operador de llamada de función del tipo de cierre. El valor devuelto por esta función de conversión será la dirección de una función que, cuando se invoca, tiene el mismo efecto que invocar al operador de llamada de función del tipo de cierre.

Tenga en cuenta que cppreference también lo cubre en su sección sobre funciones de Lambda .

Entonces las siguientes alternativas funcionarían:

 typedef bool(*DecisionFn)(int); Decide greaterThanThree{ []( int x ){ return x > 3; } }; 

y también esto:

 typedef bool(*DecisionFn)(); Decide greaterThanThree{ [](){ return true ; } }; 

y como señala 5gon12eder , también puede usar std::function , pero tenga en cuenta que std::function tiene un gran peso , por lo que no es una solución de compromiso sin costo.

La respuesta de Shafik Yaghmour explica correctamente por qué el lambda no se puede pasar como un puntero de función si tiene una captura. Me gustaría mostrar dos soluciones simples para el problema.

  1. Utilice la std::function lugar de punteros de función sin formato.

    Esta es una solución muy limpia. Sin embargo, tenga en cuenta que incluye una sobrecarga adicional para el borrado de tipo (probablemente una llamada de función virtual).

     #include  #include  struct Decide { using DecisionFn = std::function; Decide(DecisionFn dec) : dec_ {std::move(dec)} {} DecisionFn dec_; }; int main() { int x = 5; Decide greaterThanThree { [x](){ return x > 3; } }; } 
  2. Use una expresión lambda que no capture nada.

    Dado que su predicado es realmente solo una constante booleana, lo siguiente funcionaría rápidamente alrededor del problema actual. Vea esta respuesta para una buena explicación de por qué y cómo está funcionando.

     // Your 'Decide' class as in your post. int main() { int x = 5; Decide greaterThanThree { (x > 3) ? [](){ return true; } : [](){ return false; } }; } 

Sé que esto es un poco viejo …

Pero quería agregar:

¡La expresión lambda (incluso las capturadas) se puede manejar como un puntero de función!

Es complicado porque una expresión Lambda no es una función simple. En realidad es un objeto con un operador ().

¡Cuando eres creativo, puedes usar esto! Piense en una clase de “función” en el estilo de std :: function. Si guardas el objeto!

También puede usar el puntero de función.

Para usar el puntero a la función, puede usar lo siguiente:

 int first = 5; auto lambda = [=](int x, int z) { return x + z + first; }; int(decltype(lambda)::*ptr)(int, int)const = &decltype(lambda)::operator(); std::cout << "test = " << (lambda.*ptr)(2, 3) << std::endl; 

Para construir una clase que pueda comenzar a funcionar como una "función estándar", haré un breve ejemplo. Primero necesita una clase / estructura que pueda almacenar el puntero de objeto y función, también necesita un operador () para ejecutarla:

 // OT => Object Type // RT => Return Type // A ... => Arguments template struct lambda_expression { OT _object; RT(OT::*_function)(A...)const; lambda_expression(const OT & object) : _object(object), _function(&decltype(_object)::operator()) {} RT operator() (A ... args) const { return (_object.*_function)(args...); } }; 

Con esto ahora puedes ejecutar lambdas capturadas, no capturadas, tal como lo haces con el original:

 auto capture_lambda() { int first = 5; auto lambda = [=](int x, int z) { return x + z + first; }; return lambda_expression(lambda); } auto noncapture_lambda() { auto lambda = [](int x, int z) { return x + z; }; return lambda_expression(lambda); } void refcapture_lambda() { int test; auto lambda = [&](int x, int z) { test = x + z; }; lambda_expressionf(lambda); f(2, 3); std::cout << "test value = " << test << std::endl; } int main(int argc, char **argv) { auto f_capture = capture_lambda(); auto f_noncapture = noncapture_lambda(); std::cout << "main test = " << f_capture(2, 3) << std::endl; std::cout << "main test = " << f_noncapture(2, 3) << std::endl; refcapture_lambda(); system("PAUSE"); return 0; } 

Este código funciona con VS2015 Espero que ayude:)

¡Saludos!

Editar: plantilla de agujas eliminada FP, parámetro de puntero de función eliminado, renombrado a lambda_expression

Actualización 04.07.17:

 template  struct function : public function {}; template  struct function { private: C mObject; public: function(const C & obj) : mObject(obj) {} template typename std::result_of::type operator()(Args... a) { return this->mObject.operator()(a...); } template typename std::result_of::type operator()(Args... a) const { return this->mObject.operator()(a...); } }; namespace make { template auto function(const C & obj) { return ::function(obj); } } int main(int argc, char ** argv) { auto func = make::function([](int y, int x) { return x*y; }); std::cout << func(2, 4) << std::endl; system("PAUSE"); return 0; } 

La captura de lambdas no se puede convertir a punteros de función, como se señaló en esta respuesta .

Sin embargo, a menudo es bastante molesto proporcionar un puntero a una API que solo acepta uno. El método más frecuentemente citado para hacerlo es proporcionar una función y llamar a un objeto estático con ella.

 static Callable callable; static bool wrapper() { return callable(); } 

Esto es tedioso Llevamos esta idea más allá y automatizamos el proceso de creación de wrapper y hacemos la vida mucho más fácil.

 #include #include template union storage { storage() {} std::decay_t callable; }; template auto fnptr_(Callable&& c, Ret (*)(Args...)) { static bool used = false; static storage s; using type = decltype(s.callable); if(used) s.callable.~type(); new (&s.callable) type(std::forward(c)); used = true; return [](Args... args) -> Ret { return Ret(s.callable(std::forward(args)...)); }; } template Fn* fnptr(Callable&& c) { return fnptr_(std::forward(c), (Fn*)nullptr); } 

Y úsalo como

 void foo(void (*fn)()) { fn(); } int main() { int i = 42; auto fn = fnptr([i]{std::cout << i;}); foo(fn); // compiles! } 

Vivir

Esto es esencialmente declarar una función anónima en cada aparición de fnptr .

Tenga en cuenta que las invocaciones de fnptr sobrescriben los fnptr de fnptr del mismo tipo. Remediamos esto, hasta cierto punto, con el parámetro int N

 std::function func1, func2; auto fn1 = fnptr(func1); auto fn2 = fnptr(func2); // different function 

Como lo mencionaron los demás, puede sustituir la función Lambda en lugar del puntero a la función. Estoy usando este método en mi interfaz C ++ para resolver el ODS F77 RKSUITE.

 //C interface to Fortran subroutine UT extern "C" void UT(void(*)(double*,double*,double*),double*,double*,double*, double*,double*,double*,int*); // C++ wrapper which calls extern "C" void UT routine static void rk_ut(void(*)(double*,double*,double*),double*,double*,double*, double*,double*,double*,int*); // Call of rk_ut with lambda passed instead of function pointer to derivative // routine mathlib::RungeKuttaSolver::rk_ut([](double* T,double* Y,double* YP)->void{YP[0]=Y[1]; YP[1]= -Y[0];}, TWANT,T,Y,YP,YMAX,WORK,UFLAG); 

Si bien el enfoque de la plantilla es inteligente por varias razones, es importante recordar el ciclo de vida de la lambda y las variables capturadas. Si se va a usar cualquier forma de un puntero lambda y la lambda no es una continuación descendente, entonces solo se debe usar una copia [=] lambda. Es decir, incluso entonces, capturar un puntero a una variable en la stack es INSEGURO si el tiempo de vida de los punteros capturados (desenrollado de la stack) es más corto que la duración de la lambda.

Una solución más simple para capturar un lambda como un puntero es:

auto pLamdba = new std::function<...fn-sig...>([=](...fn-sig...){...});

por ejemplo, new std::function([=]() -> void {...}

Solo recuerde delete pLamdba más tarde delete pLamdba así que asegúrese de no filtrar la memoria lambda. El secreto para darse cuenta aquí es que lambdas puede capturar lambdas (pregúntese cómo funciona) y también que para que std::function funcione genéricamente, la implementación lambda necesita contener suficiente información interna para proporcionar acceso al tamaño de la lambda (y capturado) de datos (que es la razón por la que la delete debería funcionar [ejecutando destructores de tipos capturados]).