C ++ lambda con capturas como puntero de función

Estaba jugando con C ++ lambdas y su conversión implícita a indicadores de función. Mi ejemplo inicial fue usarlos como callback para la función ftw. Esto funciona como se esperaba

#include  #include  using namespace std; int main() { auto callback = [](const char *fpath, const struct stat *sb, int typeflag) -> int { cout << fpath << endl; return 0; }; int ret = ftw("/etc", callback, 1); return ret; } 

Después de modificarlo para usar capturas:

 int main() { vector entries; auto callback = [&](const char *fpath, const struct stat *sb, int typeflag) -> int { entries.push_back(fpath); return 0; }; int ret = ftw("/etc", callback, 1); for (auto entry : entries ) { cout << entry << endl; } return ret; } 

Tengo el error del comstackdor:

 error: cannot convert 'main()::' to '__ftw_func_t {aka int (*)(const char*, const stat*, int)}' for argument '2' to 'int ftw(const char*, __ftw_func_t, int)' 

Después de leer un poco Aprendí que las lambdas que usan capturas no se pueden convertir implícitamente a punteros de función.

¿Hay una solución para esto? ¿El hecho de que no puedan convertirse “implícitamente” significa que pueden convertirse “explícitamente”? (Intenté lanzar, sin éxito). ¿Cuál sería una manera limpia de modificar el ejemplo de trabajo para poder agregar las entradas a algún objeto usando lambdas ?.

Dado que la captura de lambdas necesita preservar un estado, no existe realmente una “solución alternativa” simple, ya que no son solo funciones ordinarias. El punto sobre un puntero de función es que apunta a una única función global, y esta información no tiene espacio para un estado.

La solución más cercana (que esencialmente descarta la condición de estado) es proporcionar algún tipo de variable global a la que se accede desde su lambda / función. Por ejemplo, podría hacer un objeto functor tradicional y darle una función de miembro estático que haga referencia a alguna instancia única (global / estática).

Pero eso es una especie de derrotar todo el propósito de capturar lambdas.

Me encontré con este problema.

El código comstack bien sin capturas lambda, pero hay un error de conversión de tipo con captura lambda.

La solución con C ++ 11 es usar std::function (edit: otra solución que no requiere modificar la firma de la función se muestra después de este ejemplo). También puede usar boost::function (que realmente se ejecuta significativamente más rápido). Código de ejemplo: modificado para que compile, comstackdo con gcc 4.7.1 :

 #include  #include  #include  using namespace std; int ftw(const char *fpath, std::function callback) { return callback(fpath); } int main() { vector entries; std::function callback = [&](const char *fpath) -> int { entries.push_back(fpath); return 0; }; int ret = ftw("/etc", callback); for (auto entry : entries ) { cout << entry << endl; } return ret; } 

Editar: tuve que volver a visitar esto cuando me encontré con un código heredado en el que no pude modificar la firma de la función original, pero aún necesitaba usar lambdas. A continuación, se incluye una solución que no requiere modificar la firma de la función de la función original:

 #include  #include  #include  using namespace std; // Original ftw function taking raw function pointer that cannot be modified int ftw(const char *fpath, int(*callback)(const char *path)) { return callback(fpath); } static std::function ftw_callback_function; static int ftw_callback_helper(const char *path) { return ftw_callback_function(path); } // ftw overload accepting lambda function static int ftw(const char *fpath, std::function callback) { ftw_callback_function = callback; return ftw(fpath, ftw_callback_helper); } int main() { vector entries; std::function callback = [&](const char *fpath) -> int { entries.push_back(fpath); return 0; }; int ret = ftw("/etc", callback); for (auto entry : entries ) { cout << entry << endl; } return ret; } 

ORIGINAL

Las funciones Lambda son muy prácticas y reducen un código. En mi caso, necesitaba lambdas para progtwigción paralela. Pero requiere punteros de captura y función. Mi solución está aquí. Pero tenga cuidado con el scope de las variables que capturó.

 template Tret lambda_ptr_exec(T* v) { return (Tret) (*v)(); } template Tfp lambda_ptr(T& v) { return (Tfp) lambda_ptr_exec; } 

Ejemplo

 int a = 100; auto b = [&]() { a += 1;}; void (*fp)(void*) = lambda_ptr(b); fp(&b); 

Ejemplo con un valor de retorno

 int a = 100; auto b = [&]() {return a;}; int (*fp)(void*) = lambda_ptr(b); fp(&b); 

ACTUALIZAR

Versión mejorada

Pasó un tiempo desde que se publicó la primera publicación sobre C ++ lambda con capturas como un puntero a la función. Como fue útil para mí y para otras personas, hice algunas mejoras.

La api del puntero C de la función estándar usa la convención vacía fn (void * data). Por defecto, esta convención se usa y lambda debe declararse con un argumento void *.

Implementación mejorada

 struct Lambda { template static Tret lambda_ptr_exec(void* data) { return (Tret) (*(T*)fn())(data); } template static Tfp ptr(T& t) { fn(&t); return (Tfp) lambda_ptr_exec; } template static void* fn(void* new_fn = nullptr) { static void* fn; if (new_fn != nullptr) fn = new_fn; return fn; } }; 

Exapmle

 int a = 100; auto b = [&](void*) {return ++a;}; 

Convirtiendo lambda con capturas en un puntero C

 void (*f1)(void*) = Lambda::ptr(b); f1(nullptr); printf("%d\n", a); // 101 

Puede ser usado de esta manera también

 auto f2 = Lambda::ptr(b); f2(nullptr); printf("%d\n", a); // 102 

En caso de que se use el valor de retorno

 int (*f3)(void*) = Lambda::ptr(b); printf("%d\n", f3(nullptr)); // 103 

Y en caso de que se usen datos

 auto b2 = [&](void* data) {return *(int*)(data) + a;}; int (*f4)(void*) = Lambda::ptr(b2); int data = 5; printf("%d\n", f4(&data)); // 108 

Hehe: una pregunta bastante antigua, pero aún así …

 #include  #include  #include  using namespace std; // We dont try to outsmart the compiler... template int ftw(const char *fpath, T callback) { return callback(fpath); } int main() { vector entries; // ... now the @ftw can accept lambda int ret = ftw("/etc", [&](const char *fpath) -> int { entries.push_back(fpath); return 0; }); // ... and function object too struct _ { static int lambda(vector& entries, const char* fpath) { entries.push_back(fpath); return 0; } }; ret = ftw("/tmp", bind(_::lambda, ref(entries), placeholders::_1)); for (auto entry : entries ) { cout << entry << endl; } return ret; } 

Usando el método localmente global (estático) se puede hacer como se siguió

 template  auto wrap(T t) { static T fn = t; return [] { fn(); }; } 

Supongamos que tenemos

 void some_c_func(void (*callback)()); 

Entonces el uso será

 some_c_func(wrap([&] { // code })); 

Esto funciona porque cada lambda tiene una firma única, por lo que hacerlo estático no es un problema. Seguir un contenedor genérico con un número variado de argumentos y cualquier tipo de devolución utilizando el mismo método.

 template  struct lambda_traits : lambda_traits { }; template  struct lambda_traits { typedef R (*pointer)(Args...); static pointer cify(T t) { static T fn = t; return [](Args... args) { return fn(args...); }; } }; template  inline typename lambda_traits::pointer cify(T t) { return lambda_traits::cify(t); } 

Y uso similar

 void some_c_func(int (*callback)(some_struct*, float)); some_c_func(cify([&](some_struct* s, float f) { // making use of "s" and "f" return 0; })); 

Existe una forma maliciosa de convertir una lambda de captura en un puntero de función, pero debe tener cuidado al usarla:

https://codereview.stackexchange.com/questions/79612/c-ifying-a-capturing-lambda

Su código se vería así (advertencia: comstackción del cerebro):

 int main() { vector entries; auto const callback = cify([&](const char *fpath, const struct stat *sb, int typeflag) -> int { entries.push_back(fpath); return 0; }); int ret = ftw("/etc", callback, 1); for (auto entry : entries ) { cout << entry << endl; } return ret; } 

Encontré una respuesta aquí: http://meh.schizofreni.co/programming/magic/2013/01/23/function-pointer-from-lambda.html

Convierte lambda pointer a void* y convierte de nuevo cuando sea necesario.

  1. void* :

    auto voidfunction = new decltype (to_function (lambda)) (to_function (lambda));

  2. desde el void* :

    función automática = static_cast (voidfunction);